diff --git a/src/modules/ad/domain/dtos/create-ad.request.ts b/src/modules/ad/domain/dtos/create-ad.request.ts index 672d698..8284cac 100644 --- a/src/modules/ad/domain/dtos/create-ad.request.ts +++ b/src/modules/ad/domain/dtos/create-ad.request.ts @@ -1,7 +1,6 @@ import { AutoMap } from '@automapper/classes'; import { IsOptional, - IsString, IsBoolean, IsDate, IsInt, @@ -20,6 +19,7 @@ import { ScheduleDTO } from './create.schedule.dto'; import { AddressDTO } from './create.address.dto'; import { HasProperPassengerSeats } from './utils/has-passenger-seats.validator'; import { HasProperDriverSeats } from './utils/has-driver-seats.validator'; +import { HasProperPositionIndexes } from './utils/address-position.validator'; export class CreateAdRequest { @IsOptional() @@ -27,6 +27,7 @@ export class CreateAdRequest { @AutoMap() uuid?: string; + // TODO create validation rule to verify is user exists ?? @IsUUID(4) @AutoMap() userUuid: string; @@ -97,6 +98,8 @@ export class CreateAdRequest { strict?: boolean; @ArrayMinSize(2) + @Type(() => AddressDTO) + @HasProperPositionIndexes() @ValidateNested({ each: true }) @AutoMap(() => [AddressDTO]) addresses: AddressDTO[]; diff --git a/src/modules/ad/domain/dtos/create.address.dto.ts b/src/modules/ad/domain/dtos/create.address.dto.ts index 18872f7..17a7b79 100644 --- a/src/modules/ad/domain/dtos/create.address.dto.ts +++ b/src/modules/ad/domain/dtos/create.address.dto.ts @@ -19,9 +19,10 @@ export class AddressDTO { @AutoMap() adUuid?: string; + @IsOptional() @IsInt() @AutoMap() - position: number; + position?: number; @IsLongitude() @AutoMap() diff --git a/src/modules/ad/domain/dtos/utils/address-position.validator.ts b/src/modules/ad/domain/dtos/utils/address-position.validator.ts new file mode 100644 index 0000000..1452f0f --- /dev/null +++ b/src/modules/ad/domain/dtos/utils/address-position.validator.ts @@ -0,0 +1,35 @@ +import { ValidateBy, ValidationOptions, buildMessage } from 'class-validator'; +import { AddressDTO } from '../create.address.dto'; + +export function hasProperPositionIndexes(value: AddressDTO[]) { + if (value.every((address) => address.position === undefined)) return true; + else if (value.every((address) => typeof address.position === 'number')) { + value.sort((a, b) => a.position - b.position); + for (let i = 1; i < value.length; i++) { + if (value[i - 1].position >= value[i].position) return false; + } + return true; + } + return false; +} + +export function HasProperPositionIndexes( + validationOptions?: ValidationOptions, +): PropertyDecorator { + return ValidateBy( + { + name: '', + constraints: [], + validator: { + validate: (value: AddressDTO[]): boolean => + hasProperPositionIndexes(value), + defaultMessage: buildMessage( + () => + `indexes position incorrect, please provide a complete list of indexes or ordened list of adresses from start to end of journey`, + validationOptions, + ), + }, + }, + validationOptions, + ); +} diff --git a/src/modules/ad/domain/entities/ad.ts b/src/modules/ad/domain/entities/ad.ts index 1d3f04e..865070d 100644 --- a/src/modules/ad/domain/entities/ad.ts +++ b/src/modules/ad/domain/entities/ad.ts @@ -8,17 +8,16 @@ import { IsEnum, ValidateNested, ArrayMinSize, + IsUUID, } from 'class-validator'; import { Address } from '../entities/address'; import { Frequency } from '../types/frequency.enum'; -import { Type } from 'class-transformer'; - export class Ad { - @IsString() + @IsUUID(4) @AutoMap() uuid: string; - @IsString() + @IsUUID(4) @AutoMap() userUuid: string; diff --git a/src/modules/ad/tests/unit/has-proper-addresses-indexes.spec.ts b/src/modules/ad/tests/unit/has-proper-addresses-indexes.spec.ts new file mode 100644 index 0000000..9010706 --- /dev/null +++ b/src/modules/ad/tests/unit/has-proper-addresses-indexes.spec.ts @@ -0,0 +1,70 @@ +import { AddressDTO } from '../../domain/dtos/create.address.dto'; +import { hasProperPositionIndexes } from '../../domain/dtos/utils/address-position.validator'; +describe('addresses position validators', () => { + const mockAddress1: AddressDTO = { + lon: 48.68944505415954, + lat: 6.176510296462267, + houseNumber: '5', + street: 'Avenue Foch', + locality: 'Nancy', + postalCode: '54000', + country: 'France', + }; + const mockAddress2: AddressDTO = { + lon: 48.8566, + lat: 2.3522, + locality: 'Paris', + postalCode: '75000', + country: 'France', + }; + + const mockAddress3: AddressDTO = { + lon: 49.2628, + lat: 4.0347, + locality: 'Reims', + postalCode: '51454', + country: 'France', + }; + it('should validate if none of position is definded ', () => { + expect( + hasProperPositionIndexes([mockAddress1, mockAddress2, mockAddress3]), + ).toBeTruthy(); + }); + it('should throw an error if position are partialy defined ', () => { + mockAddress1.position = 0; + expect( + hasProperPositionIndexes([mockAddress1, mockAddress2, mockAddress3]), + ).toBeFalsy(); + }); + it('should throw an error if position are partialy defined ', () => { + mockAddress1.position = 0; + mockAddress2.position = null; + mockAddress3.position = undefined; + expect( + hasProperPositionIndexes([mockAddress1, mockAddress2, mockAddress3]), + ).toBeFalsy(); + }); + + it('should throw an error if positions are not incremented ', () => { + mockAddress1.position = 0; + mockAddress2.position = 1; + mockAddress3.position = 1; + expect( + hasProperPositionIndexes([mockAddress1, mockAddress2, mockAddress3]), + ).toBeFalsy(); + }); + it('should validate if all positions are defined and incremented', () => { + mockAddress1.position = 0; + mockAddress2.position = 1; + mockAddress3.position = 2; + expect( + hasProperPositionIndexes([mockAddress1, mockAddress2, mockAddress3]), + ).toBeTruthy(); + mockAddress1.position = 10; + mockAddress2.position = 0; + mockAddress3.position = 3; + expect( + hasProperPositionIndexes([mockAddress1, mockAddress2, mockAddress3]), + ).toBeTruthy(); + }); +});