diff --git a/README.md b/README.md index 92c67cb..c683b97 100644 --- a/README.md +++ b/README.md @@ -48,30 +48,34 @@ npm run migrate The app exposes the following [gRPC](https://grpc.io/) services : -- **FindByUuid** : find an ad by its uuid +- **FindById** : find an ad by its id ```json { - "uuid": "80126a61-d128-4f96-afdb-92e33c75a3e1" + "id": "80126a61-d128-4f96-afdb-92e33c75a3e1" } ``` -- **Create** : create an ad (note that uuid is optional, a uuid will be automatically attributed if it is not provided) +- **Create** : create an ad (note that id is optional, an id (as a uuid) will be automatically attributed if it is not provided) Punctual driver ad : ```json { - "userUuid": "80c9bb02-0931-4a1d-bea6-22d358992245", + "userId": "80c9bb02-0931-4a1d-bea6-22d358992245", "driver": true, - "seatsDriver": 3, + "seatsProposed": 3, "frequency": "PUNCTUAL", - "departure": "2023-01-15 09:00", - "addresses": [ + "fromDate": "2023-01-15", + "toDate": "2023-01-15", + "schedule": { + "thu": "09:00" + }, + "waypoints": [ { "position": 0, - "lon": 48.68944505415954, - "lat": 6.176510296462267, + "lon": 48.689445, + "lat": 6.17651, "houseNumber": "5", "street": "Avenue Foch", "locality": "Nancy", @@ -94,14 +98,18 @@ The app exposes the following [gRPC](https://grpc.io/) services : ```json { - "userUuid": "80c9bb02-0931-4a1d-bea6-22d358992245", + "userId": "80c9bb02-0931-4a1d-bea6-22d358992245", "driver": true, "pasenger": true, - "seatsDriver": 3, - "seatsPassenger": 1, + "seatsProposed": 3, + "seatsRequested": 1, "frequency": "PUNCTUAL", - "departure": "2023-01-15 09:00", - "addresses": [ + "fromDate": "2023-01-15", + "toDate": "2023-01-15", + "schedule": { + "thu": "09:00" + }, + "waypoints": [ { "position": 0, "lon": 48.68944505415954, @@ -139,11 +147,11 @@ The app exposes the following [gRPC](https://grpc.io/) services : "tue": "07:05", "fri": "07:10" }, - "addresses": [ + "waypoints": [ { "position": 0, - "lon": 48.68944505415954, - "lat": 6.176510296462267, + "lon": 48.689445, + "lat": 6.17651, "houseNumber": "5", "street": "Avenue Foch", "locality": "Nancy", @@ -164,15 +172,14 @@ The app exposes the following [gRPC](https://grpc.io/) services : The list of possible options when creating an ad : - - uuid (optional): the uuid of the ad - - userUuid: the user uuid + - id (optional): the id of the ad (as a uuid) + - 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 - frequency: `PUNCTUAL` or `RECURRENT` - - departure (required if punctual): departure date and hour/minute for a punctual ad - - fromDate (required if recurrent): start date for recurrent ad - - toDate (required if recurrent): end date for recurrent ad - - schedule (required if recurrent): an object with the departure time for each carpooled day in the week + - 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 object with the departure time for each carpooled day in the week - marginDurations (optional): an object with the margin duration (in seconds) for each carpooled day in the week, eg: { @@ -181,10 +188,10 @@ The app exposes the following [gRPC](https://grpc.io/) services : "fri": 950 } - - seatsDriver (optional): number of seats proposed as driver; - - seatsPassenger (optional): number of seats requested as passenger; + - 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 - - addresses: an array of adresses that represent the waypoints of the journey (only first and last waypoints are used for passenger 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 **must** be consecutives Default values must be set in `.env` file. diff --git a/src/app.module.ts b/src/app.module.ts index f11aeaf..ebb535c 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -2,8 +2,6 @@ import { Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; // import { HealthModule } from './modules/health/health.module'; import { AdModule } from './modules/ad/ad.module'; -import { AutomapperModule } from '@automapper/nestjs'; -import { classes } from '@automapper/classes'; import { MessageBrokerModule, MessageBrokerModuleOptions, @@ -21,7 +19,6 @@ import { HealthModule } from '@modules/health/health.module'; ConfigModule.forRoot({ isGlobal: true }), EventEmitterModule.forRoot(), RequestContextModule, - AutomapperModule.forRoot({ strategyInitializer: classes() }), MessageBrokerModule.forRootAsync({ imports: [ConfigModule], inject: [ConfigService], diff --git a/src/modules/ad/interface/grpc-controllers/dtos/address.dto.ts b/src/modules/ad/interface/grpc-controllers/dtos/address.dto.ts index cdd5384..9659d96 100644 --- a/src/modules/ad/interface/grpc-controllers/dtos/address.dto.ts +++ b/src/modules/ad/interface/grpc-controllers/dtos/address.dto.ts @@ -1,33 +1,26 @@ -import { AutoMap } from '@automapper/classes'; import { IsOptional, IsString } from 'class-validator'; import { CoordinatesDto as CoordinatesDto } from './coordinates.dto'; export class AddressDto extends CoordinatesDto { @IsOptional() - @AutoMap() name?: string; @IsOptional() @IsString() - @AutoMap() houseNumber?: string; @IsOptional() @IsString() - @AutoMap() street?: string; @IsOptional() @IsString() - @AutoMap() locality?: string; @IsOptional() @IsString() - @AutoMap() postalCode?: string; @IsString() - @AutoMap() country: string; } diff --git a/src/modules/ad/interface/grpc-controllers/dtos/coordinates.dto.ts b/src/modules/ad/interface/grpc-controllers/dtos/coordinates.dto.ts index 99f5783..3e622be 100644 --- a/src/modules/ad/interface/grpc-controllers/dtos/coordinates.dto.ts +++ b/src/modules/ad/interface/grpc-controllers/dtos/coordinates.dto.ts @@ -1,4 +1,3 @@ -import { AutoMap } from '@automapper/classes'; import { Transform } from 'class-transformer'; import { IsLatitude, IsLongitude } from 'class-validator'; import { toPrecision } from './transformers/to-precision'; @@ -8,13 +7,11 @@ export class CoordinatesDto { toClassOnly: true, }) @IsLongitude() - @AutoMap() lon: number; @Transform(({ value }) => toPrecision(value, 6), { toClassOnly: true, }) @IsLatitude() - @AutoMap() lat: number; } diff --git a/src/modules/ad/interface/grpc-controllers/dtos/create-ad.request.dto.ts b/src/modules/ad/interface/grpc-controllers/dtos/create-ad.request.dto.ts index f7f31b5..6d07d97 100644 --- a/src/modules/ad/interface/grpc-controllers/dtos/create-ad.request.dto.ts +++ b/src/modules/ad/interface/grpc-controllers/dtos/create-ad.request.dto.ts @@ -1,4 +1,3 @@ -import { AutoMap } from '@automapper/classes'; import { IsOptional, IsBoolean, @@ -21,65 +20,54 @@ import { Frequency } from '@modules/ad/core/ad.types'; export class CreateAdRequestDto { @IsUUID(4) - @AutoMap() userId: string; @IsOptional() @IsBoolean() - @AutoMap() driver?: boolean; @IsOptional() @IsBoolean() - @AutoMap() passenger?: boolean; @Transform(({ value }) => intToFrequency(value), { toClassOnly: true, }) @IsEnum(Frequency) - @AutoMap() frequency: Frequency; @IsISO8601({ strict: true, strictSeparator: true, }) - @AutoMap() fromDate: string; @IsISO8601({ strict: true, strictSeparator: true, }) - @AutoMap() toDate: string; @Type(() => ScheduleDto) @IsSchedule() @ValidateNested({ each: true }) - @AutoMap() schedule: ScheduleDto; @IsOptional() @Type(() => MarginDurationsDto) @ValidateNested({ each: true }) - @AutoMap() marginDurations?: MarginDurationsDto; @IsOptional() @IsInt() - @AutoMap() seatsProposed?: number; @IsOptional() @IsInt() - @AutoMap() seatsRequested?: number; @IsOptional() @IsBoolean() - @AutoMap() strict?: boolean; @Type(() => WaypointDto) @@ -87,6 +75,5 @@ export class CreateAdRequestDto { @ArrayMinSize(2) @HasValidPositionIndexes() @ValidateNested({ each: true }) - @AutoMap() waypoints: WaypointDto[]; } diff --git a/src/modules/ad/interface/grpc-controllers/dtos/margin-durations.dto.ts b/src/modules/ad/interface/grpc-controllers/dtos/margin-durations.dto.ts index 564f23d..5637707 100644 --- a/src/modules/ad/interface/grpc-controllers/dtos/margin-durations.dto.ts +++ b/src/modules/ad/interface/grpc-controllers/dtos/margin-durations.dto.ts @@ -1,39 +1,31 @@ -import { AutoMap } from '@automapper/classes'; import { IsInt, IsOptional } from 'class-validator'; export class MarginDurationsDto { @IsOptional() @IsInt() - @AutoMap() mon?: number; @IsOptional() @IsInt() - @AutoMap() tue?: number; @IsOptional() @IsInt() - @AutoMap() wed?: number; @IsOptional() @IsInt() - @AutoMap() thu?: number; @IsOptional() @IsInt() - @AutoMap() fri?: number; @IsOptional() @IsInt() - @AutoMap() sat?: number; @IsOptional() @IsInt() - @AutoMap() sun?: number; } diff --git a/src/modules/ad/interface/grpc-controllers/dtos/schedule.dto.ts b/src/modules/ad/interface/grpc-controllers/dtos/schedule.dto.ts index 9b9e9d0..7316a64 100644 --- a/src/modules/ad/interface/grpc-controllers/dtos/schedule.dto.ts +++ b/src/modules/ad/interface/grpc-controllers/dtos/schedule.dto.ts @@ -1,39 +1,31 @@ -import { AutoMap } from '@automapper/classes'; import { IsOptional, IsMilitaryTime } from 'class-validator'; export class ScheduleDto { @IsOptional() @IsMilitaryTime() - @AutoMap() mon?: string; @IsOptional() @IsMilitaryTime() - @AutoMap() tue?: string; @IsOptional() @IsMilitaryTime() - @AutoMap() wed?: string; @IsOptional() @IsMilitaryTime() - @AutoMap() thu?: string; @IsOptional() @IsMilitaryTime() - @AutoMap() fri?: string; @IsOptional() @IsMilitaryTime() - @AutoMap() sat?: string; @IsOptional() @IsMilitaryTime() - @AutoMap() sun?: string; } diff --git a/src/modules/ad/interface/grpc-controllers/dtos/waypoint.dto.ts b/src/modules/ad/interface/grpc-controllers/dtos/waypoint.dto.ts index 40c5b52..ded5386 100644 --- a/src/modules/ad/interface/grpc-controllers/dtos/waypoint.dto.ts +++ b/src/modules/ad/interface/grpc-controllers/dtos/waypoint.dto.ts @@ -1,10 +1,8 @@ -import { AutoMap } from '@automapper/classes'; import { IsInt, IsOptional } from 'class-validator'; import { AddressDto } from './address.dto'; export class WaypointDto extends AddressDto { @IsOptional() @IsInt() - @AutoMap() position?: number; } diff --git a/src/modules/ad/tests/unit/core/find-ad-by-id.query-handler.spec.ts b/src/modules/ad/tests/unit/core/find-ad-by-id.query-handler.spec.ts new file mode 100644 index 0000000..37f294c --- /dev/null +++ b/src/modules/ad/tests/unit/core/find-ad-by-id.query-handler.spec.ts @@ -0,0 +1,132 @@ +import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens'; +import { AdEntity } from '@modules/ad/core/ad.entity'; +import { + CreateAdProps, + DefaultAdProps, + Frequency, +} from '@modules/ad/core/ad.types'; +import { FindAdByIdQuery } from '@modules/ad/core/queries/find-ad-by-id/find-ad-by-id.query'; +import { FindAdByIdQueryHandler } from '@modules/ad/core/queries/find-ad-by-id/find-ad-by-id.query-handler'; +import { MarginDurationsProps } from '@modules/ad/core/value-objects/margin-durations.value-object'; +import { WaypointProps } from '@modules/ad/core/value-objects/waypoint.value-object'; +import { Test, TestingModule } from '@nestjs/testing'; + +const originWaypointProps: WaypointProps = { + position: 0, + address: { + houseNumber: '5', + street: 'Avenue Foch', + locality: 'Nancy', + postalCode: '54000', + country: 'France', + coordinates: { + lon: 48.68944505415954, + lat: 6.176510296462267, + }, + }, +}; +const destinationWaypointProps: WaypointProps = { + position: 1, + address: { + locality: 'Paris', + postalCode: '75000', + country: 'France', + coordinates: { + lon: 48.8566, + lat: 2.3522, + }, + }, +}; +const marginDurationsProps: MarginDurationsProps = { + mon: 600, + tue: 600, + wed: 600, + thu: 600, + fri: 600, + sat: 600, + sun: 600, +}; +const baseCreateAdProps = { + userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36', + seatsProposed: 3, + seatsRequested: 1, + strict: false, + waypoints: [originWaypointProps, destinationWaypointProps], +}; +const punctualCreateAdProps = { + fromDate: '2023-06-22', + toDate: '2023-06-22', + schedule: { + wed: '08:30', + }, + frequency: Frequency.PUNCTUAL, +}; +const punctualPassengerCreateAdProps: CreateAdProps = { + ...baseCreateAdProps, + ...punctualCreateAdProps, + marginDurations: marginDurationsProps, + driver: false, + passenger: true, +}; + +const defaultAdProps: DefaultAdProps = { + driver: false, + passenger: true, + marginDurations: { + mon: 900, + tue: 900, + wed: 900, + thu: 900, + fri: 900, + sat: 900, + sun: 900, + }, + seatsProposed: 3, + seatsRequested: 1, + strict: false, +}; + +const ad: AdEntity = AdEntity.create( + punctualPassengerCreateAdProps, + defaultAdProps, +); + +const mockAdRepository = { + findOneById: jest.fn().mockImplementation(() => ad), +}; + +describe('find-ad-by-id.query-handler', () => { + let findAdByIdQueryHandler: FindAdByIdQueryHandler; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + { + provide: AD_REPOSITORY, + useValue: mockAdRepository, + }, + FindAdByIdQueryHandler, + ], + }).compile(); + + findAdByIdQueryHandler = module.get( + FindAdByIdQueryHandler, + ); + }); + + it('should be defined', () => { + expect(findAdByIdQueryHandler).toBeDefined(); + }); + + describe('execution', () => { + it('should return an ad', async () => { + const findAdbyIdQuery = new FindAdByIdQuery( + 'dd264806-13b4-4226-9b18-87adf0ad5dd1', + ); + const ad: AdEntity = await findAdByIdQueryHandler.execute( + findAdbyIdQuery, + ); + expect(ad.getProps().fromDate).toBe('2023-06-22'); + }); + }); +});