Merge branch 'updatePackages' into 'main'
Update packages See merge request v3/service/ad!23
This commit is contained in:
commit
a5fb44f9e4
17
.env.dist
17
.env.dist
|
@ -10,24 +10,9 @@ DATABASE_URL="postgresql://mobicoop:mobicoop@v3-db:5432/mobicoop?schema=ad"
|
||||||
# MESSAGE BROKER
|
# MESSAGE BROKER
|
||||||
MESSAGE_BROKER_URI=amqp://v3-broker:5672
|
MESSAGE_BROKER_URI=amqp://v3-broker:5672
|
||||||
MESSAGE_BROKER_EXCHANGE=mobicoop
|
MESSAGE_BROKER_EXCHANGE=mobicoop
|
||||||
|
MESSAGE_BROKER_EXCHANGE_DURABILITY=true
|
||||||
|
|
||||||
# REDIS
|
# REDIS
|
||||||
REDIS_HOST=v3-redis
|
REDIS_HOST=v3-redis
|
||||||
REDIS_PASSWORD=redis
|
REDIS_PASSWORD=redis
|
||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
|
|
||||||
# DEFAULT CARPOOL DEPARTURE TIME MARGIN (in seconds)
|
|
||||||
DEPARTURE_TIME_MARGIN=900
|
|
||||||
|
|
||||||
# DEFAULT ROLE
|
|
||||||
ROLE=passenger
|
|
||||||
|
|
||||||
# SEATS PROPOSED AS DRIVER / REQUESTED AS PASSENGER
|
|
||||||
SEATS_PROPOSED=3
|
|
||||||
SEATS_REQUESTED=1
|
|
||||||
|
|
||||||
# ACCEPT ONLY SAME FREQUENCY REQUESTS
|
|
||||||
STRICT_FREQUENCY=false
|
|
||||||
|
|
||||||
# default timezone
|
|
||||||
TIMEZONE=Europe/Paris
|
|
||||||
|
|
34
README.md
34
README.md
|
@ -64,12 +64,14 @@ The app exposes the following [gRPC](https://grpc.io/) services :
|
||||||
{
|
{
|
||||||
"userId": "80c9bb02-0931-4a1d-bea6-22d358992245",
|
"userId": "80c9bb02-0931-4a1d-bea6-22d358992245",
|
||||||
"driver": true,
|
"driver": true,
|
||||||
|
"passenger": false,
|
||||||
"seatsProposed": 3,
|
"seatsProposed": 3,
|
||||||
"frequency": "PUNCTUAL",
|
"frequency": "PUNCTUAL",
|
||||||
"fromDate": "2023-01-15",
|
"fromDate": "2023-01-15",
|
||||||
"toDate": "2023-01-15",
|
"toDate": "2023-01-15",
|
||||||
"schedule": [
|
"schedule": [
|
||||||
{
|
{
|
||||||
|
"day": 0,
|
||||||
"time": "09:00",
|
"time": "09:00",
|
||||||
"margin": 900
|
"margin": 900
|
||||||
}
|
}
|
||||||
|
@ -103,7 +105,7 @@ The app exposes the following [gRPC](https://grpc.io/) services :
|
||||||
{
|
{
|
||||||
"userId": "80c9bb02-0931-4a1d-bea6-22d358992245",
|
"userId": "80c9bb02-0931-4a1d-bea6-22d358992245",
|
||||||
"driver": true,
|
"driver": true,
|
||||||
"pasenger": true,
|
"passenger": true,
|
||||||
"seatsProposed": 3,
|
"seatsProposed": 3,
|
||||||
"seatsRequested": 1,
|
"seatsRequested": 1,
|
||||||
"frequency": "PUNCTUAL",
|
"frequency": "PUNCTUAL",
|
||||||
|
@ -111,6 +113,7 @@ The app exposes the following [gRPC](https://grpc.io/) services :
|
||||||
"toDate": "2023-01-15",
|
"toDate": "2023-01-15",
|
||||||
"schedule": [
|
"schedule": [
|
||||||
{
|
{
|
||||||
|
"day": 0,
|
||||||
"time": "09:00",
|
"time": "09:00",
|
||||||
"margin": 900
|
"margin": 900
|
||||||
}
|
}
|
||||||
|
@ -144,22 +147,25 @@ The app exposes the following [gRPC](https://grpc.io/) services :
|
||||||
{
|
{
|
||||||
"userId": "80c9bb02-0931-4a1d-bea6-22d358992245",
|
"userId": "80c9bb02-0931-4a1d-bea6-22d358992245",
|
||||||
"passenger": true,
|
"passenger": true,
|
||||||
"seatsPassenger": 1,
|
"seatsRequested": 1,
|
||||||
"frequency": "RECURRRENT",
|
"frequency": "RECURRRENT",
|
||||||
"fromDate": "2023-01-15",
|
"fromDate": "2023-01-15",
|
||||||
"toDate": "2023-12-31",
|
"toDate": "2023-12-31",
|
||||||
"schedule": [
|
"schedule": [
|
||||||
{
|
{
|
||||||
"day": 1,
|
"day": 1,
|
||||||
"time": "07:00"
|
"time": "07:00",
|
||||||
|
"margin": 600
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"day": 2,
|
"day": 2,
|
||||||
"time": "07:05"
|
"time": "07:05",
|
||||||
|
"margin": 600
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"day": 5,
|
"day": 5,
|
||||||
"time": "07:10"
|
"time": "07:10",
|
||||||
|
"margin": 600
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"waypoints": [
|
"waypoints": [
|
||||||
|
@ -188,22 +194,20 @@ The app exposes the following [gRPC](https://grpc.io/) services :
|
||||||
The list of possible options when creating an ad :
|
The list of possible options when creating an ad :
|
||||||
|
|
||||||
- userId: the user id (as a uuid)
|
- userId: the user id (as a uuid)
|
||||||
- driver (boolean, optional): if the ad is a driver ad
|
- driver (boolean): if the ad is a driver ad. Note that at least a role must be set to true (driver and/or passenger).
|
||||||
- passenger (boolean, optional): if the ad is a passenger ad
|
- passenger (boolean): if the ad is a passenger ad. Note that at least a role must be set to true (driver and/or passenger).
|
||||||
- frequency: `PUNCTUAL` or `RECURRENT`
|
- frequency: `PUNCTUAL` or `RECURRENT`
|
||||||
- fromDate: start date for recurrent ad, carpool date for punctual ad
|
- fromDate: start date for recurrent ad, carpool date for punctual ad
|
||||||
- toDate: end date for recurrent ad, same as fromDate for punctual ad
|
- toDate: end date for recurrent ad, same as fromDate for punctual ad
|
||||||
- schedule: an array of schedule items, as schedule item containing :
|
- schedule: an array of schedule items, a schedule item containing :
|
||||||
- the week day as a number, from 0 (sunday) to 6 (saturday) if the ad is recurrent
|
- the week day as a number, from 0 (sunday) to 6 (saturday)
|
||||||
- the departure time (as HH:MM)
|
- the departure time (as HH:MM)
|
||||||
- the margin around the departure time in seconds (optional)
|
- the margin around the departure time in seconds
|
||||||
- seatsProposed (optional): number of seats proposed as driver
|
- seatsProposed: number of seats proposed as driver (required if `driver` is true)
|
||||||
- seatsRequested (optional): number of seats requested as passenger
|
- seatsRequested: number of seats requested as passenger (required if `passenger` is true)
|
||||||
- strict (boolean, optional): if set to true, allow matching only with similar frequency ads
|
- strict (boolean): if set to true, allow matching only with similar frequency ads
|
||||||
- waypoints: an array of addresses that represent the waypoints of the journey (only first and last waypoints are used for passenger ads). Note that positions are **required** and **must** be consecutives
|
- waypoints: an array of addresses that represent the waypoints of the journey (only first and last waypoints are used for passenger ads). Note that positions are **required** and **must** be consecutives
|
||||||
|
|
||||||
Default values must be set in `.env` file.
|
|
||||||
|
|
||||||
## Messages
|
## Messages
|
||||||
|
|
||||||
As mentionned earlier, RabbitMQ messages are sent after these events :
|
As mentionned earlier, RabbitMQ messages are sent after these events :
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
86
package.json
86
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@mobicoop/ad",
|
"name": "@mobicoop/ad",
|
||||||
"version": "2.1.1",
|
"version": "2.2.0",
|
||||||
"description": "Mobicoop V3 Ad",
|
"description": "Mobicoop V3 Ad",
|
||||||
"author": "sbriat",
|
"author": "sbriat",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
@ -30,56 +30,56 @@
|
||||||
"migrate:deploy": "npx prisma migrate deploy"
|
"migrate:deploy": "npx prisma migrate deploy"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@grpc/grpc-js": "^1.8.14",
|
"@grpc/grpc-js": "^1.9.5",
|
||||||
"@grpc/proto-loader": "^0.7.6",
|
"@grpc/proto-loader": "^0.7.10",
|
||||||
"@liaoliaots/nestjs-redis": "^9.0.5",
|
"@songkeys/nestjs-redis": "^10.0.0",
|
||||||
"@mobicoop/configuration-module": "^1.2.0",
|
"@mobicoop/configuration-module": "^2.0.0",
|
||||||
"@mobicoop/ddd-library": "^1.0.0",
|
"@mobicoop/ddd-library": "^2.0.0",
|
||||||
"@mobicoop/health-module": "^2.0.0",
|
"@mobicoop/health-module": "^2.3.1",
|
||||||
"@mobicoop/message-broker-module": "^1.2.0",
|
"@mobicoop/message-broker-module": "^2.1.1",
|
||||||
"@nestjs/common": "^9.0.0",
|
"@nestjs/common": "^10.2.7",
|
||||||
"@nestjs/config": "^2.3.1",
|
"@nestjs/config": "^3.1.1",
|
||||||
"@nestjs/core": "^9.0.0",
|
"@nestjs/core": "^10.2.7",
|
||||||
"@nestjs/cqrs": "^9.0.3",
|
"@nestjs/cqrs": "^10.2.6",
|
||||||
"@nestjs/event-emitter": "^1.4.2",
|
"@nestjs/event-emitter": "^2.0.2",
|
||||||
"@nestjs/microservices": "^9.4.0",
|
"@nestjs/microservices": "^10.2.7",
|
||||||
"@nestjs/platform-express": "^9.0.0",
|
"@nestjs/platform-express": "^10.2.7",
|
||||||
"@nestjs/terminus": "^9.2.2",
|
"@nestjs/terminus": "^10.1.1",
|
||||||
"@prisma/client": "^4.13.0",
|
"@prisma/client": "^5.4.2",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.0",
|
"class-validator": "^0.14.0",
|
||||||
"geo-tz": "^7.0.7",
|
"geo-tz": "^7.0.7",
|
||||||
"ioredis": "^5.3.2",
|
"ioredis": "^5.3.2",
|
||||||
"nestjs-request-context": "^2.1.0",
|
"nestjs-request-context": "^3.0.0",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rxjs": "^7.2.0",
|
"rxjs": "^7.8.1",
|
||||||
"timezonecomplete": "^5.12.4"
|
"timezonecomplete": "^5.12.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/cli": "^9.0.0",
|
"@nestjs/cli": "^10.1.18",
|
||||||
"@nestjs/schematics": "^9.0.0",
|
"@nestjs/schematics": "^10.0.2",
|
||||||
"@nestjs/testing": "^9.0.0",
|
"@nestjs/testing": "^10.2.7",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.19",
|
||||||
"@types/jest": "29.5.0",
|
"@types/jest": "29.5.5",
|
||||||
"@types/node": "18.15.11",
|
"@types/node": "20.8.6",
|
||||||
"@types/supertest": "^2.0.11",
|
"@types/supertest": "^2.0.14",
|
||||||
"@types/uuid": "^9.0.2",
|
"@types/uuid": "^9.0.5",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
"@typescript-eslint/eslint-plugin": "^6.7.5",
|
||||||
"@typescript-eslint/parser": "^5.0.0",
|
"@typescript-eslint/parser": "^6.7.5",
|
||||||
"dotenv-cli": "^7.2.1",
|
"dotenv-cli": "^7.3.0",
|
||||||
"eslint": "^8.0.1",
|
"eslint": "^8.51.0",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^9.0.0",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^5.0.1",
|
||||||
"jest": "29.5.0",
|
"jest": "29.7.0",
|
||||||
"prettier": "^2.3.2",
|
"prettier": "^3.0.3",
|
||||||
"prisma": "^4.13.0",
|
"prisma": "^5.4.2",
|
||||||
"source-map-support": "^0.5.20",
|
"source-map-support": "^0.5.21",
|
||||||
"supertest": "^6.1.3",
|
"supertest": "^6.3.3",
|
||||||
"ts-jest": "29.0.5",
|
"ts-jest": "29.1.1",
|
||||||
"ts-loader": "^9.2.3",
|
"ts-loader": "^9.5.0",
|
||||||
"ts-node": "^10.0.0",
|
"ts-node": "^10.9.1",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"typescript": "^4.7.4"
|
"typescript": "^5.2.2"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"moduleFileExtensions": [
|
"moduleFileExtensions": [
|
||||||
|
@ -88,6 +88,7 @@
|
||||||
"ts"
|
"ts"
|
||||||
],
|
],
|
||||||
"modulePathIgnorePatterns": [
|
"modulePathIgnorePatterns": [
|
||||||
|
".constants.ts",
|
||||||
".module.ts",
|
".module.ts",
|
||||||
".dto.ts",
|
".dto.ts",
|
||||||
".di-tokens.ts",
|
".di-tokens.ts",
|
||||||
|
@ -105,6 +106,7 @@
|
||||||
"**/*.(t|j)s"
|
"**/*.(t|j)s"
|
||||||
],
|
],
|
||||||
"coveragePathIgnorePatterns": [
|
"coveragePathIgnorePatterns": [
|
||||||
|
".constants.ts",
|
||||||
".module.ts",
|
".module.ts",
|
||||||
".dto.ts",
|
".dto.ts",
|
||||||
".di-tokens.ts",
|
".di-tokens.ts",
|
||||||
|
|
|
@ -26,15 +26,19 @@ import { HEALTH_CRITICAL_LOGGING_KEY, SERVICE_NAME } from './app.constants';
|
||||||
useFactory: async (
|
useFactory: async (
|
||||||
configService: ConfigService,
|
configService: ConfigService,
|
||||||
): Promise<ConfigurationModuleOptions> => ({
|
): Promise<ConfigurationModuleOptions> => ({
|
||||||
domain: configService.get<string>('SERVICE_CONFIGURATION_DOMAIN'),
|
domain: configService.get<string>(
|
||||||
|
'SERVICE_CONFIGURATION_DOMAIN',
|
||||||
|
) as string,
|
||||||
messageBroker: {
|
messageBroker: {
|
||||||
uri: configService.get<string>('MESSAGE_BROKER_URI'),
|
uri: configService.get<string>('MESSAGE_BROKER_URI') as string,
|
||||||
exchange: configService.get<string>('MESSAGE_BROKER_EXCHANGE'),
|
exchange: configService.get<string>(
|
||||||
|
'MESSAGE_BROKER_EXCHANGE',
|
||||||
|
) as string,
|
||||||
},
|
},
|
||||||
redis: {
|
redis: {
|
||||||
host: configService.get<string>('REDIS_HOST'),
|
host: configService.get<string>('REDIS_HOST') as string,
|
||||||
password: configService.get<string>('REDIS_PASSWORD'),
|
password: configService.get<string>('REDIS_PASSWORD'),
|
||||||
port: configService.get<number>('REDIS_PORT'),
|
port: configService.get<number>('REDIS_PORT') as number,
|
||||||
},
|
},
|
||||||
setConfigurationBrokerQueue: 'ad-configuration-create-update',
|
setConfigurationBrokerQueue: 'ad-configuration-create-update',
|
||||||
deleteConfigurationQueue: 'ad-configuration-delete',
|
deleteConfigurationQueue: 'ad-configuration-delete',
|
||||||
|
|
|
@ -22,6 +22,6 @@ async function bootstrap() {
|
||||||
});
|
});
|
||||||
|
|
||||||
await app.startAllMicroservices();
|
await app.startAllMicroservices();
|
||||||
await app.listen(process.env.HEALTH_SERVICE_PORT);
|
await app.listen(process.env.HEALTH_SERVICE_PORT as unknown as number);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
export const AD_MESSAGE_PUBLISHER = Symbol('AD_MESSAGE_PUBLISHER');
|
export const AD_MESSAGE_PUBLISHER = Symbol('AD_MESSAGE_PUBLISHER');
|
||||||
export const PARAMS_PROVIDER = Symbol('PARAMS_PROVIDER');
|
|
||||||
export const TIMEZONE_FINDER = Symbol('TIMEZONE_FINDER');
|
export const TIMEZONE_FINDER = Symbol('TIMEZONE_FINDER');
|
||||||
export const TIME_CONVERTER = Symbol('TIME_CONVERTER');
|
export const TIME_CONVERTER = Symbol('TIME_CONVERTER');
|
||||||
export const INPUT_DATETIME_TRANSFORMER = Symbol('INPUT_DATETIME_TRANSFORMER');
|
export const INPUT_DATETIME_TRANSFORMER = Symbol('INPUT_DATETIME_TRANSFORMER');
|
||||||
|
|
|
@ -37,15 +37,15 @@ export class AdMapper
|
||||||
const record: AdWriteModel = {
|
const record: AdWriteModel = {
|
||||||
uuid: copy.id,
|
uuid: copy.id,
|
||||||
userUuid: copy.userId,
|
userUuid: copy.userId,
|
||||||
driver: copy.driver,
|
driver: copy.driver as boolean,
|
||||||
passenger: copy.passenger,
|
passenger: copy.passenger as boolean,
|
||||||
frequency: copy.frequency,
|
frequency: copy.frequency,
|
||||||
fromDate: new Date(copy.fromDate),
|
fromDate: new Date(copy.fromDate),
|
||||||
toDate: new Date(copy.toDate),
|
toDate: new Date(copy.toDate),
|
||||||
schedule: {
|
schedule: {
|
||||||
create: copy.schedule.map((scheduleItem: ScheduleItemProps) => ({
|
create: copy.schedule.map((scheduleItem: ScheduleItemProps) => ({
|
||||||
uuid: v4(),
|
uuid: v4(),
|
||||||
day: scheduleItem.day,
|
day: scheduleItem.day as number,
|
||||||
time: new Date(
|
time: new Date(
|
||||||
1970,
|
1970,
|
||||||
0,
|
0,
|
||||||
|
@ -53,14 +53,14 @@ export class AdMapper
|
||||||
parseInt(scheduleItem.time.split(':')[0]),
|
parseInt(scheduleItem.time.split(':')[0]),
|
||||||
parseInt(scheduleItem.time.split(':')[1]),
|
parseInt(scheduleItem.time.split(':')[1]),
|
||||||
),
|
),
|
||||||
margin: scheduleItem.margin,
|
margin: scheduleItem.margin as number,
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
seatsProposed: copy.seatsProposed,
|
seatsProposed: copy.seatsProposed as number,
|
||||||
seatsRequested: copy.seatsRequested,
|
seatsRequested: copy.seatsRequested as number,
|
||||||
strict: copy.strict,
|
strict: copy.strict as boolean,
|
||||||
waypoints: {
|
waypoints: {
|
||||||
create: copy.waypoints.map((waypoint: WaypointProps) => ({
|
create: copy.waypoints.map((waypoint: WaypointProps) => ({
|
||||||
uuid: v4(),
|
uuid: v4(),
|
||||||
|
@ -92,7 +92,7 @@ export class AdMapper
|
||||||
userId: record.userUuid,
|
userId: record.userUuid,
|
||||||
driver: record.driver,
|
driver: record.driver,
|
||||||
passenger: record.passenger,
|
passenger: record.passenger,
|
||||||
frequency: Frequency[record.frequency],
|
frequency: record.frequency as Frequency,
|
||||||
fromDate: record.fromDate.toISOString().split('T')[0],
|
fromDate: record.fromDate.toISOString().split('T')[0],
|
||||||
toDate: record.toDate.toISOString().split('T')[0],
|
toDate: record.toDate.toISOString().split('T')[0],
|
||||||
schedule: record.schedule.map((scheduleItem: ScheduleItemModel) => ({
|
schedule: record.schedule.map((scheduleItem: ScheduleItemModel) => ({
|
||||||
|
@ -133,8 +133,8 @@ export class AdMapper
|
||||||
const props = entity.getProps();
|
const props = entity.getProps();
|
||||||
const response = new AdResponseDto(entity);
|
const response = new AdResponseDto(entity);
|
||||||
response.userId = props.userId;
|
response.userId = props.userId;
|
||||||
response.driver = props.driver;
|
response.driver = props.driver as boolean;
|
||||||
response.passenger = props.passenger;
|
response.passenger = props.passenger as boolean;
|
||||||
response.frequency = props.frequency;
|
response.frequency = props.frequency;
|
||||||
response.fromDate = this.outputDatetimeTransformer.fromDate(
|
response.fromDate = this.outputDatetimeTransformer.fromDate(
|
||||||
{
|
{
|
||||||
|
@ -156,7 +156,7 @@ export class AdMapper
|
||||||
response.schedule = props.schedule.map(
|
response.schedule = props.schedule.map(
|
||||||
(scheduleItem: ScheduleItemProps) => ({
|
(scheduleItem: ScheduleItemProps) => ({
|
||||||
day: this.outputDatetimeTransformer.day(
|
day: this.outputDatetimeTransformer.day(
|
||||||
scheduleItem.day,
|
scheduleItem.day as number,
|
||||||
{
|
{
|
||||||
date: props.fromDate,
|
date: props.fromDate,
|
||||||
time: scheduleItem.time,
|
time: scheduleItem.time,
|
||||||
|
@ -172,11 +172,11 @@ export class AdMapper
|
||||||
},
|
},
|
||||||
props.frequency,
|
props.frequency,
|
||||||
),
|
),
|
||||||
margin: scheduleItem.margin,
|
margin: scheduleItem.margin as number,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
response.seatsProposed = props.seatsProposed;
|
response.seatsProposed = props.seatsProposed as number;
|
||||||
response.seatsRequested = props.seatsRequested;
|
response.seatsRequested = props.seatsRequested as number;
|
||||||
response.waypoints = props.waypoints.map((waypoint: WaypointProps) => ({
|
response.waypoints = props.waypoints.map((waypoint: WaypointProps) => ({
|
||||||
position: waypoint.position,
|
position: waypoint.position,
|
||||||
name: waypoint.address.name,
|
name: waypoint.address.name,
|
||||||
|
|
|
@ -6,12 +6,10 @@ import {
|
||||||
AD_REPOSITORY,
|
AD_REPOSITORY,
|
||||||
INPUT_DATETIME_TRANSFORMER,
|
INPUT_DATETIME_TRANSFORMER,
|
||||||
OUTPUT_DATETIME_TRANSFORMER,
|
OUTPUT_DATETIME_TRANSFORMER,
|
||||||
PARAMS_PROVIDER,
|
|
||||||
TIMEZONE_FINDER,
|
TIMEZONE_FINDER,
|
||||||
TIME_CONVERTER,
|
TIME_CONVERTER,
|
||||||
} from './ad.di-tokens';
|
} from './ad.di-tokens';
|
||||||
import { AdRepository } from './infrastructure/ad.repository';
|
import { AdRepository } from './infrastructure/ad.repository';
|
||||||
import { DefaultParamsProvider } from './infrastructure/default-params-provider';
|
|
||||||
import { AdMapper } from './ad.mapper';
|
import { AdMapper } from './ad.mapper';
|
||||||
import { CreateAdService } from './core/application/commands/create-ad/create-ad.service';
|
import { CreateAdService } from './core/application/commands/create-ad/create-ad.service';
|
||||||
import { TimezoneFinder } from './infrastructure/timezone-finder';
|
import { TimezoneFinder } from './infrastructure/timezone-finder';
|
||||||
|
@ -52,10 +50,6 @@ const messagePublishers: Provider[] = [
|
||||||
const orms: Provider[] = [PrismaService];
|
const orms: Provider[] = [PrismaService];
|
||||||
|
|
||||||
const adapters: Provider[] = [
|
const adapters: Provider[] = [
|
||||||
{
|
|
||||||
provide: PARAMS_PROVIDER,
|
|
||||||
useClass: DefaultParamsProvider,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: TIMEZONE_FINDER,
|
provide: TIMEZONE_FINDER,
|
||||||
useClass: TimezoneFinder,
|
useClass: TimezoneFinder,
|
||||||
|
@ -87,12 +81,6 @@ const adapters: Provider[] = [
|
||||||
...orms,
|
...orms,
|
||||||
...adapters,
|
...adapters,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [PrismaService, AdMapper, AD_REPOSITORY, TIMEZONE_FINDER],
|
||||||
PrismaService,
|
|
||||||
AdMapper,
|
|
||||||
AD_REPOSITORY,
|
|
||||||
PARAMS_PROVIDER,
|
|
||||||
TIMEZONE_FINDER,
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export class AdModule {}
|
export class AdModule {}
|
||||||
|
|
|
@ -5,15 +5,15 @@ import { Command, CommandProps } from '@mobicoop/ddd-library';
|
||||||
|
|
||||||
export class CreateAdCommand extends Command {
|
export class CreateAdCommand extends Command {
|
||||||
readonly userId: string;
|
readonly userId: string;
|
||||||
readonly driver?: boolean;
|
readonly driver: boolean;
|
||||||
readonly passenger?: boolean;
|
readonly passenger: boolean;
|
||||||
readonly frequency?: Frequency;
|
readonly frequency: Frequency;
|
||||||
readonly fromDate: string;
|
readonly fromDate: string;
|
||||||
readonly toDate: string;
|
readonly toDate: string;
|
||||||
readonly schedule: ScheduleItem[];
|
readonly schedule: ScheduleItem[];
|
||||||
readonly seatsProposed?: number;
|
readonly seatsProposed?: number;
|
||||||
readonly seatsRequested?: number;
|
readonly seatsRequested?: number;
|
||||||
readonly strict?: boolean;
|
readonly strict: boolean;
|
||||||
readonly waypoints: Waypoint[];
|
readonly waypoints: Waypoint[];
|
||||||
|
|
||||||
constructor(props: CommandProps<CreateAdCommand>) {
|
constructor(props: CommandProps<CreateAdCommand>) {
|
||||||
|
|
|
@ -4,13 +4,10 @@ import { Inject } from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
AD_REPOSITORY,
|
AD_REPOSITORY,
|
||||||
INPUT_DATETIME_TRANSFORMER,
|
INPUT_DATETIME_TRANSFORMER,
|
||||||
PARAMS_PROVIDER,
|
|
||||||
} from '@modules/ad/ad.di-tokens';
|
} from '@modules/ad/ad.di-tokens';
|
||||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||||
import { Waypoint } from '../../types/waypoint';
|
import { Waypoint } from '../../types/waypoint';
|
||||||
import { DefaultParams } from '../../ports/default-params.type';
|
|
||||||
import { AdRepositoryPort } from '../../ports/ad.repository.port';
|
import { AdRepositoryPort } from '../../ports/ad.repository.port';
|
||||||
import { DefaultParamsProviderPort } from '../../ports/default-params-provider.port';
|
|
||||||
import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors';
|
import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors';
|
||||||
import { AggregateID, ConflictException } from '@mobicoop/ddd-library';
|
import { AggregateID, ConflictException } from '@mobicoop/ddd-library';
|
||||||
import { ScheduleItem } from '../../types/schedule-item';
|
import { ScheduleItem } from '../../types/schedule-item';
|
||||||
|
@ -18,30 +15,48 @@ import { DateTimeTransformerPort } from '../../ports/datetime-transformer.port';
|
||||||
|
|
||||||
@CommandHandler(CreateAdCommand)
|
@CommandHandler(CreateAdCommand)
|
||||||
export class CreateAdService implements ICommandHandler {
|
export class CreateAdService implements ICommandHandler {
|
||||||
private readonly _defaultParams: DefaultParams;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(AD_REPOSITORY)
|
@Inject(AD_REPOSITORY)
|
||||||
private readonly repository: AdRepositoryPort,
|
private readonly repository: AdRepositoryPort,
|
||||||
@Inject(PARAMS_PROVIDER)
|
|
||||||
private readonly defaultParamsProvider: DefaultParamsProviderPort,
|
|
||||||
@Inject(INPUT_DATETIME_TRANSFORMER)
|
@Inject(INPUT_DATETIME_TRANSFORMER)
|
||||||
private readonly datetimeTransformer: DateTimeTransformerPort,
|
private readonly datetimeTransformer: DateTimeTransformerPort,
|
||||||
) {
|
) {}
|
||||||
this._defaultParams = defaultParamsProvider.getParams();
|
|
||||||
}
|
|
||||||
|
|
||||||
async execute(command: CreateAdCommand): Promise<AggregateID> {
|
async execute(command: CreateAdCommand): Promise<AggregateID> {
|
||||||
const ad = AdEntity.create(
|
const ad = AdEntity.create({
|
||||||
{
|
userId: command.userId,
|
||||||
userId: command.userId,
|
driver: command.driver,
|
||||||
driver: command.driver,
|
passenger: command.passenger,
|
||||||
passenger: command.passenger,
|
frequency: command.frequency,
|
||||||
frequency: command.frequency,
|
fromDate: this.datetimeTransformer.fromDate(
|
||||||
fromDate: this.datetimeTransformer.fromDate(
|
{
|
||||||
|
date: command.fromDate,
|
||||||
|
time: command.schedule[0].time,
|
||||||
|
coordinates: {
|
||||||
|
lon: command.waypoints[0].lon,
|
||||||
|
lat: command.waypoints[0].lat,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
command.frequency,
|
||||||
|
),
|
||||||
|
toDate: this.datetimeTransformer.toDate(
|
||||||
|
command.toDate,
|
||||||
|
{
|
||||||
|
date: command.fromDate,
|
||||||
|
time: command.schedule[0].time,
|
||||||
|
coordinates: {
|
||||||
|
lon: command.waypoints[0].lon,
|
||||||
|
lat: command.waypoints[0].lat,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
command.frequency,
|
||||||
|
),
|
||||||
|
schedule: command.schedule.map((scheduleItem: ScheduleItem) => ({
|
||||||
|
day: this.datetimeTransformer.day(
|
||||||
|
scheduleItem.day,
|
||||||
{
|
{
|
||||||
date: command.fromDate,
|
date: command.fromDate,
|
||||||
time: command.schedule[0].time,
|
time: scheduleItem.time,
|
||||||
coordinates: {
|
coordinates: {
|
||||||
lon: command.waypoints[0].lon,
|
lon: command.waypoints[0].lon,
|
||||||
lat: command.waypoints[0].lat,
|
lat: command.waypoints[0].lat,
|
||||||
|
@ -49,11 +64,10 @@ export class CreateAdService implements ICommandHandler {
|
||||||
},
|
},
|
||||||
command.frequency,
|
command.frequency,
|
||||||
),
|
),
|
||||||
toDate: this.datetimeTransformer.toDate(
|
time: this.datetimeTransformer.time(
|
||||||
command.toDate,
|
|
||||||
{
|
{
|
||||||
date: command.fromDate,
|
date: command.fromDate,
|
||||||
time: command.schedule[0].time,
|
time: scheduleItem.time,
|
||||||
coordinates: {
|
coordinates: {
|
||||||
lon: command.waypoints[0].lon,
|
lon: command.waypoints[0].lon,
|
||||||
lat: command.waypoints[0].lat,
|
lat: command.waypoints[0].lat,
|
||||||
|
@ -61,60 +75,27 @@ export class CreateAdService implements ICommandHandler {
|
||||||
},
|
},
|
||||||
command.frequency,
|
command.frequency,
|
||||||
),
|
),
|
||||||
schedule: command.schedule.map((scheduleItem: ScheduleItem) => ({
|
margin: scheduleItem.margin,
|
||||||
day: this.datetimeTransformer.day(
|
})),
|
||||||
scheduleItem.day,
|
seatsProposed: command.seatsProposed ?? 0,
|
||||||
{
|
seatsRequested: command.seatsRequested ?? 0,
|
||||||
date: command.fromDate,
|
strict: command.strict,
|
||||||
time: scheduleItem.time,
|
waypoints: command.waypoints.map((waypoint: Waypoint) => ({
|
||||||
coordinates: {
|
position: waypoint.position,
|
||||||
lon: command.waypoints[0].lon,
|
address: {
|
||||||
lat: command.waypoints[0].lat,
|
name: waypoint.name,
|
||||||
},
|
houseNumber: waypoint.houseNumber,
|
||||||
},
|
street: waypoint.street,
|
||||||
command.frequency,
|
postalCode: waypoint.postalCode,
|
||||||
),
|
locality: waypoint.locality,
|
||||||
time: this.datetimeTransformer.time(
|
country: waypoint.country,
|
||||||
{
|
coordinates: {
|
||||||
date: command.fromDate,
|
lon: waypoint.lon,
|
||||||
time: scheduleItem.time,
|
lat: waypoint.lat,
|
||||||
coordinates: {
|
|
||||||
lon: command.waypoints[0].lon,
|
|
||||||
lat: command.waypoints[0].lat,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
command.frequency,
|
|
||||||
),
|
|
||||||
margin: scheduleItem.margin,
|
|
||||||
})),
|
|
||||||
seatsProposed: command.seatsProposed,
|
|
||||||
seatsRequested: command.seatsRequested,
|
|
||||||
strict: command.strict,
|
|
||||||
waypoints: command.waypoints.map((waypoint: Waypoint) => ({
|
|
||||||
position: waypoint.position,
|
|
||||||
address: {
|
|
||||||
name: waypoint.name,
|
|
||||||
houseNumber: waypoint.houseNumber,
|
|
||||||
street: waypoint.street,
|
|
||||||
postalCode: waypoint.postalCode,
|
|
||||||
locality: waypoint.locality,
|
|
||||||
country: waypoint.country,
|
|
||||||
coordinates: {
|
|
||||||
lon: waypoint.lon,
|
|
||||||
lat: waypoint.lat,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})),
|
},
|
||||||
},
|
})),
|
||||||
{
|
});
|
||||||
driver: this._defaultParams.DRIVER,
|
|
||||||
passenger: this._defaultParams.PASSENGER,
|
|
||||||
marginDuration: this._defaultParams.DEPARTURE_TIME_MARGIN,
|
|
||||||
strict: this._defaultParams.STRICT,
|
|
||||||
seatsProposed: this._defaultParams.SEATS_PROPOSED,
|
|
||||||
seatsRequested: this._defaultParams.SEATS_REQUESTED,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.repository.insert(ad);
|
await this.repository.insert(ad);
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
import { DefaultParams } from './default-params.type';
|
|
||||||
|
|
||||||
export interface DefaultParamsProviderPort {
|
|
||||||
getParams(): DefaultParams;
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
export type DefaultParams = {
|
|
||||||
DRIVER: boolean;
|
|
||||||
PASSENGER: boolean;
|
|
||||||
SEATS_PROPOSED: number;
|
|
||||||
SEATS_REQUESTED: number;
|
|
||||||
DEPARTURE_TIME_MARGIN: number;
|
|
||||||
STRICT: boolean;
|
|
||||||
TIMEZONE: string;
|
|
||||||
};
|
|
|
@ -1,3 +1,3 @@
|
||||||
export interface TimezoneFinderPort {
|
export interface TimezoneFinderPort {
|
||||||
timezones(lon: number, lat: number, defaultTimezone?: string): string[];
|
timezones(lon: number, lat: number): string[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export type ScheduleItem = {
|
export type ScheduleItem = {
|
||||||
day?: number;
|
day: number;
|
||||||
time: string;
|
time: string;
|
||||||
margin?: number;
|
margin: number;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Address } from './address';
|
import { Address } from './address';
|
||||||
|
|
||||||
export type Waypoint = {
|
export type Waypoint = {
|
||||||
position?: number;
|
position: number;
|
||||||
} & Address;
|
} & Address;
|
||||||
|
|
|
@ -1,29 +1,17 @@
|
||||||
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
|
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
import { AdCreatedDomainEvent } from './events/ad-created.domain-events';
|
import { AdCreatedDomainEvent } from './events/ad-created.domain-events';
|
||||||
import { AdProps, CreateAdProps, DefaultAdProps } from './ad.types';
|
import { AdProps, CreateAdProps } from './ad.types';
|
||||||
import { ScheduleItemProps } from './value-objects/schedule-item.value-object';
|
import { ScheduleItemProps } from './value-objects/schedule-item.value-object';
|
||||||
import { WaypointProps } from './value-objects/waypoint.value-object';
|
import { WaypointProps } from './value-objects/waypoint.value-object';
|
||||||
|
|
||||||
export class AdEntity extends AggregateRoot<AdProps> {
|
export class AdEntity extends AggregateRoot<AdProps> {
|
||||||
protected readonly _id: AggregateID;
|
protected readonly _id: AggregateID;
|
||||||
|
|
||||||
static create = (
|
static create = (create: CreateAdProps): AdEntity => {
|
||||||
create: CreateAdProps,
|
|
||||||
defaultAdProps: DefaultAdProps,
|
|
||||||
): AdEntity => {
|
|
||||||
const id = v4();
|
const id = v4();
|
||||||
const props: AdProps = { ...create };
|
const props: AdProps = { ...create };
|
||||||
const ad = new AdEntity({ id, props })
|
const ad = new AdEntity({ id, props });
|
||||||
.setMissingMarginDurations(defaultAdProps.marginDuration)
|
|
||||||
.setMissingStrict(defaultAdProps.strict)
|
|
||||||
.setDefaultDriverAndPassengerParameters({
|
|
||||||
driver: defaultAdProps.driver,
|
|
||||||
passenger: defaultAdProps.passenger,
|
|
||||||
seatsProposed: defaultAdProps.seatsProposed,
|
|
||||||
seatsRequested: defaultAdProps.seatsRequested,
|
|
||||||
})
|
|
||||||
.setMissingWaypointsPosition();
|
|
||||||
ad.addEvent(
|
ad.addEvent(
|
||||||
new AdCreatedDomainEvent({
|
new AdCreatedDomainEvent({
|
||||||
aggregateId: id,
|
aggregateId: id,
|
||||||
|
@ -34,9 +22,9 @@ export class AdEntity extends AggregateRoot<AdProps> {
|
||||||
fromDate: props.fromDate,
|
fromDate: props.fromDate,
|
||||||
toDate: props.toDate,
|
toDate: props.toDate,
|
||||||
schedule: props.schedule.map((day: ScheduleItemProps) => ({
|
schedule: props.schedule.map((day: ScheduleItemProps) => ({
|
||||||
day: day.day,
|
day: day.day as number,
|
||||||
time: day.time,
|
time: day.time,
|
||||||
margin: day.margin,
|
margin: day.margin as number,
|
||||||
})),
|
})),
|
||||||
seatsProposed: props.seatsProposed,
|
seatsProposed: props.seatsProposed,
|
||||||
seatsRequested: props.seatsRequested,
|
seatsRequested: props.seatsRequested,
|
||||||
|
@ -48,7 +36,7 @@ export class AdEntity extends AggregateRoot<AdProps> {
|
||||||
street: waypoint.address.street,
|
street: waypoint.address.street,
|
||||||
postalCode: waypoint.address.postalCode,
|
postalCode: waypoint.address.postalCode,
|
||||||
locality: waypoint.address.locality,
|
locality: waypoint.address.locality,
|
||||||
country: waypoint.address.postalCode,
|
country: waypoint.address.country,
|
||||||
lon: waypoint.address.coordinates.lon,
|
lon: waypoint.address.coordinates.lon,
|
||||||
lat: waypoint.address.coordinates.lat,
|
lat: waypoint.address.coordinates.lat,
|
||||||
})),
|
})),
|
||||||
|
@ -57,60 +45,7 @@ export class AdEntity extends AggregateRoot<AdProps> {
|
||||||
return ad;
|
return ad;
|
||||||
};
|
};
|
||||||
|
|
||||||
private setMissingMarginDurations = (
|
|
||||||
defaultMarginDuration: number,
|
|
||||||
): AdEntity => {
|
|
||||||
this.props.schedule.forEach((day: ScheduleItemProps) => {
|
|
||||||
if (day.margin === undefined) day.margin = defaultMarginDuration;
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
private setMissingStrict = (strict: boolean): AdEntity => {
|
|
||||||
if (this.props.strict === undefined) this.props.strict = strict;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
private setDefaultDriverAndPassengerParameters = (
|
|
||||||
defaultDriverAndPassengerParameters: DefaultDriverAndPassengerParameters,
|
|
||||||
): AdEntity => {
|
|
||||||
this.props.driver = !!this.props.driver;
|
|
||||||
this.props.passenger = !!this.props.passenger;
|
|
||||||
if (!this.props.driver && !this.props.passenger) {
|
|
||||||
this.props.driver = defaultDriverAndPassengerParameters.driver;
|
|
||||||
this.props.seatsProposed =
|
|
||||||
defaultDriverAndPassengerParameters.seatsProposed;
|
|
||||||
this.props.passenger = defaultDriverAndPassengerParameters.passenger;
|
|
||||||
this.props.seatsRequested =
|
|
||||||
defaultDriverAndPassengerParameters.seatsRequested;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
if (!this.props.seatsProposed || this.props.seatsProposed <= 0)
|
|
||||||
this.props.seatsProposed =
|
|
||||||
defaultDriverAndPassengerParameters.seatsProposed;
|
|
||||||
if (!this.props.seatsRequested || this.props.seatsRequested <= 0)
|
|
||||||
this.props.seatsRequested =
|
|
||||||
defaultDriverAndPassengerParameters.seatsRequested;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
private setMissingWaypointsPosition = (): AdEntity => {
|
|
||||||
if (this.props.waypoints[0].position === undefined) {
|
|
||||||
for (let i = 0; i < this.props.waypoints.length; i++) {
|
|
||||||
this.props.waypoints[i].position = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
validate(): void {
|
validate(): void {
|
||||||
// entity business rules validation to protect it's invariant before saving entity to a database
|
// entity business rules validation to protect it's invariant before saving entity to a database
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DefaultDriverAndPassengerParameters {
|
|
||||||
driver: boolean;
|
|
||||||
passenger: boolean;
|
|
||||||
seatsProposed: number;
|
|
||||||
seatsRequested: number;
|
|
||||||
}
|
|
||||||
|
|
|
@ -31,15 +31,6 @@ export interface CreateAdProps {
|
||||||
waypoints: WaypointProps[];
|
waypoints: WaypointProps[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DefaultAdProps {
|
|
||||||
driver: boolean;
|
|
||||||
passenger: boolean;
|
|
||||||
marginDuration: number;
|
|
||||||
strict: boolean;
|
|
||||||
seatsProposed: number;
|
|
||||||
seatsRequested: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum Frequency {
|
export enum Frequency {
|
||||||
PUNCTUAL = 'PUNCTUAL',
|
PUNCTUAL = 'PUNCTUAL',
|
||||||
RECURRENT = 'RECURRENT',
|
RECURRENT = 'RECURRENT',
|
||||||
|
|
|
@ -17,23 +17,23 @@ export interface AddressProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Address extends ValueObject<AddressProps> {
|
export class Address extends ValueObject<AddressProps> {
|
||||||
get name(): string {
|
get name(): string | undefined {
|
||||||
return this.props.name;
|
return this.props.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
get houseNumber(): string {
|
get houseNumber(): string | undefined {
|
||||||
return this.props.houseNumber;
|
return this.props.houseNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
get street(): string {
|
get street(): string | undefined {
|
||||||
return this.props.street;
|
return this.props.street;
|
||||||
}
|
}
|
||||||
|
|
||||||
get locality(): string {
|
get locality(): string | undefined {
|
||||||
return this.props.locality;
|
return this.props.locality;
|
||||||
}
|
}
|
||||||
|
|
||||||
get postalCode(): string {
|
get postalCode(): string | undefined {
|
||||||
return this.props.postalCode;
|
return this.props.postalCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
|
||||||
import { DefaultParamsProviderPort } from '../core/application/ports/default-params-provider.port';
|
|
||||||
import { DefaultParams } from '../core/application/ports/default-params.type';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class DefaultParamsProvider implements DefaultParamsProviderPort {
|
|
||||||
constructor(private readonly _configService: ConfigService) {}
|
|
||||||
getParams = (): DefaultParams => ({
|
|
||||||
DRIVER: this._configService.get('ROLE') == 'driver',
|
|
||||||
SEATS_PROPOSED: parseInt(this._configService.get('SEATS_PROPOSED')),
|
|
||||||
PASSENGER: this._configService.get('ROLE') == 'passenger',
|
|
||||||
SEATS_REQUESTED: parseInt(this._configService.get('SEATS_REQUESTED')),
|
|
||||||
DEPARTURE_TIME_MARGIN: parseInt(
|
|
||||||
this._configService.get('DEPARTURE_TIME_MARGIN'),
|
|
||||||
),
|
|
||||||
STRICT: this._configService.get('STRICT_FREQUENCY') == 'true',
|
|
||||||
TIMEZONE: this._configService.get('DEFAULT_TIMEZONE'),
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -5,26 +5,16 @@ import {
|
||||||
GeoDateTime,
|
GeoDateTime,
|
||||||
} from '../core/application/ports/datetime-transformer.port';
|
} from '../core/application/ports/datetime-transformer.port';
|
||||||
import { TimeConverterPort } from '../core/application/ports/time-converter.port';
|
import { TimeConverterPort } from '../core/application/ports/time-converter.port';
|
||||||
import {
|
import { TIMEZONE_FINDER, TIME_CONVERTER } from '../ad.di-tokens';
|
||||||
PARAMS_PROVIDER,
|
|
||||||
TIMEZONE_FINDER,
|
|
||||||
TIME_CONVERTER,
|
|
||||||
} from '../ad.di-tokens';
|
|
||||||
import { TimezoneFinderPort } from '../core/application/ports/timezone-finder.port';
|
import { TimezoneFinderPort } from '../core/application/ports/timezone-finder.port';
|
||||||
import { DefaultParamsProviderPort } from '../core/application/ports/default-params-provider.port';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class InputDateTimeTransformer implements DateTimeTransformerPort {
|
export class InputDateTimeTransformer implements DateTimeTransformerPort {
|
||||||
private readonly _defaultTimezone: string;
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(PARAMS_PROVIDER)
|
|
||||||
private readonly defaultParamsProvider: DefaultParamsProviderPort,
|
|
||||||
@Inject(TIMEZONE_FINDER)
|
@Inject(TIMEZONE_FINDER)
|
||||||
private readonly timezoneFinder: TimezoneFinderPort,
|
private readonly timezoneFinder: TimezoneFinderPort,
|
||||||
@Inject(TIME_CONVERTER) private readonly timeConverter: TimeConverterPort,
|
@Inject(TIME_CONVERTER) private readonly timeConverter: TimeConverterPort,
|
||||||
) {
|
) {}
|
||||||
this._defaultTimezone = defaultParamsProvider.getParams().TIMEZONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute the fromDate : if an ad is punctual, the departure date
|
* Compute the fromDate : if an ad is punctual, the departure date
|
||||||
|
@ -39,7 +29,6 @@ export class InputDateTimeTransformer implements DateTimeTransformerPort {
|
||||||
this.timezoneFinder.timezones(
|
this.timezoneFinder.timezones(
|
||||||
geoFromDate.coordinates.lon,
|
geoFromDate.coordinates.lon,
|
||||||
geoFromDate.coordinates.lat,
|
geoFromDate.coordinates.lat,
|
||||||
this._defaultTimezone,
|
|
||||||
)[0],
|
)[0],
|
||||||
)
|
)
|
||||||
.toISOString()
|
.toISOString()
|
||||||
|
@ -76,7 +65,6 @@ export class InputDateTimeTransformer implements DateTimeTransformerPort {
|
||||||
this.timezoneFinder.timezones(
|
this.timezoneFinder.timezones(
|
||||||
geoFromDate.coordinates.lon,
|
geoFromDate.coordinates.lon,
|
||||||
geoFromDate.coordinates.lat,
|
geoFromDate.coordinates.lat,
|
||||||
this._defaultTimezone,
|
|
||||||
)[0],
|
)[0],
|
||||||
);
|
);
|
||||||
return new Date(this.fromDate(geoFromDate, frequency)).getUTCDay();
|
return new Date(this.fromDate(geoFromDate, frequency)).getUTCDay();
|
||||||
|
@ -92,7 +80,6 @@ export class InputDateTimeTransformer implements DateTimeTransformerPort {
|
||||||
this.timezoneFinder.timezones(
|
this.timezoneFinder.timezones(
|
||||||
geoFromDate.coordinates.lon,
|
geoFromDate.coordinates.lon,
|
||||||
geoFromDate.coordinates.lat,
|
geoFromDate.coordinates.lat,
|
||||||
this._defaultTimezone,
|
|
||||||
)[0],
|
)[0],
|
||||||
);
|
);
|
||||||
return this.timeConverter
|
return this.timeConverter
|
||||||
|
@ -102,7 +89,6 @@ export class InputDateTimeTransformer implements DateTimeTransformerPort {
|
||||||
this.timezoneFinder.timezones(
|
this.timezoneFinder.timezones(
|
||||||
geoFromDate.coordinates.lon,
|
geoFromDate.coordinates.lon,
|
||||||
geoFromDate.coordinates.lat,
|
geoFromDate.coordinates.lat,
|
||||||
this._defaultTimezone,
|
|
||||||
)[0],
|
)[0],
|
||||||
)
|
)
|
||||||
.toISOString()
|
.toISOString()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { INestApplication, Injectable, OnModuleInit } from '@nestjs/common';
|
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -6,10 +6,4 @@ export class PrismaService extends PrismaClient implements OnModuleInit {
|
||||||
async onModuleInit() {
|
async onModuleInit() {
|
||||||
await this.$connect();
|
await this.$connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
async enableShutdownHooks(app: INestApplication) {
|
|
||||||
this.$on('beforeExit', async () => {
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,5 @@ import { TimezoneFinderPort } from '../core/application/ports/timezone-finder.po
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TimezoneFinder implements TimezoneFinderPort {
|
export class TimezoneFinder implements TimezoneFinderPort {
|
||||||
timezones = (
|
timezones = (lon: number, lat: number): string[] => find(lat, lon);
|
||||||
lon: number,
|
|
||||||
lat: number,
|
|
||||||
defaultTimezone?: string,
|
|
||||||
): string[] => {
|
|
||||||
const foundTimezones = find(lat, lon);
|
|
||||||
if (defaultTimezone && foundTimezones.length == 0) return [defaultTimezone];
|
|
||||||
return foundTimezones;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,24 +15,29 @@ import { WaypointDto } from './waypoint.dto';
|
||||||
import { HasValidPositionIndexes } from './validators/decorators/has-valid-position-indexes.decorator';
|
import { HasValidPositionIndexes } from './validators/decorators/has-valid-position-indexes.decorator';
|
||||||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||||
import { IsAfterOrEqual } from './validators/decorators/is-after-or-equal.decorator';
|
import { IsAfterOrEqual } from './validators/decorators/is-after-or-equal.decorator';
|
||||||
import { HasDay } from './validators/decorators/has-day.decorator';
|
import { HasRole } from './validators/decorators/has-role.decorator';
|
||||||
|
import { HasSeats } from './validators/decorators/has-seats.decorator';
|
||||||
|
|
||||||
export class CreateAdRequestDto {
|
export class CreateAdRequestDto {
|
||||||
@IsUUID(4)
|
@IsUUID(4)
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
driver?: boolean;
|
@HasRole('passenger', {
|
||||||
|
message: 'At least one of driver or passenger property needs to be truthy',
|
||||||
|
})
|
||||||
|
@HasSeats('seatsProposed', {
|
||||||
|
message: 'Number of seats proposed as a driver is required',
|
||||||
|
})
|
||||||
|
driver: boolean;
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
passenger?: boolean;
|
@HasSeats('seatsRequested', {
|
||||||
|
message: 'Number of seats requested as a passenger is required',
|
||||||
|
})
|
||||||
|
passenger: boolean;
|
||||||
|
|
||||||
@IsEnum(Frequency)
|
@IsEnum(Frequency)
|
||||||
@HasDay('schedule', {
|
|
||||||
message: 'At least a day is required for a recurrent ad',
|
|
||||||
})
|
|
||||||
frequency: Frequency;
|
frequency: Frequency;
|
||||||
|
|
||||||
@IsISO8601({
|
@IsISO8601({
|
||||||
|
@ -56,17 +61,16 @@ export class CreateAdRequestDto {
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
schedule: ScheduleItemDto[];
|
schedule: ScheduleItemDto[];
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsInt()
|
@IsInt()
|
||||||
|
@IsOptional()
|
||||||
seatsProposed?: number;
|
seatsProposed?: number;
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsInt()
|
@IsInt()
|
||||||
|
@IsOptional()
|
||||||
seatsRequested?: number;
|
seatsRequested?: number;
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
strict?: boolean;
|
strict: boolean;
|
||||||
|
|
||||||
@Type(() => WaypointDto)
|
@Type(() => WaypointDto)
|
||||||
@IsArray()
|
@IsArray()
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
import { IsOptional, IsMilitaryTime, IsInt, Min, Max } from 'class-validator';
|
import { IsMilitaryTime, IsInt, Min, Max } from 'class-validator';
|
||||||
|
|
||||||
export class ScheduleItemDto {
|
export class ScheduleItemDto {
|
||||||
@IsOptional()
|
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@Min(0)
|
@Min(0)
|
||||||
@Max(6)
|
@Max(6)
|
||||||
day?: number;
|
day: number;
|
||||||
|
|
||||||
@IsMilitaryTime()
|
@IsMilitaryTime()
|
||||||
time: string;
|
time: string;
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsInt()
|
@IsInt()
|
||||||
margin?: number;
|
margin: number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
|
||||||
import {
|
import {
|
||||||
registerDecorator,
|
registerDecorator,
|
||||||
ValidationOptions,
|
ValidationOptions,
|
||||||
ValidationArguments,
|
ValidationArguments,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
|
|
||||||
export function HasDay(
|
export function HasRole(
|
||||||
property: string,
|
property: string,
|
||||||
validationOptions?: ValidationOptions,
|
validationOptions?: ValidationOptions,
|
||||||
) {
|
) {
|
||||||
return function (object: object, propertyName: string) {
|
return function (object: object, propertyName: string) {
|
||||||
registerDecorator({
|
registerDecorator({
|
||||||
name: 'hasDay',
|
name: 'hasRole',
|
||||||
target: object.constructor,
|
target: object.constructor,
|
||||||
propertyName: propertyName,
|
propertyName: propertyName,
|
||||||
constraints: [property],
|
constraints: [property],
|
||||||
|
@ -20,13 +19,7 @@ export function HasDay(
|
||||||
validate(value: any, args: ValidationArguments) {
|
validate(value: any, args: ValidationArguments) {
|
||||||
const [relatedPropertyName] = args.constraints;
|
const [relatedPropertyName] = args.constraints;
|
||||||
const relatedValue = (args.object as any)[relatedPropertyName];
|
const relatedValue = (args.object as any)[relatedPropertyName];
|
||||||
return (
|
return value || relatedValue;
|
||||||
value == Frequency.PUNCTUAL ||
|
|
||||||
(Array.isArray(relatedValue) &&
|
|
||||||
relatedValue.some((scheduleItem) =>
|
|
||||||
scheduleItem.hasOwnProperty('day'),
|
|
||||||
))
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
|
@ -0,0 +1,27 @@
|
||||||
|
import {
|
||||||
|
registerDecorator,
|
||||||
|
ValidationOptions,
|
||||||
|
ValidationArguments,
|
||||||
|
} from 'class-validator';
|
||||||
|
|
||||||
|
export function HasSeats(
|
||||||
|
property: string,
|
||||||
|
validationOptions?: ValidationOptions,
|
||||||
|
) {
|
||||||
|
return function (object: object, propertyName: string) {
|
||||||
|
registerDecorator({
|
||||||
|
name: 'hasSeats',
|
||||||
|
target: object.constructor,
|
||||||
|
propertyName: propertyName,
|
||||||
|
constraints: [property],
|
||||||
|
options: validationOptions,
|
||||||
|
validator: {
|
||||||
|
validate(value: any, args: ValidationArguments) {
|
||||||
|
const [relatedPropertyName] = args.constraints;
|
||||||
|
const relatedValue = (args.object as any)[relatedPropertyName];
|
||||||
|
return (value && relatedValue > 0) || !value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,17 +1,10 @@
|
||||||
import { WaypointDto } from '../waypoint.dto';
|
import { WaypointDto } from '../waypoint.dto';
|
||||||
|
|
||||||
export const hasValidPositionIndexes = (waypoints: WaypointDto[]): boolean => {
|
export const hasValidPositionIndexes = (waypoints: WaypointDto[]): boolean => {
|
||||||
if (!waypoints) return false;
|
|
||||||
if (waypoints.length == 0) return false;
|
if (waypoints.length == 0) return false;
|
||||||
if (waypoints.every((waypoint) => waypoint.position === undefined))
|
const positions = Array.from(waypoints, (waypoint) => waypoint.position);
|
||||||
return false;
|
positions.sort();
|
||||||
if (waypoints.every((waypoint) => typeof waypoint.position === 'number')) {
|
for (let i = 1; i < positions.length; i++)
|
||||||
const positions = Array.from(waypoints, (waypoint) => waypoint.position);
|
if (positions[i] != positions[i - 1] + 1) return false;
|
||||||
positions.sort();
|
return true;
|
||||||
for (let i = 1; i < positions.length; i++)
|
|
||||||
if (positions[i] != positions[i - 1] + 1) return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { IsInt, IsOptional } from 'class-validator';
|
import { IsInt } from 'class-validator';
|
||||||
import { AddressDto } from './address.dto';
|
import { AddressDto } from './address.dto';
|
||||||
|
|
||||||
export class WaypointDto extends AddressDto {
|
export class WaypointDto extends AddressDto {
|
||||||
@IsOptional()
|
|
||||||
@IsInt()
|
@IsInt()
|
||||||
position?: number;
|
position: number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,7 @@ import {
|
||||||
} from '@modules/ad/ad.di-tokens';
|
} from '@modules/ad/ad.di-tokens';
|
||||||
import { AdMapper } from '@modules/ad/ad.mapper';
|
import { AdMapper } from '@modules/ad/ad.mapper';
|
||||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||||
import {
|
import { CreateAdProps, Frequency } from '@modules/ad/core/domain/ad.types';
|
||||||
CreateAdProps,
|
|
||||||
DefaultAdProps,
|
|
||||||
Frequency,
|
|
||||||
} from '@modules/ad/core/domain/ad.types';
|
|
||||||
import { AdRepository } from '@modules/ad/infrastructure/ad.repository';
|
import { AdRepository } from '@modules/ad/infrastructure/ad.repository';
|
||||||
import { OutputDateTimeTransformer } from '@modules/ad/infrastructure/output-datetime-transformer';
|
import { OutputDateTimeTransformer } from '@modules/ad/infrastructure/output-datetime-transformer';
|
||||||
import { PrismaService } from '@modules/ad/infrastructure/prisma.service';
|
import { PrismaService } from '@modules/ad/infrastructure/prisma.service';
|
||||||
|
@ -55,7 +51,7 @@ describe('Ad Repository', () => {
|
||||||
driver: 'true',
|
driver: 'true',
|
||||||
passenger: 'false',
|
passenger: 'false',
|
||||||
seatsProposed: 3,
|
seatsProposed: 3,
|
||||||
seatsRequested: 0,
|
seatsRequested: 1,
|
||||||
strict: 'false',
|
strict: 'false',
|
||||||
};
|
};
|
||||||
const punctualAd = {
|
const punctualAd = {
|
||||||
|
@ -231,19 +227,7 @@ describe('Ad Repository', () => {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultAdProps: DefaultAdProps = {
|
const adToCreate: AdEntity = AdEntity.create(createAdProps);
|
||||||
driver: false,
|
|
||||||
passenger: true,
|
|
||||||
marginDuration: 900,
|
|
||||||
seatsProposed: 3,
|
|
||||||
seatsRequested: 1,
|
|
||||||
strict: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
const adToCreate: AdEntity = AdEntity.create(
|
|
||||||
createAdProps,
|
|
||||||
defaultAdProps,
|
|
||||||
);
|
|
||||||
await adRepository.insert(adToCreate);
|
await adRepository.insert(adToCreate);
|
||||||
|
|
||||||
const afterCount = await prismaService.ad.count();
|
const afterCount = await prismaService.ad.count();
|
||||||
|
@ -319,19 +303,7 @@ describe('Ad Repository', () => {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultAdProps: DefaultAdProps = {
|
const adToCreate: AdEntity = AdEntity.create(createAdProps);
|
||||||
driver: false,
|
|
||||||
passenger: true,
|
|
||||||
marginDuration: 900,
|
|
||||||
seatsProposed: 3,
|
|
||||||
seatsRequested: 1,
|
|
||||||
strict: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
const adToCreate: AdEntity = AdEntity.create(
|
|
||||||
createAdProps,
|
|
||||||
defaultAdProps,
|
|
||||||
);
|
|
||||||
await adRepository.insert(adToCreate);
|
await adRepository.insert(adToCreate);
|
||||||
|
|
||||||
const afterCount = await prismaService.ad.count();
|
const afterCount = await prismaService.ad.count();
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||||
import {
|
import { CreateAdProps, Frequency } from '@modules/ad/core/domain/ad.types';
|
||||||
CreateAdProps,
|
|
||||||
DefaultAdProps,
|
|
||||||
Frequency,
|
|
||||||
} from '@modules/ad/core/domain/ad.types';
|
|
||||||
import { WaypointProps } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
|
import { WaypointProps } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
|
||||||
|
|
||||||
const originWaypointProps: WaypointProps = {
|
const originWaypointProps: WaypointProps = {
|
||||||
|
@ -47,6 +43,7 @@ const punctualCreateAdProps = {
|
||||||
{
|
{
|
||||||
day: 3,
|
day: 3,
|
||||||
time: '08:30',
|
time: '08:30',
|
||||||
|
margin: 600,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
frequency: Frequency.PUNCTUAL,
|
frequency: Frequency.PUNCTUAL,
|
||||||
|
@ -119,21 +116,12 @@ const recurrentDriverPassengerCreateAdProps: CreateAdProps = {
|
||||||
driver: true,
|
driver: true,
|
||||||
passenger: true,
|
passenger: true,
|
||||||
};
|
};
|
||||||
const defaultAdProps: DefaultAdProps = {
|
|
||||||
driver: false,
|
|
||||||
passenger: true,
|
|
||||||
marginDuration: 900,
|
|
||||||
seatsProposed: 3,
|
|
||||||
seatsRequested: 1,
|
|
||||||
strict: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('Ad entity create', () => {
|
describe('Ad entity create', () => {
|
||||||
describe('With complete props', () => {
|
describe('With complete props', () => {
|
||||||
it('should create a new punctual passenger ad entity', async () => {
|
it('should create a new punctual passenger ad entity', async () => {
|
||||||
const punctualPassengerAd: AdEntity = AdEntity.create(
|
const punctualPassengerAd: AdEntity = AdEntity.create(
|
||||||
punctualPassengerCreateAdProps,
|
punctualPassengerCreateAdProps,
|
||||||
defaultAdProps,
|
|
||||||
);
|
);
|
||||||
expect(punctualPassengerAd.id.length).toBe(36);
|
expect(punctualPassengerAd.id.length).toBe(36);
|
||||||
expect(punctualPassengerAd.getProps().schedule.length).toBe(1);
|
expect(punctualPassengerAd.getProps().schedule.length).toBe(1);
|
||||||
|
@ -145,7 +133,6 @@ describe('Ad entity create', () => {
|
||||||
it('should create a new punctual driver ad entity', async () => {
|
it('should create a new punctual driver ad entity', async () => {
|
||||||
const punctualDriverAd: AdEntity = AdEntity.create(
|
const punctualDriverAd: AdEntity = AdEntity.create(
|
||||||
punctualDriverCreateAdProps,
|
punctualDriverCreateAdProps,
|
||||||
defaultAdProps,
|
|
||||||
);
|
);
|
||||||
expect(punctualDriverAd.id.length).toBe(36);
|
expect(punctualDriverAd.id.length).toBe(36);
|
||||||
expect(punctualDriverAd.getProps().schedule.length).toBe(1);
|
expect(punctualDriverAd.getProps().schedule.length).toBe(1);
|
||||||
|
@ -157,7 +144,6 @@ describe('Ad entity create', () => {
|
||||||
it('should create a new punctual driver and passenger ad entity', async () => {
|
it('should create a new punctual driver and passenger ad entity', async () => {
|
||||||
const punctualDriverPassengerAd: AdEntity = AdEntity.create(
|
const punctualDriverPassengerAd: AdEntity = AdEntity.create(
|
||||||
punctualDriverPassengerCreateAdProps,
|
punctualDriverPassengerCreateAdProps,
|
||||||
defaultAdProps,
|
|
||||||
);
|
);
|
||||||
expect(punctualDriverPassengerAd.id.length).toBe(36);
|
expect(punctualDriverPassengerAd.id.length).toBe(36);
|
||||||
expect(punctualDriverPassengerAd.getProps().schedule.length).toBe(1);
|
expect(punctualDriverPassengerAd.getProps().schedule.length).toBe(1);
|
||||||
|
@ -171,7 +157,6 @@ describe('Ad entity create', () => {
|
||||||
it('should create a new recurrent passenger ad entity', async () => {
|
it('should create a new recurrent passenger ad entity', async () => {
|
||||||
const recurrentPassengerAd: AdEntity = AdEntity.create(
|
const recurrentPassengerAd: AdEntity = AdEntity.create(
|
||||||
recurrentPassengerCreateAdProps,
|
recurrentPassengerCreateAdProps,
|
||||||
defaultAdProps,
|
|
||||||
);
|
);
|
||||||
expect(recurrentPassengerAd.id.length).toBe(36);
|
expect(recurrentPassengerAd.id.length).toBe(36);
|
||||||
expect(recurrentPassengerAd.getProps().schedule.length).toBe(5);
|
expect(recurrentPassengerAd.getProps().schedule.length).toBe(5);
|
||||||
|
@ -183,7 +168,6 @@ describe('Ad entity create', () => {
|
||||||
it('should create a new recurrent driver ad entity', async () => {
|
it('should create a new recurrent driver ad entity', async () => {
|
||||||
const recurrentDriverAd: AdEntity = AdEntity.create(
|
const recurrentDriverAd: AdEntity = AdEntity.create(
|
||||||
recurrentDriverCreateAdProps,
|
recurrentDriverCreateAdProps,
|
||||||
defaultAdProps,
|
|
||||||
);
|
);
|
||||||
expect(recurrentDriverAd.id.length).toBe(36);
|
expect(recurrentDriverAd.id.length).toBe(36);
|
||||||
expect(recurrentDriverAd.getProps().schedule.length).toBe(5);
|
expect(recurrentDriverAd.getProps().schedule.length).toBe(5);
|
||||||
|
@ -195,7 +179,6 @@ describe('Ad entity create', () => {
|
||||||
it('should create a new recurrent driver and passenger ad entity', async () => {
|
it('should create a new recurrent driver and passenger ad entity', async () => {
|
||||||
const recurrentDriverPassengerAd: AdEntity = AdEntity.create(
|
const recurrentDriverPassengerAd: AdEntity = AdEntity.create(
|
||||||
recurrentDriverPassengerCreateAdProps,
|
recurrentDriverPassengerCreateAdProps,
|
||||||
defaultAdProps,
|
|
||||||
);
|
);
|
||||||
expect(recurrentDriverPassengerAd.id.length).toBe(36);
|
expect(recurrentDriverPassengerAd.id.length).toBe(36);
|
||||||
expect(recurrentDriverPassengerAd.getProps().schedule.length).toBe(5);
|
expect(recurrentDriverPassengerAd.getProps().schedule.length).toBe(5);
|
||||||
|
@ -207,177 +190,4 @@ describe('Ad entity create', () => {
|
||||||
expect(recurrentDriverPassengerAd.getProps().passenger).toBeTruthy();
|
expect(recurrentDriverPassengerAd.getProps().passenger).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('With incomplete props', () => {
|
|
||||||
it('should create a new punctual passenger ad entity if no role is given', async () => {
|
|
||||||
const punctualWithoutRoleCreateAdProps: CreateAdProps = {
|
|
||||||
...baseCreateAdProps,
|
|
||||||
...punctualCreateAdProps,
|
|
||||||
driver: false,
|
|
||||||
passenger: false,
|
|
||||||
};
|
|
||||||
const punctualWithoutRoleAd: AdEntity = AdEntity.create(
|
|
||||||
punctualWithoutRoleCreateAdProps,
|
|
||||||
defaultAdProps,
|
|
||||||
);
|
|
||||||
expect(punctualWithoutRoleAd.id.length).toBe(36);
|
|
||||||
expect(punctualWithoutRoleAd.getProps().schedule.length).toBe(1);
|
|
||||||
expect(punctualWithoutRoleAd.getProps().schedule[0].time).toBe('08:30');
|
|
||||||
expect(punctualWithoutRoleAd.getProps().driver).toBeFalsy();
|
|
||||||
expect(punctualWithoutRoleAd.getProps().passenger).toBeTruthy();
|
|
||||||
});
|
|
||||||
it('should create a new strict punctual passenger ad entity if no strict param is given', async () => {
|
|
||||||
const punctualWithoutStrictCreateAdProps: CreateAdProps = {
|
|
||||||
userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36',
|
|
||||||
seatsProposed: 3,
|
|
||||||
seatsRequested: 1,
|
|
||||||
strict: undefined,
|
|
||||||
waypoints: [originWaypointProps, destinationWaypointProps],
|
|
||||||
...punctualCreateAdProps,
|
|
||||||
driver: false,
|
|
||||||
passenger: true,
|
|
||||||
};
|
|
||||||
const punctualWithoutStrictAd: AdEntity = AdEntity.create(
|
|
||||||
punctualWithoutStrictCreateAdProps,
|
|
||||||
defaultAdProps,
|
|
||||||
);
|
|
||||||
expect(punctualWithoutStrictAd.id.length).toBe(36);
|
|
||||||
expect(punctualWithoutStrictAd.getProps().schedule.length).toBe(1);
|
|
||||||
expect(punctualWithoutStrictAd.getProps().schedule[0].time).toBe('08:30');
|
|
||||||
expect(punctualWithoutStrictAd.getProps().driver).toBeFalsy();
|
|
||||||
expect(punctualWithoutStrictAd.getProps().passenger).toBeTruthy();
|
|
||||||
expect(punctualWithoutStrictAd.getProps().strict).toBeFalsy();
|
|
||||||
});
|
|
||||||
it('should create a new punctual passenger ad entity with seats requested if no seats requested param is given', async () => {
|
|
||||||
const punctualWithoutSeatsRequestedCreateAdProps: CreateAdProps = {
|
|
||||||
userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36',
|
|
||||||
seatsProposed: 3,
|
|
||||||
seatsRequested: undefined,
|
|
||||||
strict: false,
|
|
||||||
waypoints: [originWaypointProps, destinationWaypointProps],
|
|
||||||
...punctualCreateAdProps,
|
|
||||||
driver: false,
|
|
||||||
passenger: true,
|
|
||||||
};
|
|
||||||
const punctualWithoutSeatsRequestedAd: AdEntity = AdEntity.create(
|
|
||||||
punctualWithoutSeatsRequestedCreateAdProps,
|
|
||||||
defaultAdProps,
|
|
||||||
);
|
|
||||||
expect(punctualWithoutSeatsRequestedAd.id.length).toBe(36);
|
|
||||||
expect(punctualWithoutSeatsRequestedAd.getProps().schedule.length).toBe(
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
expect(punctualWithoutSeatsRequestedAd.getProps().schedule[0].time).toBe(
|
|
||||||
'08:30',
|
|
||||||
);
|
|
||||||
expect(punctualWithoutSeatsRequestedAd.getProps().driver).toBeFalsy();
|
|
||||||
expect(punctualWithoutSeatsRequestedAd.getProps().passenger).toBeTruthy();
|
|
||||||
expect(punctualWithoutSeatsRequestedAd.getProps().seatsRequested).toBe(1);
|
|
||||||
});
|
|
||||||
it('should create a new punctual driver ad entity with seats proposed if no seats proposed param is given', async () => {
|
|
||||||
const punctualWithoutSeatsProposedCreateAdProps: CreateAdProps = {
|
|
||||||
userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36',
|
|
||||||
seatsProposed: undefined,
|
|
||||||
seatsRequested: 1,
|
|
||||||
strict: false,
|
|
||||||
waypoints: [originWaypointProps, destinationWaypointProps],
|
|
||||||
...punctualCreateAdProps,
|
|
||||||
driver: true,
|
|
||||||
passenger: false,
|
|
||||||
};
|
|
||||||
const punctualWithoutSeatsProposedAd: AdEntity = AdEntity.create(
|
|
||||||
punctualWithoutSeatsProposedCreateAdProps,
|
|
||||||
defaultAdProps,
|
|
||||||
);
|
|
||||||
expect(punctualWithoutSeatsProposedAd.id.length).toBe(36);
|
|
||||||
expect(punctualWithoutSeatsProposedAd.getProps().schedule.length).toBe(1);
|
|
||||||
expect(punctualWithoutSeatsProposedAd.getProps().schedule[0].time).toBe(
|
|
||||||
'08:30',
|
|
||||||
);
|
|
||||||
expect(punctualWithoutSeatsProposedAd.getProps().driver).toBeTruthy();
|
|
||||||
expect(punctualWithoutSeatsProposedAd.getProps().passenger).toBeFalsy();
|
|
||||||
expect(punctualWithoutSeatsProposedAd.getProps().seatsProposed).toBe(3);
|
|
||||||
});
|
|
||||||
it('should create a new punctual driver ad entity with margin durations if margin durations are empty', async () => {
|
|
||||||
const punctualWithoutMarginDurationCreateAdProps: CreateAdProps = {
|
|
||||||
...baseCreateAdProps,
|
|
||||||
waypoints: [originWaypointProps, destinationWaypointProps],
|
|
||||||
...punctualCreateAdProps,
|
|
||||||
driver: true,
|
|
||||||
passenger: false,
|
|
||||||
};
|
|
||||||
const punctualWithoutMarginDurationAd: AdEntity = AdEntity.create(
|
|
||||||
punctualWithoutMarginDurationCreateAdProps,
|
|
||||||
defaultAdProps,
|
|
||||||
);
|
|
||||||
expect(punctualWithoutMarginDurationAd.id.length).toBe(36);
|
|
||||||
expect(punctualWithoutMarginDurationAd.getProps().schedule.length).toBe(
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
expect(punctualWithoutMarginDurationAd.getProps().schedule[0].time).toBe(
|
|
||||||
'08:30',
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
punctualWithoutMarginDurationAd.getProps().schedule[0].margin,
|
|
||||||
).toBe(900);
|
|
||||||
expect(punctualWithoutMarginDurationAd.getProps().driver).toBeTruthy();
|
|
||||||
expect(punctualWithoutMarginDurationAd.getProps().passenger).toBeFalsy();
|
|
||||||
});
|
|
||||||
it('should create a new punctual passenger ad entity with valid positions if positions are missing', async () => {
|
|
||||||
const punctualWithoutPositionsCreateAdProps: CreateAdProps = {
|
|
||||||
userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36',
|
|
||||||
seatsProposed: 3,
|
|
||||||
seatsRequested: 1,
|
|
||||||
strict: undefined,
|
|
||||||
waypoints: [
|
|
||||||
{
|
|
||||||
position: undefined,
|
|
||||||
address: {
|
|
||||||
houseNumber: '5',
|
|
||||||
street: 'Avenue Foch',
|
|
||||||
locality: 'Nancy',
|
|
||||||
postalCode: '54000',
|
|
||||||
country: 'France',
|
|
||||||
coordinates: {
|
|
||||||
lat: 48.689445,
|
|
||||||
lon: 6.17651,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
position: undefined,
|
|
||||||
address: {
|
|
||||||
locality: 'Paris',
|
|
||||||
postalCode: '75000',
|
|
||||||
country: 'France',
|
|
||||||
coordinates: {
|
|
||||||
lat: 48.8566,
|
|
||||||
lon: 2.3522,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
...punctualCreateAdProps,
|
|
||||||
driver: false,
|
|
||||||
passenger: false,
|
|
||||||
};
|
|
||||||
const punctualWithoutPositionsAd: AdEntity = AdEntity.create(
|
|
||||||
punctualWithoutPositionsCreateAdProps,
|
|
||||||
defaultAdProps,
|
|
||||||
);
|
|
||||||
expect(punctualWithoutPositionsAd.id.length).toBe(36);
|
|
||||||
expect(punctualWithoutPositionsAd.getProps().schedule.length).toBe(1);
|
|
||||||
expect(punctualWithoutPositionsAd.getProps().schedule[0].time).toBe(
|
|
||||||
'08:30',
|
|
||||||
);
|
|
||||||
expect(punctualWithoutPositionsAd.getProps().driver).toBeFalsy();
|
|
||||||
expect(punctualWithoutPositionsAd.getProps().passenger).toBeTruthy();
|
|
||||||
expect(punctualWithoutPositionsAd.getProps().waypoints[0].position).toBe(
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
expect(punctualWithoutPositionsAd.getProps().waypoints[1].position).toBe(
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import {
|
import {
|
||||||
AD_REPOSITORY,
|
AD_REPOSITORY,
|
||||||
INPUT_DATETIME_TRANSFORMER,
|
INPUT_DATETIME_TRANSFORMER,
|
||||||
PARAMS_PROVIDER,
|
|
||||||
} from '@modules/ad/ad.di-tokens';
|
} from '@modules/ad/ad.di-tokens';
|
||||||
import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto';
|
import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto';
|
||||||
import { CreateAdRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/create-ad.request.dto';
|
import { CreateAdRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/create-ad.request.dto';
|
||||||
|
@ -10,7 +9,6 @@ import { AggregateID } from '@mobicoop/ddd-library';
|
||||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||||
import { ConflictException } from '@mobicoop/ddd-library';
|
import { ConflictException } from '@mobicoop/ddd-library';
|
||||||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||||
import { DefaultParamsProviderPort } from '@modules/ad/core/application/ports/default-params-provider.port';
|
|
||||||
import { CreateAdService } from '@modules/ad/core/application/commands/create-ad/create-ad.service';
|
import { CreateAdService } from '@modules/ad/core/application/commands/create-ad/create-ad.service';
|
||||||
import { CreateAdCommand } from '@modules/ad/core/application/commands/create-ad/create-ad.command';
|
import { CreateAdCommand } from '@modules/ad/core/application/commands/create-ad/create-ad.command';
|
||||||
import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors';
|
import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors';
|
||||||
|
@ -41,11 +39,15 @@ const punctualCreateAdRequest: CreateAdRequestDto = {
|
||||||
schedule: [
|
schedule: [
|
||||||
{
|
{
|
||||||
time: '08:15',
|
time: '08:15',
|
||||||
|
margin: 900,
|
||||||
|
day: 4,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
driver: true,
|
driver: true,
|
||||||
passenger: true,
|
passenger: true,
|
||||||
seatsRequested: 1,
|
seatsRequested: 1,
|
||||||
|
seatsProposed: 3,
|
||||||
|
strict: false,
|
||||||
frequency: Frequency.PUNCTUAL,
|
frequency: Frequency.PUNCTUAL,
|
||||||
waypoints: [originWaypoint, destinationWaypoint],
|
waypoints: [originWaypoint, destinationWaypoint],
|
||||||
};
|
};
|
||||||
|
@ -62,20 +64,6 @@ const mockAdRepository = {
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockDefaultParamsProvider: DefaultParamsProviderPort = {
|
|
||||||
getParams: () => {
|
|
||||||
return {
|
|
||||||
DEPARTURE_TIME_MARGIN: 900,
|
|
||||||
DRIVER: false,
|
|
||||||
SEATS_PROPOSED: 3,
|
|
||||||
PASSENGER: true,
|
|
||||||
SEATS_REQUESTED: 1,
|
|
||||||
STRICT: false,
|
|
||||||
TIMEZONE: 'Europe/Paris',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockInputDateTimeTransformer: DateTimeTransformerPort = {
|
const mockInputDateTimeTransformer: DateTimeTransformerPort = {
|
||||||
fromDate: jest.fn(),
|
fromDate: jest.fn(),
|
||||||
toDate: jest.fn(),
|
toDate: jest.fn(),
|
||||||
|
@ -93,10 +81,6 @@ describe('create-ad.service', () => {
|
||||||
provide: AD_REPOSITORY,
|
provide: AD_REPOSITORY,
|
||||||
useValue: mockAdRepository,
|
useValue: mockAdRepository,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
provide: PARAMS_PROVIDER,
|
|
||||||
useValue: mockDefaultParamsProvider,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: INPUT_DATETIME_TRANSFORMER,
|
provide: INPUT_DATETIME_TRANSFORMER,
|
||||||
useValue: mockInputDateTimeTransformer,
|
useValue: mockInputDateTimeTransformer,
|
||||||
|
@ -118,9 +102,8 @@ describe('create-ad.service', () => {
|
||||||
AdEntity.create = jest.fn().mockReturnValue({
|
AdEntity.create = jest.fn().mockReturnValue({
|
||||||
id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
|
id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
|
||||||
});
|
});
|
||||||
const result: AggregateID = await createAdService.execute(
|
const result: AggregateID =
|
||||||
createAdCommand,
|
await createAdService.execute(createAdCommand);
|
||||||
);
|
|
||||||
expect(result).toBe('047a6ecf-23d4-4d3e-877c-3225d560a8da');
|
expect(result).toBe('047a6ecf-23d4-4d3e-877c-3225d560a8da');
|
||||||
});
|
});
|
||||||
it('should throw an error if something bad happens', async () => {
|
it('should throw an error if something bad happens', async () => {
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
|
import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
|
||||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||||
import {
|
import { CreateAdProps, Frequency } from '@modules/ad/core/domain/ad.types';
|
||||||
CreateAdProps,
|
|
||||||
DefaultAdProps,
|
|
||||||
Frequency,
|
|
||||||
} from '@modules/ad/core/domain/ad.types';
|
|
||||||
import { FindAdByIdQuery } from '@modules/ad/core/application/queries/find-ad-by-id/find-ad-by-id.query';
|
import { FindAdByIdQuery } from '@modules/ad/core/application/queries/find-ad-by-id/find-ad-by-id.query';
|
||||||
import { FindAdByIdQueryHandler } from '@modules/ad/core/application/queries/find-ad-by-id/find-ad-by-id.query-handler';
|
import { FindAdByIdQueryHandler } from '@modules/ad/core/application/queries/find-ad-by-id/find-ad-by-id.query-handler';
|
||||||
import { WaypointProps } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
|
import { WaypointProps } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
|
||||||
|
@ -60,19 +56,7 @@ const punctualPassengerCreateAdProps: CreateAdProps = {
|
||||||
passenger: true,
|
passenger: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultAdProps: DefaultAdProps = {
|
const ad: AdEntity = AdEntity.create(punctualPassengerCreateAdProps);
|
||||||
marginDuration: 900,
|
|
||||||
driver: false,
|
|
||||||
passenger: true,
|
|
||||||
seatsProposed: 3,
|
|
||||||
seatsRequested: 1,
|
|
||||||
strict: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ad: AdEntity = AdEntity.create(
|
|
||||||
punctualPassengerCreateAdProps,
|
|
||||||
defaultAdProps,
|
|
||||||
);
|
|
||||||
|
|
||||||
const mockAdRepository = {
|
const mockAdRepository = {
|
||||||
findOneById: jest.fn().mockImplementation(() => ad),
|
findOneById: jest.fn().mockImplementation(() => ad),
|
||||||
|
@ -106,9 +90,8 @@ describe('find-ad-by-id.query-handler', () => {
|
||||||
const findAdbyIdQuery = new FindAdByIdQuery(
|
const findAdbyIdQuery = new FindAdByIdQuery(
|
||||||
'dd264806-13b4-4226-9b18-87adf0ad5dd1',
|
'dd264806-13b4-4226-9b18-87adf0ad5dd1',
|
||||||
);
|
);
|
||||||
const ad: AdEntity = await findAdByIdQueryHandler.execute(
|
const ad: AdEntity =
|
||||||
findAdbyIdQuery,
|
await findAdByIdQueryHandler.execute(findAdbyIdQuery);
|
||||||
);
|
|
||||||
expect(ad.getProps().fromDate).toBe('2023-06-22');
|
expect(ad.getProps().fromDate).toBe('2023-06-22');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
import { DefaultParams } from '@modules/ad/core/application/ports/default-params.type';
|
|
||||||
import { DefaultParamsProvider } from '@modules/ad/infrastructure/default-params-provider';
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
|
|
||||||
const mockConfigService = {
|
|
||||||
get: jest.fn().mockImplementation((value: string) => {
|
|
||||||
switch (value) {
|
|
||||||
case 'DEPARTURE_TIME_MARGIN':
|
|
||||||
return 900;
|
|
||||||
case 'ROLE':
|
|
||||||
return 'passenger';
|
|
||||||
case 'SEATS_PROPOSED':
|
|
||||||
return 3;
|
|
||||||
case 'SEATS_REQUESTED':
|
|
||||||
return 1;
|
|
||||||
case 'STRICT_FREQUENCY':
|
|
||||||
return 'false';
|
|
||||||
case 'DEFAULT_TIMEZONE':
|
|
||||||
return 'Europe/Paris';
|
|
||||||
default:
|
|
||||||
return 'some_default_value';
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('DefaultParamsProvider', () => {
|
|
||||||
let defaultParamsProvider: DefaultParamsProvider;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
imports: [],
|
|
||||||
providers: [
|
|
||||||
DefaultParamsProvider,
|
|
||||||
{
|
|
||||||
provide: ConfigService,
|
|
||||||
useValue: mockConfigService,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
defaultParamsProvider = module.get<DefaultParamsProvider>(
|
|
||||||
DefaultParamsProvider,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(defaultParamsProvider).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should provide default params', async () => {
|
|
||||||
const params: DefaultParams = defaultParamsProvider.getParams();
|
|
||||||
expect(params.DEPARTURE_TIME_MARGIN).toBe(900);
|
|
||||||
expect(params.PASSENGER).toBeTruthy();
|
|
||||||
expect(params.DRIVER).toBeFalsy();
|
|
||||||
expect(params.TIMEZONE).toBe('Europe/Paris');
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,29 +1,10 @@
|
||||||
import {
|
import { TIMEZONE_FINDER, TIME_CONVERTER } from '@modules/ad/ad.di-tokens';
|
||||||
PARAMS_PROVIDER,
|
|
||||||
TIMEZONE_FINDER,
|
|
||||||
TIME_CONVERTER,
|
|
||||||
} from '@modules/ad/ad.di-tokens';
|
|
||||||
import { Frequency } from '@modules/ad/core/application/ports/datetime-transformer.port';
|
import { Frequency } from '@modules/ad/core/application/ports/datetime-transformer.port';
|
||||||
import { DefaultParamsProviderPort } from '@modules/ad/core/application/ports/default-params-provider.port';
|
|
||||||
import { TimeConverterPort } from '@modules/ad/core/application/ports/time-converter.port';
|
import { TimeConverterPort } from '@modules/ad/core/application/ports/time-converter.port';
|
||||||
import { TimezoneFinderPort } from '@modules/ad/core/application/ports/timezone-finder.port';
|
import { TimezoneFinderPort } from '@modules/ad/core/application/ports/timezone-finder.port';
|
||||||
import { InputDateTimeTransformer } from '@modules/ad/infrastructure/input-datetime-transformer';
|
import { InputDateTimeTransformer } from '@modules/ad/infrastructure/input-datetime-transformer';
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
|
||||||
const mockDefaultParamsProvider: DefaultParamsProviderPort = {
|
|
||||||
getParams: () => {
|
|
||||||
return {
|
|
||||||
DEPARTURE_TIME_MARGIN: 900,
|
|
||||||
DRIVER: false,
|
|
||||||
SEATS_PROPOSED: 3,
|
|
||||||
PASSENGER: true,
|
|
||||||
SEATS_REQUESTED: 1,
|
|
||||||
STRICT: false,
|
|
||||||
TIMEZONE: 'Europe/Paris',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockTimezoneFinder: TimezoneFinderPort = {
|
const mockTimezoneFinder: TimezoneFinderPort = {
|
||||||
timezones: jest.fn().mockImplementation(() => ['Europe/Paris']),
|
timezones: jest.fn().mockImplementation(() => ['Europe/Paris']),
|
||||||
};
|
};
|
||||||
|
@ -56,10 +37,6 @@ describe('Input Datetime Transformer', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
{
|
|
||||||
provide: PARAMS_PROVIDER,
|
|
||||||
useValue: mockDefaultParamsProvider,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: TIMEZONE_FINDER,
|
provide: TIMEZONE_FINDER,
|
||||||
useValue: mockTimezoneFinder,
|
useValue: mockTimezoneFinder,
|
||||||
|
|
|
@ -1,29 +1,10 @@
|
||||||
import {
|
import { TIMEZONE_FINDER, TIME_CONVERTER } from '@modules/ad/ad.di-tokens';
|
||||||
PARAMS_PROVIDER,
|
|
||||||
TIMEZONE_FINDER,
|
|
||||||
TIME_CONVERTER,
|
|
||||||
} from '@modules/ad/ad.di-tokens';
|
|
||||||
import { Frequency } from '@modules/ad/core/application/ports/datetime-transformer.port';
|
import { Frequency } from '@modules/ad/core/application/ports/datetime-transformer.port';
|
||||||
import { DefaultParamsProviderPort } from '@modules/ad/core/application/ports/default-params-provider.port';
|
|
||||||
import { TimeConverterPort } from '@modules/ad/core/application/ports/time-converter.port';
|
import { TimeConverterPort } from '@modules/ad/core/application/ports/time-converter.port';
|
||||||
import { TimezoneFinderPort } from '@modules/ad/core/application/ports/timezone-finder.port';
|
import { TimezoneFinderPort } from '@modules/ad/core/application/ports/timezone-finder.port';
|
||||||
import { OutputDateTimeTransformer } from '@modules/ad/infrastructure/output-datetime-transformer';
|
import { OutputDateTimeTransformer } from '@modules/ad/infrastructure/output-datetime-transformer';
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
|
||||||
const mockDefaultParamsProvider: DefaultParamsProviderPort = {
|
|
||||||
getParams: () => {
|
|
||||||
return {
|
|
||||||
DEPARTURE_TIME_MARGIN: 900,
|
|
||||||
DRIVER: false,
|
|
||||||
SEATS_PROPOSED: 3,
|
|
||||||
PASSENGER: true,
|
|
||||||
SEATS_REQUESTED: 1,
|
|
||||||
STRICT: false,
|
|
||||||
TIMEZONE: 'Europe/Paris',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockTimezoneFinder: TimezoneFinderPort = {
|
const mockTimezoneFinder: TimezoneFinderPort = {
|
||||||
timezones: jest.fn().mockImplementation(() => ['Europe/Paris']),
|
timezones: jest.fn().mockImplementation(() => ['Europe/Paris']),
|
||||||
};
|
};
|
||||||
|
@ -56,10 +37,6 @@ describe('Output Datetime Transformer', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
{
|
|
||||||
provide: PARAMS_PROVIDER,
|
|
||||||
useValue: mockDefaultParamsProvider,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: TIMEZONE_FINDER,
|
provide: TIMEZONE_FINDER,
|
||||||
useValue: mockTimezoneFinder,
|
useValue: mockTimezoneFinder,
|
||||||
|
|
|
@ -34,11 +34,15 @@ const punctualCreateAdRequest: CreateAdRequestDto = {
|
||||||
schedule: [
|
schedule: [
|
||||||
{
|
{
|
||||||
time: '08:15',
|
time: '08:15',
|
||||||
|
day: 4,
|
||||||
|
margin: 600,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
driver: false,
|
driver: false,
|
||||||
passenger: true,
|
passenger: true,
|
||||||
seatsRequested: 1,
|
seatsRequested: 1,
|
||||||
|
seatsProposed: 3,
|
||||||
|
strict: false,
|
||||||
frequency: Frequency.PUNCTUAL,
|
frequency: Frequency.PUNCTUAL,
|
||||||
waypoints: [originWaypoint, destinationWaypoint],
|
waypoints: [originWaypoint, destinationWaypoint],
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
|
||||||
import { ScheduleItemDto } from '@modules/ad/interface/grpc-controllers/dtos/schedule-item.dto';
|
|
||||||
import { HasDay } from '@modules/ad/interface/grpc-controllers/dtos/validators/decorators/has-day.decorator';
|
|
||||||
import { Validator } from 'class-validator';
|
|
||||||
|
|
||||||
describe('Has day decorator', () => {
|
|
||||||
class MyClass {
|
|
||||||
@HasDay('schedule', {
|
|
||||||
message: 'At least a day is required for a recurrent ad',
|
|
||||||
})
|
|
||||||
frequency: Frequency;
|
|
||||||
|
|
||||||
schedule: ScheduleItemDto[];
|
|
||||||
}
|
|
||||||
|
|
||||||
it('should return a property decorator has a function', () => {
|
|
||||||
const hasDay = HasDay('someProperty');
|
|
||||||
expect(typeof hasDay).toBe('function');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should validate a punctual frequency associated with a valid schedule', async () => {
|
|
||||||
const myClassInstance = new MyClass();
|
|
||||||
myClassInstance.frequency = Frequency.PUNCTUAL;
|
|
||||||
myClassInstance.schedule = [
|
|
||||||
{
|
|
||||||
time: '07:15',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const validator = new Validator();
|
|
||||||
const validation = await validator.validate(myClassInstance);
|
|
||||||
expect(validation.length).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should validate a recurrent frequency associated with a valid schedule', async () => {
|
|
||||||
const myClassInstance = new MyClass();
|
|
||||||
myClassInstance.frequency = Frequency.RECURRENT;
|
|
||||||
myClassInstance.schedule = [
|
|
||||||
{
|
|
||||||
time: '07:15',
|
|
||||||
day: 1,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const validator = new Validator();
|
|
||||||
const validation = await validator.validate(myClassInstance);
|
|
||||||
expect(validation.length).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not validate a recurrent frequency associated with an invalid schedule', async () => {
|
|
||||||
const myClassInstance = new MyClass();
|
|
||||||
myClassInstance.frequency = Frequency.RECURRENT;
|
|
||||||
myClassInstance.schedule = [
|
|
||||||
{
|
|
||||||
time: '07:15',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const validator = new Validator();
|
|
||||||
const validation = await validator.validate(myClassInstance);
|
|
||||||
expect(validation.length).toBe(1);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { HasRole } from '@modules/ad/interface/grpc-controllers/dtos/validators/decorators/has-role.decorator';
|
||||||
|
import { Validator } from 'class-validator';
|
||||||
|
|
||||||
|
describe('has role decorator', () => {
|
||||||
|
class MyClass {
|
||||||
|
@HasRole('passenger')
|
||||||
|
driver: boolean;
|
||||||
|
|
||||||
|
passenger: boolean;
|
||||||
|
}
|
||||||
|
it('should return a property decorator has a function', () => {
|
||||||
|
const hasRole = HasRole('property');
|
||||||
|
expect(typeof hasRole).toBe('function');
|
||||||
|
});
|
||||||
|
it('should validate an instance with driver only set to true', async () => {
|
||||||
|
const myClassInstance = new MyClass();
|
||||||
|
myClassInstance.driver = true;
|
||||||
|
myClassInstance.passenger = false;
|
||||||
|
const validator = new Validator();
|
||||||
|
const validation = await validator.validate(myClassInstance);
|
||||||
|
expect(validation.length).toBe(0);
|
||||||
|
});
|
||||||
|
it('should validate an instance with passenger only set to true', async () => {
|
||||||
|
const myClassInstance = new MyClass();
|
||||||
|
myClassInstance.driver = false;
|
||||||
|
myClassInstance.passenger = true;
|
||||||
|
const validator = new Validator();
|
||||||
|
const validation = await validator.validate(myClassInstance);
|
||||||
|
expect(validation.length).toBe(0);
|
||||||
|
});
|
||||||
|
it('should validate an instance with driver and passenger set to true', async () => {
|
||||||
|
const myClassInstance = new MyClass();
|
||||||
|
myClassInstance.driver = true;
|
||||||
|
myClassInstance.passenger = true;
|
||||||
|
const validator = new Validator();
|
||||||
|
const validation = await validator.validate(myClassInstance);
|
||||||
|
expect(validation.length).toBe(0);
|
||||||
|
});
|
||||||
|
it('should not validate an instance without driver and passenger set to true', async () => {
|
||||||
|
const myClassInstance = new MyClass();
|
||||||
|
myClassInstance.driver = false;
|
||||||
|
myClassInstance.passenger = false;
|
||||||
|
const validator = new Validator();
|
||||||
|
const validation = await validator.validate(myClassInstance);
|
||||||
|
expect(validation.length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { HasSeats } from '@modules/ad/interface/grpc-controllers/dtos/validators/decorators/has-seats.decorator';
|
||||||
|
import { Validator } from 'class-validator';
|
||||||
|
|
||||||
|
describe('has seats decorator', () => {
|
||||||
|
class MyClass {
|
||||||
|
@HasSeats('seatsProposed')
|
||||||
|
driver: boolean;
|
||||||
|
@HasSeats('seatsRequested')
|
||||||
|
passenger: boolean;
|
||||||
|
|
||||||
|
seatsProposed?: number;
|
||||||
|
seatsRequested?: number;
|
||||||
|
}
|
||||||
|
it('should return a property decorator has a function', () => {
|
||||||
|
const hasSeats = HasSeats('property');
|
||||||
|
expect(typeof hasSeats).toBe('function');
|
||||||
|
});
|
||||||
|
it('should validate an instance with seats proposed as driver', async () => {
|
||||||
|
const myClassInstance = new MyClass();
|
||||||
|
myClassInstance.driver = true;
|
||||||
|
myClassInstance.seatsProposed = 3;
|
||||||
|
const validator = new Validator();
|
||||||
|
const validation = await validator.validate(myClassInstance);
|
||||||
|
expect(validation.length).toBe(0);
|
||||||
|
});
|
||||||
|
it('should validate an instance with seats requested as passenger', async () => {
|
||||||
|
const myClassInstance = new MyClass();
|
||||||
|
myClassInstance.passenger = true;
|
||||||
|
myClassInstance.seatsRequested = 1;
|
||||||
|
const validator = new Validator();
|
||||||
|
const validation = await validator.validate(myClassInstance);
|
||||||
|
expect(validation.length).toBe(0);
|
||||||
|
});
|
||||||
|
it('should validate an instance with seats proposed as driver and passenger', async () => {
|
||||||
|
const myClassInstance = new MyClass();
|
||||||
|
myClassInstance.driver = true;
|
||||||
|
myClassInstance.seatsProposed = 3;
|
||||||
|
myClassInstance.passenger = true;
|
||||||
|
myClassInstance.seatsRequested = 1;
|
||||||
|
const validator = new Validator();
|
||||||
|
const validation = await validator.validate(myClassInstance);
|
||||||
|
expect(validation.length).toBe(0);
|
||||||
|
});
|
||||||
|
it('should not validate an instance without seats proposed as driver', async () => {
|
||||||
|
const myClassInstance = new MyClass();
|
||||||
|
myClassInstance.driver = true;
|
||||||
|
myClassInstance.seatsProposed = 0;
|
||||||
|
const validator = new Validator();
|
||||||
|
const validation = await validator.validate(myClassInstance);
|
||||||
|
expect(validation.length).toBe(1);
|
||||||
|
});
|
||||||
|
it('should not validate an instance without seats requested as passenger', async () => {
|
||||||
|
const myClassInstance = new MyClass();
|
||||||
|
myClassInstance.passenger = true;
|
||||||
|
const validator = new Validator();
|
||||||
|
const validation = await validator.validate(myClassInstance);
|
||||||
|
expect(validation.length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
|
@ -2,7 +2,8 @@ import { hasValidPositionIndexes } from '@modules/ad/interface/grpc-controllers/
|
||||||
import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto';
|
import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto';
|
||||||
|
|
||||||
describe('addresses position validator', () => {
|
describe('addresses position validator', () => {
|
||||||
const mockAddress1: WaypointDto = {
|
const waypoint1: WaypointDto = {
|
||||||
|
position: 0,
|
||||||
lat: 48.689445,
|
lat: 48.689445,
|
||||||
lon: 6.17651,
|
lon: 6.17651,
|
||||||
houseNumber: '5',
|
houseNumber: '5',
|
||||||
|
@ -11,14 +12,16 @@ describe('addresses position validator', () => {
|
||||||
postalCode: '54000',
|
postalCode: '54000',
|
||||||
country: 'France',
|
country: 'France',
|
||||||
};
|
};
|
||||||
const mockAddress2: WaypointDto = {
|
const waypoint2: WaypointDto = {
|
||||||
|
position: 1,
|
||||||
lat: 48.8566,
|
lat: 48.8566,
|
||||||
lon: 2.3522,
|
lon: 2.3522,
|
||||||
locality: 'Paris',
|
locality: 'Paris',
|
||||||
postalCode: '75000',
|
postalCode: '75000',
|
||||||
country: 'France',
|
country: 'France',
|
||||||
};
|
};
|
||||||
const mockAddress3: WaypointDto = {
|
const waypoint3: WaypointDto = {
|
||||||
|
position: 2,
|
||||||
lon: 49.2628,
|
lon: 49.2628,
|
||||||
lat: 4.0347,
|
lat: 4.0347,
|
||||||
locality: 'Reims',
|
locality: 'Reims',
|
||||||
|
@ -26,50 +29,26 @@ describe('addresses position validator', () => {
|
||||||
country: 'France',
|
country: 'France',
|
||||||
};
|
};
|
||||||
|
|
||||||
it('should not validate if no position is defined', () => {
|
|
||||||
expect(
|
|
||||||
hasValidPositionIndexes([mockAddress1, mockAddress2, mockAddress3]),
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
|
||||||
it('should not validate if only one position is defined', () => {
|
|
||||||
mockAddress1.position = 0;
|
|
||||||
expect(
|
|
||||||
hasValidPositionIndexes([mockAddress1, mockAddress2, mockAddress3]),
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
|
||||||
it('should not validate if positions are partially defined', () => {
|
|
||||||
mockAddress1.position = 0;
|
|
||||||
mockAddress2.position = null;
|
|
||||||
mockAddress3.position = undefined;
|
|
||||||
expect(
|
|
||||||
hasValidPositionIndexes([mockAddress1, mockAddress2, mockAddress3]),
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not validate if multiple positions have same value', () => {
|
it('should not validate if multiple positions have same value', () => {
|
||||||
mockAddress1.position = 0;
|
|
||||||
mockAddress2.position = 1;
|
|
||||||
mockAddress3.position = 1;
|
|
||||||
expect(
|
expect(
|
||||||
hasValidPositionIndexes([mockAddress1, mockAddress2, mockAddress3]),
|
hasValidPositionIndexes([waypoint1, waypoint1, waypoint2]),
|
||||||
).toBeFalsy();
|
).toBeFalsy();
|
||||||
});
|
});
|
||||||
it('should validate if all positions are ordered', () => {
|
it('should not validate if positions are not consecutives', () => {
|
||||||
mockAddress1.position = 0;
|
expect(hasValidPositionIndexes([waypoint1, waypoint3])).toBeFalsy();
|
||||||
mockAddress2.position = 1;
|
|
||||||
mockAddress3.position = 2;
|
|
||||||
expect(
|
|
||||||
hasValidPositionIndexes([mockAddress1, mockAddress2, mockAddress3]),
|
|
||||||
).toBeTruthy();
|
|
||||||
mockAddress1.position = 1;
|
|
||||||
mockAddress2.position = 2;
|
|
||||||
mockAddress3.position = 3;
|
|
||||||
expect(
|
|
||||||
hasValidPositionIndexes([mockAddress1, mockAddress2, mockAddress3]),
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
});
|
||||||
it('should not validate if no waypoints are defined', () => {
|
it('should not validate if waypoints are empty', () => {
|
||||||
expect(hasValidPositionIndexes(undefined)).toBeFalsy();
|
|
||||||
expect(hasValidPositionIndexes([])).toBeFalsy();
|
expect(hasValidPositionIndexes([])).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
it('should validate if all positions are ordered', () => {
|
||||||
|
expect(
|
||||||
|
hasValidPositionIndexes([waypoint1, waypoint2, waypoint3]),
|
||||||
|
).toBeTruthy();
|
||||||
|
waypoint1.position = 1;
|
||||||
|
waypoint2.position = 2;
|
||||||
|
waypoint3.position = 3;
|
||||||
|
expect(
|
||||||
|
hasValidPositionIndexes([waypoint1, waypoint2, waypoint3]),
|
||||||
|
).toBeTruthy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import { Module, Provider } from '@nestjs/common';
|
import { Module, Provider } from '@nestjs/common';
|
||||||
import { MESSAGE_PUBLISHER } from './messager.di-tokens';
|
import { MESSAGE_PUBLISHER } from './messager.di-tokens';
|
||||||
|
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
import { SERVICE_NAME } from '@src/app.constants';
|
||||||
import {
|
import {
|
||||||
MessageBrokerModule,
|
MessageBrokerModule,
|
||||||
MessageBrokerModuleOptions,
|
MessageBrokerModuleOptions,
|
||||||
MessageBrokerPublisher,
|
MessageBrokerPublisher,
|
||||||
} from '@mobicoop/message-broker-module';
|
} from '@mobicoop/message-broker-module';
|
||||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
||||||
import { SERVICE_NAME } from '@src/app.constants';
|
|
||||||
|
|
||||||
const imports = [
|
const imports = [
|
||||||
MessageBrokerModule.forRootAsync({
|
MessageBrokerModule.forRootAsync({
|
||||||
|
@ -15,8 +16,13 @@ const imports = [
|
||||||
useFactory: async (
|
useFactory: async (
|
||||||
configService: ConfigService,
|
configService: ConfigService,
|
||||||
): Promise<MessageBrokerModuleOptions> => ({
|
): Promise<MessageBrokerModuleOptions> => ({
|
||||||
uri: configService.get<string>('MESSAGE_BROKER_URI'),
|
uri: configService.get<string>('MESSAGE_BROKER_URI') as string,
|
||||||
exchange: configService.get<string>('MESSAGE_BROKER_EXCHANGE'),
|
exchange: {
|
||||||
|
name: configService.get<string>('MESSAGE_BROKER_EXCHANGE') as string,
|
||||||
|
durable: configService.get<boolean>(
|
||||||
|
'MESSAGE_BROKER_EXCHANGE_DURABILITY',
|
||||||
|
) as boolean,
|
||||||
|
},
|
||||||
name: SERVICE_NAME,
|
name: SERVICE_NAME,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -12,10 +12,10 @@
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strictNullChecks": false,
|
"strictNullChecks": true,
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": true,
|
||||||
"strictBindCallApply": false,
|
"strictBindCallApply": false,
|
||||||
"forceConsistentCasingInFileNames": false,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"noFallthroughCasesInSwitch": false,
|
"noFallthroughCasesInSwitch": false,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@libs/*": ["src/libs/*"],
|
"@libs/*": ["src/libs/*"],
|
||||||
|
|
Loading…
Reference in New Issue