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_URI=amqp://v3-broker:5672
|
||||
MESSAGE_BROKER_EXCHANGE=mobicoop
|
||||
MESSAGE_BROKER_EXCHANGE_DURABILITY=true
|
||||
|
||||
# REDIS
|
||||
REDIS_HOST=v3-redis
|
||||
REDIS_PASSWORD=redis
|
||||
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",
|
||||
"driver": true,
|
||||
"passenger": false,
|
||||
"seatsProposed": 3,
|
||||
"frequency": "PUNCTUAL",
|
||||
"fromDate": "2023-01-15",
|
||||
"toDate": "2023-01-15",
|
||||
"schedule": [
|
||||
{
|
||||
"day": 0,
|
||||
"time": "09:00",
|
||||
"margin": 900
|
||||
}
|
||||
|
@ -103,7 +105,7 @@ The app exposes the following [gRPC](https://grpc.io/) services :
|
|||
{
|
||||
"userId": "80c9bb02-0931-4a1d-bea6-22d358992245",
|
||||
"driver": true,
|
||||
"pasenger": true,
|
||||
"passenger": true,
|
||||
"seatsProposed": 3,
|
||||
"seatsRequested": 1,
|
||||
"frequency": "PUNCTUAL",
|
||||
|
@ -111,6 +113,7 @@ The app exposes the following [gRPC](https://grpc.io/) services :
|
|||
"toDate": "2023-01-15",
|
||||
"schedule": [
|
||||
{
|
||||
"day": 0,
|
||||
"time": "09:00",
|
||||
"margin": 900
|
||||
}
|
||||
|
@ -144,22 +147,25 @@ The app exposes the following [gRPC](https://grpc.io/) services :
|
|||
{
|
||||
"userId": "80c9bb02-0931-4a1d-bea6-22d358992245",
|
||||
"passenger": true,
|
||||
"seatsPassenger": 1,
|
||||
"seatsRequested": 1,
|
||||
"frequency": "RECURRRENT",
|
||||
"fromDate": "2023-01-15",
|
||||
"toDate": "2023-12-31",
|
||||
"schedule": [
|
||||
{
|
||||
"day": 1,
|
||||
"time": "07:00"
|
||||
"time": "07:00",
|
||||
"margin": 600
|
||||
},
|
||||
{
|
||||
"day": 2,
|
||||
"time": "07:05"
|
||||
"time": "07:05",
|
||||
"margin": 600
|
||||
},
|
||||
{
|
||||
"day": 5,
|
||||
"time": "07:10"
|
||||
"time": "07:10",
|
||||
"margin": 600
|
||||
}
|
||||
],
|
||||
"waypoints": [
|
||||
|
@ -188,22 +194,20 @@ The app exposes the following [gRPC](https://grpc.io/) services :
|
|||
The list of possible options when creating an ad :
|
||||
|
||||
- userId: the user id (as a uuid)
|
||||
- driver (boolean, optional): if the ad is a driver ad
|
||||
- passenger (boolean, optional): if the ad is a passenger 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): 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`
|
||||
- fromDate: start date for recurrent ad, carpool date 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 :
|
||||
- the week day as a number, from 0 (sunday) to 6 (saturday) if the ad is recurrent
|
||||
- schedule: an array of schedule items, a schedule item containing :
|
||||
- the week day as a number, from 0 (sunday) to 6 (saturday)
|
||||
- the departure time (as HH:MM)
|
||||
- the margin around the departure time in seconds (optional)
|
||||
- seatsProposed (optional): number of seats proposed as driver
|
||||
- seatsRequested (optional): number of seats requested as passenger
|
||||
- strict (boolean, optional): if set to true, allow matching only with similar frequency ads
|
||||
- the margin around the departure time in seconds
|
||||
- seatsProposed: number of seats proposed as driver (required if `driver` is true)
|
||||
- seatsRequested: number of seats requested as passenger (required if `passenger` is true)
|
||||
- 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
|
||||
|
||||
Default values must be set in `.env` file.
|
||||
|
||||
## Messages
|
||||
|
||||
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",
|
||||
"version": "2.1.1",
|
||||
"version": "2.2.0",
|
||||
"description": "Mobicoop V3 Ad",
|
||||
"author": "sbriat",
|
||||
"private": true,
|
||||
|
@ -30,56 +30,56 @@
|
|||
"migrate:deploy": "npx prisma migrate deploy"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "^1.8.14",
|
||||
"@grpc/proto-loader": "^0.7.6",
|
||||
"@liaoliaots/nestjs-redis": "^9.0.5",
|
||||
"@mobicoop/configuration-module": "^1.2.0",
|
||||
"@mobicoop/ddd-library": "^1.0.0",
|
||||
"@mobicoop/health-module": "^2.0.0",
|
||||
"@mobicoop/message-broker-module": "^1.2.0",
|
||||
"@nestjs/common": "^9.0.0",
|
||||
"@nestjs/config": "^2.3.1",
|
||||
"@nestjs/core": "^9.0.0",
|
||||
"@nestjs/cqrs": "^9.0.3",
|
||||
"@nestjs/event-emitter": "^1.4.2",
|
||||
"@nestjs/microservices": "^9.4.0",
|
||||
"@nestjs/platform-express": "^9.0.0",
|
||||
"@nestjs/terminus": "^9.2.2",
|
||||
"@prisma/client": "^4.13.0",
|
||||
"@grpc/grpc-js": "^1.9.5",
|
||||
"@grpc/proto-loader": "^0.7.10",
|
||||
"@songkeys/nestjs-redis": "^10.0.0",
|
||||
"@mobicoop/configuration-module": "^2.0.0",
|
||||
"@mobicoop/ddd-library": "^2.0.0",
|
||||
"@mobicoop/health-module": "^2.3.1",
|
||||
"@mobicoop/message-broker-module": "^2.1.1",
|
||||
"@nestjs/common": "^10.2.7",
|
||||
"@nestjs/config": "^3.1.1",
|
||||
"@nestjs/core": "^10.2.7",
|
||||
"@nestjs/cqrs": "^10.2.6",
|
||||
"@nestjs/event-emitter": "^2.0.2",
|
||||
"@nestjs/microservices": "^10.2.7",
|
||||
"@nestjs/platform-express": "^10.2.7",
|
||||
"@nestjs/terminus": "^10.1.1",
|
||||
"@prisma/client": "^5.4.2",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.0",
|
||||
"geo-tz": "^7.0.7",
|
||||
"ioredis": "^5.3.2",
|
||||
"nestjs-request-context": "^2.1.0",
|
||||
"nestjs-request-context": "^3.0.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rxjs": "^7.2.0",
|
||||
"rxjs": "^7.8.1",
|
||||
"timezonecomplete": "^5.12.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^9.0.0",
|
||||
"@nestjs/schematics": "^9.0.0",
|
||||
"@nestjs/testing": "^9.0.0",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/jest": "29.5.0",
|
||||
"@types/node": "18.15.11",
|
||||
"@types/supertest": "^2.0.11",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
||||
"@typescript-eslint/parser": "^5.0.0",
|
||||
"dotenv-cli": "^7.2.1",
|
||||
"eslint": "^8.0.1",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"jest": "29.5.0",
|
||||
"prettier": "^2.3.2",
|
||||
"prisma": "^4.13.0",
|
||||
"source-map-support": "^0.5.20",
|
||||
"supertest": "^6.1.3",
|
||||
"ts-jest": "29.0.5",
|
||||
"ts-loader": "^9.2.3",
|
||||
"ts-node": "^10.0.0",
|
||||
"@nestjs/cli": "^10.1.18",
|
||||
"@nestjs/schematics": "^10.0.2",
|
||||
"@nestjs/testing": "^10.2.7",
|
||||
"@types/express": "^4.17.19",
|
||||
"@types/jest": "29.5.5",
|
||||
"@types/node": "20.8.6",
|
||||
"@types/supertest": "^2.0.14",
|
||||
"@types/uuid": "^9.0.5",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.5",
|
||||
"@typescript-eslint/parser": "^6.7.5",
|
||||
"dotenv-cli": "^7.3.0",
|
||||
"eslint": "^8.51.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"jest": "29.7.0",
|
||||
"prettier": "^3.0.3",
|
||||
"prisma": "^5.4.2",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^6.3.3",
|
||||
"ts-jest": "29.1.1",
|
||||
"ts-loader": "^9.5.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"typescript": "^4.7.4"
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
|
@ -88,6 +88,7 @@
|
|||
"ts"
|
||||
],
|
||||
"modulePathIgnorePatterns": [
|
||||
".constants.ts",
|
||||
".module.ts",
|
||||
".dto.ts",
|
||||
".di-tokens.ts",
|
||||
|
@ -105,6 +106,7 @@
|
|||
"**/*.(t|j)s"
|
||||
],
|
||||
"coveragePathIgnorePatterns": [
|
||||
".constants.ts",
|
||||
".module.ts",
|
||||
".dto.ts",
|
||||
".di-tokens.ts",
|
||||
|
|
|
@ -26,15 +26,19 @@ import { HEALTH_CRITICAL_LOGGING_KEY, SERVICE_NAME } from './app.constants';
|
|||
useFactory: async (
|
||||
configService: ConfigService,
|
||||
): Promise<ConfigurationModuleOptions> => ({
|
||||
domain: configService.get<string>('SERVICE_CONFIGURATION_DOMAIN'),
|
||||
domain: configService.get<string>(
|
||||
'SERVICE_CONFIGURATION_DOMAIN',
|
||||
) as string,
|
||||
messageBroker: {
|
||||
uri: configService.get<string>('MESSAGE_BROKER_URI'),
|
||||
exchange: configService.get<string>('MESSAGE_BROKER_EXCHANGE'),
|
||||
uri: configService.get<string>('MESSAGE_BROKER_URI') as string,
|
||||
exchange: configService.get<string>(
|
||||
'MESSAGE_BROKER_EXCHANGE',
|
||||
) as string,
|
||||
},
|
||||
redis: {
|
||||
host: configService.get<string>('REDIS_HOST'),
|
||||
host: configService.get<string>('REDIS_HOST') as string,
|
||||
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',
|
||||
deleteConfigurationQueue: 'ad-configuration-delete',
|
||||
|
|
|
@ -22,6 +22,6 @@ async function bootstrap() {
|
|||
});
|
||||
|
||||
await app.startAllMicroservices();
|
||||
await app.listen(process.env.HEALTH_SERVICE_PORT);
|
||||
await app.listen(process.env.HEALTH_SERVICE_PORT as unknown as number);
|
||||
}
|
||||
bootstrap();
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
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 TIME_CONVERTER = Symbol('TIME_CONVERTER');
|
||||
export const INPUT_DATETIME_TRANSFORMER = Symbol('INPUT_DATETIME_TRANSFORMER');
|
||||
|
|
|
@ -37,15 +37,15 @@ export class AdMapper
|
|||
const record: AdWriteModel = {
|
||||
uuid: copy.id,
|
||||
userUuid: copy.userId,
|
||||
driver: copy.driver,
|
||||
passenger: copy.passenger,
|
||||
driver: copy.driver as boolean,
|
||||
passenger: copy.passenger as boolean,
|
||||
frequency: copy.frequency,
|
||||
fromDate: new Date(copy.fromDate),
|
||||
toDate: new Date(copy.toDate),
|
||||
schedule: {
|
||||
create: copy.schedule.map((scheduleItem: ScheduleItemProps) => ({
|
||||
uuid: v4(),
|
||||
day: scheduleItem.day,
|
||||
day: scheduleItem.day as number,
|
||||
time: new Date(
|
||||
1970,
|
||||
0,
|
||||
|
@ -53,14 +53,14 @@ export class AdMapper
|
|||
parseInt(scheduleItem.time.split(':')[0]),
|
||||
parseInt(scheduleItem.time.split(':')[1]),
|
||||
),
|
||||
margin: scheduleItem.margin,
|
||||
margin: scheduleItem.margin as number,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
})),
|
||||
},
|
||||
seatsProposed: copy.seatsProposed,
|
||||
seatsRequested: copy.seatsRequested,
|
||||
strict: copy.strict,
|
||||
seatsProposed: copy.seatsProposed as number,
|
||||
seatsRequested: copy.seatsRequested as number,
|
||||
strict: copy.strict as boolean,
|
||||
waypoints: {
|
||||
create: copy.waypoints.map((waypoint: WaypointProps) => ({
|
||||
uuid: v4(),
|
||||
|
@ -92,7 +92,7 @@ export class AdMapper
|
|||
userId: record.userUuid,
|
||||
driver: record.driver,
|
||||
passenger: record.passenger,
|
||||
frequency: Frequency[record.frequency],
|
||||
frequency: record.frequency as Frequency,
|
||||
fromDate: record.fromDate.toISOString().split('T')[0],
|
||||
toDate: record.toDate.toISOString().split('T')[0],
|
||||
schedule: record.schedule.map((scheduleItem: ScheduleItemModel) => ({
|
||||
|
@ -133,8 +133,8 @@ export class AdMapper
|
|||
const props = entity.getProps();
|
||||
const response = new AdResponseDto(entity);
|
||||
response.userId = props.userId;
|
||||
response.driver = props.driver;
|
||||
response.passenger = props.passenger;
|
||||
response.driver = props.driver as boolean;
|
||||
response.passenger = props.passenger as boolean;
|
||||
response.frequency = props.frequency;
|
||||
response.fromDate = this.outputDatetimeTransformer.fromDate(
|
||||
{
|
||||
|
@ -156,7 +156,7 @@ export class AdMapper
|
|||
response.schedule = props.schedule.map(
|
||||
(scheduleItem: ScheduleItemProps) => ({
|
||||
day: this.outputDatetimeTransformer.day(
|
||||
scheduleItem.day,
|
||||
scheduleItem.day as number,
|
||||
{
|
||||
date: props.fromDate,
|
||||
time: scheduleItem.time,
|
||||
|
@ -172,11 +172,11 @@ export class AdMapper
|
|||
},
|
||||
props.frequency,
|
||||
),
|
||||
margin: scheduleItem.margin,
|
||||
margin: scheduleItem.margin as number,
|
||||
}),
|
||||
);
|
||||
response.seatsProposed = props.seatsProposed;
|
||||
response.seatsRequested = props.seatsRequested;
|
||||
response.seatsProposed = props.seatsProposed as number;
|
||||
response.seatsRequested = props.seatsRequested as number;
|
||||
response.waypoints = props.waypoints.map((waypoint: WaypointProps) => ({
|
||||
position: waypoint.position,
|
||||
name: waypoint.address.name,
|
||||
|
|
|
@ -6,12 +6,10 @@ import {
|
|||
AD_REPOSITORY,
|
||||
INPUT_DATETIME_TRANSFORMER,
|
||||
OUTPUT_DATETIME_TRANSFORMER,
|
||||
PARAMS_PROVIDER,
|
||||
TIMEZONE_FINDER,
|
||||
TIME_CONVERTER,
|
||||
} from './ad.di-tokens';
|
||||
import { AdRepository } from './infrastructure/ad.repository';
|
||||
import { DefaultParamsProvider } from './infrastructure/default-params-provider';
|
||||
import { AdMapper } from './ad.mapper';
|
||||
import { CreateAdService } from './core/application/commands/create-ad/create-ad.service';
|
||||
import { TimezoneFinder } from './infrastructure/timezone-finder';
|
||||
|
@ -52,10 +50,6 @@ const messagePublishers: Provider[] = [
|
|||
const orms: Provider[] = [PrismaService];
|
||||
|
||||
const adapters: Provider[] = [
|
||||
{
|
||||
provide: PARAMS_PROVIDER,
|
||||
useClass: DefaultParamsProvider,
|
||||
},
|
||||
{
|
||||
provide: TIMEZONE_FINDER,
|
||||
useClass: TimezoneFinder,
|
||||
|
@ -87,12 +81,6 @@ const adapters: Provider[] = [
|
|||
...orms,
|
||||
...adapters,
|
||||
],
|
||||
exports: [
|
||||
PrismaService,
|
||||
AdMapper,
|
||||
AD_REPOSITORY,
|
||||
PARAMS_PROVIDER,
|
||||
TIMEZONE_FINDER,
|
||||
],
|
||||
exports: [PrismaService, AdMapper, AD_REPOSITORY, TIMEZONE_FINDER],
|
||||
})
|
||||
export class AdModule {}
|
||||
|
|
|
@ -5,15 +5,15 @@ import { Command, CommandProps } from '@mobicoop/ddd-library';
|
|||
|
||||
export class CreateAdCommand extends Command {
|
||||
readonly userId: string;
|
||||
readonly driver?: boolean;
|
||||
readonly passenger?: boolean;
|
||||
readonly frequency?: Frequency;
|
||||
readonly driver: boolean;
|
||||
readonly passenger: boolean;
|
||||
readonly frequency: Frequency;
|
||||
readonly fromDate: string;
|
||||
readonly toDate: string;
|
||||
readonly schedule: ScheduleItem[];
|
||||
readonly seatsProposed?: number;
|
||||
readonly seatsRequested?: number;
|
||||
readonly strict?: boolean;
|
||||
readonly strict: boolean;
|
||||
readonly waypoints: Waypoint[];
|
||||
|
||||
constructor(props: CommandProps<CreateAdCommand>) {
|
||||
|
|
|
@ -4,13 +4,10 @@ import { Inject } from '@nestjs/common';
|
|||
import {
|
||||
AD_REPOSITORY,
|
||||
INPUT_DATETIME_TRANSFORMER,
|
||||
PARAMS_PROVIDER,
|
||||
} from '@modules/ad/ad.di-tokens';
|
||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||
import { Waypoint } from '../../types/waypoint';
|
||||
import { DefaultParams } from '../../ports/default-params.type';
|
||||
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 { AggregateID, ConflictException } from '@mobicoop/ddd-library';
|
||||
import { ScheduleItem } from '../../types/schedule-item';
|
||||
|
@ -18,30 +15,48 @@ import { DateTimeTransformerPort } from '../../ports/datetime-transformer.port';
|
|||
|
||||
@CommandHandler(CreateAdCommand)
|
||||
export class CreateAdService implements ICommandHandler {
|
||||
private readonly _defaultParams: DefaultParams;
|
||||
|
||||
constructor(
|
||||
@Inject(AD_REPOSITORY)
|
||||
private readonly repository: AdRepositoryPort,
|
||||
@Inject(PARAMS_PROVIDER)
|
||||
private readonly defaultParamsProvider: DefaultParamsProviderPort,
|
||||
@Inject(INPUT_DATETIME_TRANSFORMER)
|
||||
private readonly datetimeTransformer: DateTimeTransformerPort,
|
||||
) {
|
||||
this._defaultParams = defaultParamsProvider.getParams();
|
||||
}
|
||||
) {}
|
||||
|
||||
async execute(command: CreateAdCommand): Promise<AggregateID> {
|
||||
const ad = AdEntity.create(
|
||||
{
|
||||
userId: command.userId,
|
||||
driver: command.driver,
|
||||
passenger: command.passenger,
|
||||
frequency: command.frequency,
|
||||
fromDate: this.datetimeTransformer.fromDate(
|
||||
const ad = AdEntity.create({
|
||||
userId: command.userId,
|
||||
driver: command.driver,
|
||||
passenger: command.passenger,
|
||||
frequency: command.frequency,
|
||||
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,
|
||||
time: command.schedule[0].time,
|
||||
time: scheduleItem.time,
|
||||
coordinates: {
|
||||
lon: command.waypoints[0].lon,
|
||||
lat: command.waypoints[0].lat,
|
||||
|
@ -49,11 +64,10 @@ export class CreateAdService implements ICommandHandler {
|
|||
},
|
||||
command.frequency,
|
||||
),
|
||||
toDate: this.datetimeTransformer.toDate(
|
||||
command.toDate,
|
||||
time: this.datetimeTransformer.time(
|
||||
{
|
||||
date: command.fromDate,
|
||||
time: command.schedule[0].time,
|
||||
time: scheduleItem.time,
|
||||
coordinates: {
|
||||
lon: command.waypoints[0].lon,
|
||||
lat: command.waypoints[0].lat,
|
||||
|
@ -61,60 +75,27 @@ export class CreateAdService implements ICommandHandler {
|
|||
},
|
||||
command.frequency,
|
||||
),
|
||||
schedule: command.schedule.map((scheduleItem: ScheduleItem) => ({
|
||||
day: this.datetimeTransformer.day(
|
||||
scheduleItem.day,
|
||||
{
|
||||
date: command.fromDate,
|
||||
time: scheduleItem.time,
|
||||
coordinates: {
|
||||
lon: command.waypoints[0].lon,
|
||||
lat: command.waypoints[0].lat,
|
||||
},
|
||||
},
|
||||
command.frequency,
|
||||
),
|
||||
time: this.datetimeTransformer.time(
|
||||
{
|
||||
date: command.fromDate,
|
||||
time: scheduleItem.time,
|
||||
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,
|
||||
},
|
||||
margin: scheduleItem.margin,
|
||||
})),
|
||||
seatsProposed: command.seatsProposed ?? 0,
|
||||
seatsRequested: command.seatsRequested ?? 0,
|
||||
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 {
|
||||
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 {
|
||||
timezones(lon: number, lat: number, defaultTimezone?: string): string[];
|
||||
timezones(lon: number, lat: number): string[];
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export type ScheduleItem = {
|
||||
day?: number;
|
||||
day: number;
|
||||
time: string;
|
||||
margin?: number;
|
||||
margin: number;
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Address } from './address';
|
||||
|
||||
export type Waypoint = {
|
||||
position?: number;
|
||||
position: number;
|
||||
} & Address;
|
||||
|
|
|
@ -1,29 +1,17 @@
|
|||
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
|
||||
import { v4 } from 'uuid';
|
||||
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 { WaypointProps } from './value-objects/waypoint.value-object';
|
||||
|
||||
export class AdEntity extends AggregateRoot<AdProps> {
|
||||
protected readonly _id: AggregateID;
|
||||
|
||||
static create = (
|
||||
create: CreateAdProps,
|
||||
defaultAdProps: DefaultAdProps,
|
||||
): AdEntity => {
|
||||
static create = (create: CreateAdProps): AdEntity => {
|
||||
const id = v4();
|
||||
const props: AdProps = { ...create };
|
||||
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();
|
||||
const ad = new AdEntity({ id, props });
|
||||
ad.addEvent(
|
||||
new AdCreatedDomainEvent({
|
||||
aggregateId: id,
|
||||
|
@ -34,9 +22,9 @@ export class AdEntity extends AggregateRoot<AdProps> {
|
|||
fromDate: props.fromDate,
|
||||
toDate: props.toDate,
|
||||
schedule: props.schedule.map((day: ScheduleItemProps) => ({
|
||||
day: day.day,
|
||||
day: day.day as number,
|
||||
time: day.time,
|
||||
margin: day.margin,
|
||||
margin: day.margin as number,
|
||||
})),
|
||||
seatsProposed: props.seatsProposed,
|
||||
seatsRequested: props.seatsRequested,
|
||||
|
@ -48,7 +36,7 @@ export class AdEntity extends AggregateRoot<AdProps> {
|
|||
street: waypoint.address.street,
|
||||
postalCode: waypoint.address.postalCode,
|
||||
locality: waypoint.address.locality,
|
||||
country: waypoint.address.postalCode,
|
||||
country: waypoint.address.country,
|
||||
lon: waypoint.address.coordinates.lon,
|
||||
lat: waypoint.address.coordinates.lat,
|
||||
})),
|
||||
|
@ -57,60 +45,7 @@ export class AdEntity extends AggregateRoot<AdProps> {
|
|||
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 {
|
||||
// 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[];
|
||||
}
|
||||
|
||||
export interface DefaultAdProps {
|
||||
driver: boolean;
|
||||
passenger: boolean;
|
||||
marginDuration: number;
|
||||
strict: boolean;
|
||||
seatsProposed: number;
|
||||
seatsRequested: number;
|
||||
}
|
||||
|
||||
export enum Frequency {
|
||||
PUNCTUAL = 'PUNCTUAL',
|
||||
RECURRENT = 'RECURRENT',
|
||||
|
|
|
@ -17,23 +17,23 @@ export interface AddressProps {
|
|||
}
|
||||
|
||||
export class Address extends ValueObject<AddressProps> {
|
||||
get name(): string {
|
||||
get name(): string | undefined {
|
||||
return this.props.name;
|
||||
}
|
||||
|
||||
get houseNumber(): string {
|
||||
get houseNumber(): string | undefined {
|
||||
return this.props.houseNumber;
|
||||
}
|
||||
|
||||
get street(): string {
|
||||
get street(): string | undefined {
|
||||
return this.props.street;
|
||||
}
|
||||
|
||||
get locality(): string {
|
||||
get locality(): string | undefined {
|
||||
return this.props.locality;
|
||||
}
|
||||
|
||||
get postalCode(): string {
|
||||
get postalCode(): string | undefined {
|
||||
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,
|
||||
} from '../core/application/ports/datetime-transformer.port';
|
||||
import { TimeConverterPort } from '../core/application/ports/time-converter.port';
|
||||
import {
|
||||
PARAMS_PROVIDER,
|
||||
TIMEZONE_FINDER,
|
||||
TIME_CONVERTER,
|
||||
} from '../ad.di-tokens';
|
||||
import { TIMEZONE_FINDER, TIME_CONVERTER } from '../ad.di-tokens';
|
||||
import { TimezoneFinderPort } from '../core/application/ports/timezone-finder.port';
|
||||
import { DefaultParamsProviderPort } from '../core/application/ports/default-params-provider.port';
|
||||
|
||||
@Injectable()
|
||||
export class InputDateTimeTransformer implements DateTimeTransformerPort {
|
||||
private readonly _defaultTimezone: string;
|
||||
constructor(
|
||||
@Inject(PARAMS_PROVIDER)
|
||||
private readonly defaultParamsProvider: DefaultParamsProviderPort,
|
||||
@Inject(TIMEZONE_FINDER)
|
||||
private readonly timezoneFinder: TimezoneFinderPort,
|
||||
@Inject(TIME_CONVERTER) private readonly timeConverter: TimeConverterPort,
|
||||
) {
|
||||
this._defaultTimezone = defaultParamsProvider.getParams().TIMEZONE;
|
||||
}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Compute the fromDate : if an ad is punctual, the departure date
|
||||
|
@ -39,7 +29,6 @@ export class InputDateTimeTransformer implements DateTimeTransformerPort {
|
|||
this.timezoneFinder.timezones(
|
||||
geoFromDate.coordinates.lon,
|
||||
geoFromDate.coordinates.lat,
|
||||
this._defaultTimezone,
|
||||
)[0],
|
||||
)
|
||||
.toISOString()
|
||||
|
@ -76,7 +65,6 @@ export class InputDateTimeTransformer implements DateTimeTransformerPort {
|
|||
this.timezoneFinder.timezones(
|
||||
geoFromDate.coordinates.lon,
|
||||
geoFromDate.coordinates.lat,
|
||||
this._defaultTimezone,
|
||||
)[0],
|
||||
);
|
||||
return new Date(this.fromDate(geoFromDate, frequency)).getUTCDay();
|
||||
|
@ -92,7 +80,6 @@ export class InputDateTimeTransformer implements DateTimeTransformerPort {
|
|||
this.timezoneFinder.timezones(
|
||||
geoFromDate.coordinates.lon,
|
||||
geoFromDate.coordinates.lat,
|
||||
this._defaultTimezone,
|
||||
)[0],
|
||||
);
|
||||
return this.timeConverter
|
||||
|
@ -102,7 +89,6 @@ export class InputDateTimeTransformer implements DateTimeTransformerPort {
|
|||
this.timezoneFinder.timezones(
|
||||
geoFromDate.coordinates.lon,
|
||||
geoFromDate.coordinates.lat,
|
||||
this._defaultTimezone,
|
||||
)[0],
|
||||
)
|
||||
.toISOString()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { INestApplication, Injectable, OnModuleInit } from '@nestjs/common';
|
||||
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
@Injectable()
|
||||
|
@ -6,10 +6,4 @@ export class PrismaService extends PrismaClient implements OnModuleInit {
|
|||
async onModuleInit() {
|
||||
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()
|
||||
export class TimezoneFinder implements TimezoneFinderPort {
|
||||
timezones = (
|
||||
lon: number,
|
||||
lat: number,
|
||||
defaultTimezone?: string,
|
||||
): string[] => {
|
||||
const foundTimezones = find(lat, lon);
|
||||
if (defaultTimezone && foundTimezones.length == 0) return [defaultTimezone];
|
||||
return foundTimezones;
|
||||
};
|
||||
timezones = (lon: number, lat: number): string[] => find(lat, lon);
|
||||
}
|
||||
|
|
|
@ -15,24 +15,29 @@ import { WaypointDto } from './waypoint.dto';
|
|||
import { HasValidPositionIndexes } from './validators/decorators/has-valid-position-indexes.decorator';
|
||||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
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 {
|
||||
@IsUUID(4)
|
||||
userId: string;
|
||||
|
||||
@IsOptional()
|
||||
@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()
|
||||
passenger?: boolean;
|
||||
@HasSeats('seatsRequested', {
|
||||
message: 'Number of seats requested as a passenger is required',
|
||||
})
|
||||
passenger: boolean;
|
||||
|
||||
@IsEnum(Frequency)
|
||||
@HasDay('schedule', {
|
||||
message: 'At least a day is required for a recurrent ad',
|
||||
})
|
||||
frequency: Frequency;
|
||||
|
||||
@IsISO8601({
|
||||
|
@ -56,17 +61,16 @@ export class CreateAdRequestDto {
|
|||
@ValidateNested({ each: true })
|
||||
schedule: ScheduleItemDto[];
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
seatsProposed?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
seatsRequested?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
strict?: boolean;
|
||||
strict: boolean;
|
||||
|
||||
@Type(() => WaypointDto)
|
||||
@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 {
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
@Max(6)
|
||||
day?: number;
|
||||
day: number;
|
||||
|
||||
@IsMilitaryTime()
|
||||
time: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
margin?: number;
|
||||
margin: number;
|
||||
}
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
import {
|
||||
registerDecorator,
|
||||
ValidationOptions,
|
||||
ValidationArguments,
|
||||
} from 'class-validator';
|
||||
|
||||
export function HasDay(
|
||||
export function HasRole(
|
||||
property: string,
|
||||
validationOptions?: ValidationOptions,
|
||||
) {
|
||||
return function (object: object, propertyName: string) {
|
||||
registerDecorator({
|
||||
name: 'hasDay',
|
||||
name: 'hasRole',
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
constraints: [property],
|
||||
|
@ -20,13 +19,7 @@ export function HasDay(
|
|||
validate(value: any, args: ValidationArguments) {
|
||||
const [relatedPropertyName] = args.constraints;
|
||||
const relatedValue = (args.object as any)[relatedPropertyName];
|
||||
return (
|
||||
value == Frequency.PUNCTUAL ||
|
||||
(Array.isArray(relatedValue) &&
|
||||
relatedValue.some((scheduleItem) =>
|
||||
scheduleItem.hasOwnProperty('day'),
|
||||
))
|
||||
);
|
||||
return value || relatedValue;
|
||||
},
|
||||
},
|
||||
});
|
|
@ -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';
|
||||
|
||||
export const hasValidPositionIndexes = (waypoints: WaypointDto[]): boolean => {
|
||||
if (!waypoints) return false;
|
||||
if (waypoints.length == 0) return false;
|
||||
if (waypoints.every((waypoint) => waypoint.position === undefined))
|
||||
return false;
|
||||
if (waypoints.every((waypoint) => typeof waypoint.position === 'number')) {
|
||||
const positions = Array.from(waypoints, (waypoint) => waypoint.position);
|
||||
positions.sort();
|
||||
for (let i = 1; i < positions.length; i++)
|
||||
if (positions[i] != positions[i - 1] + 1) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
const positions = Array.from(waypoints, (waypoint) => waypoint.position);
|
||||
positions.sort();
|
||||
for (let i = 1; i < positions.length; i++)
|
||||
if (positions[i] != positions[i - 1] + 1) return false;
|
||||
return true;
|
||||
};
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { IsInt, IsOptional } from 'class-validator';
|
||||
import { IsInt } from 'class-validator';
|
||||
import { AddressDto } from './address.dto';
|
||||
|
||||
export class WaypointDto extends AddressDto {
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
position?: number;
|
||||
position: number;
|
||||
}
|
||||
|
|
|
@ -7,11 +7,7 @@ import {
|
|||
} from '@modules/ad/ad.di-tokens';
|
||||
import { AdMapper } from '@modules/ad/ad.mapper';
|
||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||
import {
|
||||
CreateAdProps,
|
||||
DefaultAdProps,
|
||||
Frequency,
|
||||
} from '@modules/ad/core/domain/ad.types';
|
||||
import { CreateAdProps, Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
import { AdRepository } from '@modules/ad/infrastructure/ad.repository';
|
||||
import { OutputDateTimeTransformer } from '@modules/ad/infrastructure/output-datetime-transformer';
|
||||
import { PrismaService } from '@modules/ad/infrastructure/prisma.service';
|
||||
|
@ -55,7 +51,7 @@ describe('Ad Repository', () => {
|
|||
driver: 'true',
|
||||
passenger: 'false',
|
||||
seatsProposed: 3,
|
||||
seatsRequested: 0,
|
||||
seatsRequested: 1,
|
||||
strict: 'false',
|
||||
};
|
||||
const punctualAd = {
|
||||
|
@ -231,19 +227,7 @@ describe('Ad Repository', () => {
|
|||
],
|
||||
};
|
||||
|
||||
const defaultAdProps: DefaultAdProps = {
|
||||
driver: false,
|
||||
passenger: true,
|
||||
marginDuration: 900,
|
||||
seatsProposed: 3,
|
||||
seatsRequested: 1,
|
||||
strict: false,
|
||||
};
|
||||
|
||||
const adToCreate: AdEntity = AdEntity.create(
|
||||
createAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
const adToCreate: AdEntity = AdEntity.create(createAdProps);
|
||||
await adRepository.insert(adToCreate);
|
||||
|
||||
const afterCount = await prismaService.ad.count();
|
||||
|
@ -319,19 +303,7 @@ describe('Ad Repository', () => {
|
|||
],
|
||||
};
|
||||
|
||||
const defaultAdProps: DefaultAdProps = {
|
||||
driver: false,
|
||||
passenger: true,
|
||||
marginDuration: 900,
|
||||
seatsProposed: 3,
|
||||
seatsRequested: 1,
|
||||
strict: false,
|
||||
};
|
||||
|
||||
const adToCreate: AdEntity = AdEntity.create(
|
||||
createAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
const adToCreate: AdEntity = AdEntity.create(createAdProps);
|
||||
await adRepository.insert(adToCreate);
|
||||
|
||||
const afterCount = await prismaService.ad.count();
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||
import {
|
||||
CreateAdProps,
|
||||
DefaultAdProps,
|
||||
Frequency,
|
||||
} from '@modules/ad/core/domain/ad.types';
|
||||
import { CreateAdProps, Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
import { WaypointProps } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
|
||||
|
||||
const originWaypointProps: WaypointProps = {
|
||||
|
@ -47,6 +43,7 @@ const punctualCreateAdProps = {
|
|||
{
|
||||
day: 3,
|
||||
time: '08:30',
|
||||
margin: 600,
|
||||
},
|
||||
],
|
||||
frequency: Frequency.PUNCTUAL,
|
||||
|
@ -119,21 +116,12 @@ const recurrentDriverPassengerCreateAdProps: CreateAdProps = {
|
|||
driver: true,
|
||||
passenger: true,
|
||||
};
|
||||
const defaultAdProps: DefaultAdProps = {
|
||||
driver: false,
|
||||
passenger: true,
|
||||
marginDuration: 900,
|
||||
seatsProposed: 3,
|
||||
seatsRequested: 1,
|
||||
strict: false,
|
||||
};
|
||||
|
||||
describe('Ad entity create', () => {
|
||||
describe('With complete props', () => {
|
||||
it('should create a new punctual passenger ad entity', async () => {
|
||||
const punctualPassengerAd: AdEntity = AdEntity.create(
|
||||
punctualPassengerCreateAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
expect(punctualPassengerAd.id.length).toBe(36);
|
||||
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 () => {
|
||||
const punctualDriverAd: AdEntity = AdEntity.create(
|
||||
punctualDriverCreateAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
expect(punctualDriverAd.id.length).toBe(36);
|
||||
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 () => {
|
||||
const punctualDriverPassengerAd: AdEntity = AdEntity.create(
|
||||
punctualDriverPassengerCreateAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
expect(punctualDriverPassengerAd.id.length).toBe(36);
|
||||
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 () => {
|
||||
const recurrentPassengerAd: AdEntity = AdEntity.create(
|
||||
recurrentPassengerCreateAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
expect(recurrentPassengerAd.id.length).toBe(36);
|
||||
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 () => {
|
||||
const recurrentDriverAd: AdEntity = AdEntity.create(
|
||||
recurrentDriverCreateAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
expect(recurrentDriverAd.id.length).toBe(36);
|
||||
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 () => {
|
||||
const recurrentDriverPassengerAd: AdEntity = AdEntity.create(
|
||||
recurrentDriverPassengerCreateAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
expect(recurrentDriverPassengerAd.id.length).toBe(36);
|
||||
expect(recurrentDriverPassengerAd.getProps().schedule.length).toBe(5);
|
||||
|
@ -207,177 +190,4 @@ describe('Ad entity create', () => {
|
|||
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 {
|
||||
AD_REPOSITORY,
|
||||
INPUT_DATETIME_TRANSFORMER,
|
||||
PARAMS_PROVIDER,
|
||||
} from '@modules/ad/ad.di-tokens';
|
||||
import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.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 { ConflictException } from '@mobicoop/ddd-library';
|
||||
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 { CreateAdCommand } from '@modules/ad/core/application/commands/create-ad/create-ad.command';
|
||||
import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors';
|
||||
|
@ -41,11 +39,15 @@ const punctualCreateAdRequest: CreateAdRequestDto = {
|
|||
schedule: [
|
||||
{
|
||||
time: '08:15',
|
||||
margin: 900,
|
||||
day: 4,
|
||||
},
|
||||
],
|
||||
driver: true,
|
||||
passenger: true,
|
||||
seatsRequested: 1,
|
||||
seatsProposed: 3,
|
||||
strict: false,
|
||||
frequency: Frequency.PUNCTUAL,
|
||||
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 = {
|
||||
fromDate: jest.fn(),
|
||||
toDate: jest.fn(),
|
||||
|
@ -93,10 +81,6 @@ describe('create-ad.service', () => {
|
|||
provide: AD_REPOSITORY,
|
||||
useValue: mockAdRepository,
|
||||
},
|
||||
{
|
||||
provide: PARAMS_PROVIDER,
|
||||
useValue: mockDefaultParamsProvider,
|
||||
},
|
||||
{
|
||||
provide: INPUT_DATETIME_TRANSFORMER,
|
||||
useValue: mockInputDateTimeTransformer,
|
||||
|
@ -118,9 +102,8 @@ describe('create-ad.service', () => {
|
|||
AdEntity.create = jest.fn().mockReturnValue({
|
||||
id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
|
||||
});
|
||||
const result: AggregateID = await createAdService.execute(
|
||||
createAdCommand,
|
||||
);
|
||||
const result: AggregateID =
|
||||
await createAdService.execute(createAdCommand);
|
||||
expect(result).toBe('047a6ecf-23d4-4d3e-877c-3225d560a8da');
|
||||
});
|
||||
it('should throw an error if something bad happens', async () => {
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
|
||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||
import {
|
||||
CreateAdProps,
|
||||
DefaultAdProps,
|
||||
Frequency,
|
||||
} from '@modules/ad/core/domain/ad.types';
|
||||
import { CreateAdProps, 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 { 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';
|
||||
|
@ -60,19 +56,7 @@ const punctualPassengerCreateAdProps: CreateAdProps = {
|
|||
passenger: true,
|
||||
};
|
||||
|
||||
const defaultAdProps: DefaultAdProps = {
|
||||
marginDuration: 900,
|
||||
driver: false,
|
||||
passenger: true,
|
||||
seatsProposed: 3,
|
||||
seatsRequested: 1,
|
||||
strict: false,
|
||||
};
|
||||
|
||||
const ad: AdEntity = AdEntity.create(
|
||||
punctualPassengerCreateAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
const ad: AdEntity = AdEntity.create(punctualPassengerCreateAdProps);
|
||||
|
||||
const mockAdRepository = {
|
||||
findOneById: jest.fn().mockImplementation(() => ad),
|
||||
|
@ -106,9 +90,8 @@ describe('find-ad-by-id.query-handler', () => {
|
|||
const findAdbyIdQuery = new FindAdByIdQuery(
|
||||
'dd264806-13b4-4226-9b18-87adf0ad5dd1',
|
||||
);
|
||||
const ad: AdEntity = await findAdByIdQueryHandler.execute(
|
||||
findAdbyIdQuery,
|
||||
);
|
||||
const ad: AdEntity =
|
||||
await findAdByIdQueryHandler.execute(findAdbyIdQuery);
|
||||
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 {
|
||||
PARAMS_PROVIDER,
|
||||
TIMEZONE_FINDER,
|
||||
TIME_CONVERTER,
|
||||
} from '@modules/ad/ad.di-tokens';
|
||||
import { TIMEZONE_FINDER, TIME_CONVERTER } from '@modules/ad/ad.di-tokens';
|
||||
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 { TimezoneFinderPort } from '@modules/ad/core/application/ports/timezone-finder.port';
|
||||
import { InputDateTimeTransformer } from '@modules/ad/infrastructure/input-datetime-transformer';
|
||||
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 = {
|
||||
timezones: jest.fn().mockImplementation(() => ['Europe/Paris']),
|
||||
};
|
||||
|
@ -56,10 +37,6 @@ describe('Input Datetime Transformer', () => {
|
|||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
{
|
||||
provide: PARAMS_PROVIDER,
|
||||
useValue: mockDefaultParamsProvider,
|
||||
},
|
||||
{
|
||||
provide: TIMEZONE_FINDER,
|
||||
useValue: mockTimezoneFinder,
|
||||
|
|
|
@ -1,29 +1,10 @@
|
|||
import {
|
||||
PARAMS_PROVIDER,
|
||||
TIMEZONE_FINDER,
|
||||
TIME_CONVERTER,
|
||||
} from '@modules/ad/ad.di-tokens';
|
||||
import { TIMEZONE_FINDER, TIME_CONVERTER } from '@modules/ad/ad.di-tokens';
|
||||
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 { TimezoneFinderPort } from '@modules/ad/core/application/ports/timezone-finder.port';
|
||||
import { OutputDateTimeTransformer } from '@modules/ad/infrastructure/output-datetime-transformer';
|
||||
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 = {
|
||||
timezones: jest.fn().mockImplementation(() => ['Europe/Paris']),
|
||||
};
|
||||
|
@ -56,10 +37,6 @@ describe('Output Datetime Transformer', () => {
|
|||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
{
|
||||
provide: PARAMS_PROVIDER,
|
||||
useValue: mockDefaultParamsProvider,
|
||||
},
|
||||
{
|
||||
provide: TIMEZONE_FINDER,
|
||||
useValue: mockTimezoneFinder,
|
||||
|
|
|
@ -34,11 +34,15 @@ const punctualCreateAdRequest: CreateAdRequestDto = {
|
|||
schedule: [
|
||||
{
|
||||
time: '08:15',
|
||||
day: 4,
|
||||
margin: 600,
|
||||
},
|
||||
],
|
||||
driver: false,
|
||||
passenger: true,
|
||||
seatsRequested: 1,
|
||||
seatsProposed: 3,
|
||||
strict: false,
|
||||
frequency: Frequency.PUNCTUAL,
|
||||
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';
|
||||
|
||||
describe('addresses position validator', () => {
|
||||
const mockAddress1: WaypointDto = {
|
||||
const waypoint1: WaypointDto = {
|
||||
position: 0,
|
||||
lat: 48.689445,
|
||||
lon: 6.17651,
|
||||
houseNumber: '5',
|
||||
|
@ -11,14 +12,16 @@ describe('addresses position validator', () => {
|
|||
postalCode: '54000',
|
||||
country: 'France',
|
||||
};
|
||||
const mockAddress2: WaypointDto = {
|
||||
const waypoint2: WaypointDto = {
|
||||
position: 1,
|
||||
lat: 48.8566,
|
||||
lon: 2.3522,
|
||||
locality: 'Paris',
|
||||
postalCode: '75000',
|
||||
country: 'France',
|
||||
};
|
||||
const mockAddress3: WaypointDto = {
|
||||
const waypoint3: WaypointDto = {
|
||||
position: 2,
|
||||
lon: 49.2628,
|
||||
lat: 4.0347,
|
||||
locality: 'Reims',
|
||||
|
@ -26,50 +29,26 @@ describe('addresses position validator', () => {
|
|||
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', () => {
|
||||
mockAddress1.position = 0;
|
||||
mockAddress2.position = 1;
|
||||
mockAddress3.position = 1;
|
||||
expect(
|
||||
hasValidPositionIndexes([mockAddress1, mockAddress2, mockAddress3]),
|
||||
hasValidPositionIndexes([waypoint1, waypoint1, waypoint2]),
|
||||
).toBeFalsy();
|
||||
});
|
||||
it('should validate if all positions are ordered', () => {
|
||||
mockAddress1.position = 0;
|
||||
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 positions are not consecutives', () => {
|
||||
expect(hasValidPositionIndexes([waypoint1, waypoint3])).toBeFalsy();
|
||||
});
|
||||
it('should not validate if no waypoints are defined', () => {
|
||||
expect(hasValidPositionIndexes(undefined)).toBeFalsy();
|
||||
it('should not validate if waypoints are empty', () => {
|
||||
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 { MESSAGE_PUBLISHER } from './messager.di-tokens';
|
||||
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { SERVICE_NAME } from '@src/app.constants';
|
||||
import {
|
||||
MessageBrokerModule,
|
||||
MessageBrokerModuleOptions,
|
||||
MessageBrokerPublisher,
|
||||
} from '@mobicoop/message-broker-module';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { SERVICE_NAME } from '@src/app.constants';
|
||||
|
||||
const imports = [
|
||||
MessageBrokerModule.forRootAsync({
|
||||
|
@ -15,8 +16,13 @@ const imports = [
|
|||
useFactory: async (
|
||||
configService: ConfigService,
|
||||
): Promise<MessageBrokerModuleOptions> => ({
|
||||
uri: configService.get<string>('MESSAGE_BROKER_URI'),
|
||||
exchange: configService.get<string>('MESSAGE_BROKER_EXCHANGE'),
|
||||
uri: configService.get<string>('MESSAGE_BROKER_URI') as string,
|
||||
exchange: {
|
||||
name: configService.get<string>('MESSAGE_BROKER_EXCHANGE') as string,
|
||||
durable: configService.get<boolean>(
|
||||
'MESSAGE_BROKER_EXCHANGE_DURABILITY',
|
||||
) as boolean,
|
||||
},
|
||||
name: SERVICE_NAME,
|
||||
}),
|
||||
}),
|
||||
|
|
|
@ -12,10 +12,10 @@
|
|||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": false,
|
||||
"noImplicitAny": false,
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"strictBindCallApply": false,
|
||||
"forceConsistentCasingInFileNames": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": false,
|
||||
"paths": {
|
||||
"@libs/*": ["src/libs/*"],
|
||||
|
|
Loading…
Reference in New Issue