From da96f52c1e292079f4066ea7e40ba595fbffbcf6 Mon Sep 17 00:00:00 2001 From: sbriat Date: Thu, 11 May 2023 17:47:55 +0200 Subject: [PATCH] wip --- .../migrations/20230425130853_/migration.sql | 65 --- prisma/migrations/migration_lock.toml | 3 - prisma/schema.prisma | 35 +- src/modules/ad/ad.module.ts | 8 + .../primaries/ad-messager.controller.ts | 26 +- .../ad/adapters/secondaries/ad.repository.ts | 5 +- .../secondaries/default-params.provider.ts | 16 + .../ad/adapters/secondaries/messager.ts | 4 +- src/modules/ad/commands/create-ad.command.ts | 47 +- .../ad/domain/dtos/create-ad.request.ts | 86 ++-- .../ad/domain/entities/ad.completer.ts | 7 + src/modules/ad/domain/entities/ad.ts | 21 +- src/modules/ad/domain/entities/geography.ts | 98 ++++ .../ad/domain/entities/time-converter.ts | 12 +- .../ad/domain/types/default-params.type.ts | 5 + src/modules/ad/domain/types/frequency.enum.ts | 4 +- src/modules/ad/domain/types/role.enum.ts | 4 + .../ad/domain/usecases/create-ad.usecase.ts | 10 + src/modules/ad/mappers/ad.profile.ts | 34 -- .../default-params.provider.spec.ts | 38 ++ .../ad/tests/unit/domain/ad.completer.spec.ts | 42 ++ .../unit/domain/create-ad.usecase.spec.ts | 30 +- .../tests/unit/domain/time-converter.spec.ts | 22 +- .../secondaries/prisma-repository.abstract.ts | 26 +- .../tests/unit/prisma-repository.spec.ts | 2 +- .../adapters/secondaries/georouter-creator.ts | 28 ++ .../secondaries/graphhopper-georouter.ts | 324 +++++++++++++ .../domain/entities}/route.ts | 14 +- .../domain/entities/spacetime-point.ts | 13 + .../interfaces/georouter-creator.interface.ts | 5 + .../domain/interfaces/georouter.interface.ts | 7 + .../domain/types/georouter-settings.type.ts | 5 + .../geography/domain/types/named-route.ts | 6 + .../geography/domain/types/path.type.ts | 6 + .../geography/domain/types/timezoner.ts | 6 + .../exceptions/geography.exception.ts | 13 + .../tests/unit/georouter-creator.spec.ts | 47 ++ .../tests/unit/graphhopper-georouter.spec.ts | 456 ++++++++++++++++++ .../geography/tests/unit/route.spec.ts | 48 ++ .../adapters/primaries/matcher.controller.ts | 4 +- .../matcher/adapters/primaries/matcher.proto | 51 +- .../secondaries/default-params.provider.ts | 22 +- .../secondaries/graphhopper-georouter.ts | 6 +- .../entities/ecosystem/algorithm-settings.ts | 22 +- .../domain/entities/ecosystem/geography.ts | 6 +- .../entities/ecosystem/matcher-route.ts | 16 + .../domain/entities/ecosystem/named-route.ts | 4 +- .../types/default-algorithm-settings.type.ts | 22 +- .../matcher/domain/usecases/match.usecase.ts | 14 +- src/modules/matcher/matcher.module.ts | 2 + .../unit/domain/ecosystem/geography.spec.ts | 12 +- .../{route.spec.ts => matcher-route.spec.ts} | 12 +- .../engine/algorithm-factory-creator.spec.ts | 22 +- .../engine/algorithm-factory.abstract.spec.ts | 22 +- .../engine/classic-algorithm-factory.spec.ts | 22 +- .../classic-geo.filter.processor.spec.ts | 22 +- .../classic-time.filter.processor.spec.ts | 22 +- ...assic-waypoint.completer.processor.spec.ts | 22 +- .../domain/engine/classic.selector.spec.ts | 22 +- .../domain/engine/completer.abstract.spec.ts | 22 +- .../domain/engine/filter.abstract.spec.ts | 22 +- .../journey.completer.processor.spec.ts | 22 +- .../tests/unit/domain/engine/matcher.spec.ts | 22 +- .../domain/engine/processor.abstract.spec.ts | 22 +- .../engine/route.completer.processor.spec.ts | 22 +- .../domain/engine/selector.abstract.spec.ts | 22 +- .../tests/unit/domain/match.usecase.spec.ts | 22 +- .../tests/unit/queries/match.query.spec.ts | 22 +- src/modules/utils/exception-code.enum.ts | 19 + 69 files changed, 1700 insertions(+), 492 deletions(-) delete mode 100644 prisma/migrations/20230425130853_/migration.sql delete mode 100644 prisma/migrations/migration_lock.toml create mode 100644 src/modules/ad/adapters/secondaries/default-params.provider.ts create mode 100644 src/modules/ad/domain/entities/ad.completer.ts create mode 100644 src/modules/ad/domain/entities/geography.ts create mode 100644 src/modules/ad/domain/types/default-params.type.ts create mode 100644 src/modules/ad/domain/types/role.enum.ts create mode 100644 src/modules/ad/tests/unit/adapters/secondaries/default-params.provider.spec.ts create mode 100644 src/modules/ad/tests/unit/domain/ad.completer.spec.ts create mode 100644 src/modules/geography/adapters/secondaries/georouter-creator.ts create mode 100644 src/modules/geography/adapters/secondaries/graphhopper-georouter.ts rename src/modules/{matcher/domain/entities/ecosystem => geography/domain/entities}/route.ts (72%) create mode 100644 src/modules/geography/domain/entities/spacetime-point.ts create mode 100644 src/modules/geography/domain/interfaces/georouter-creator.interface.ts create mode 100644 src/modules/geography/domain/interfaces/georouter.interface.ts create mode 100644 src/modules/geography/domain/types/georouter-settings.type.ts create mode 100644 src/modules/geography/domain/types/named-route.ts create mode 100644 src/modules/geography/domain/types/path.type.ts create mode 100644 src/modules/geography/domain/types/timezoner.ts create mode 100644 src/modules/geography/exceptions/geography.exception.ts create mode 100644 src/modules/geography/tests/unit/georouter-creator.spec.ts create mode 100644 src/modules/geography/tests/unit/graphhopper-georouter.spec.ts create mode 100644 src/modules/geography/tests/unit/route.spec.ts create mode 100644 src/modules/matcher/domain/entities/ecosystem/matcher-route.ts rename src/modules/matcher/tests/unit/domain/ecosystem/{route.spec.ts => matcher-route.spec.ts} (84%) create mode 100644 src/modules/utils/exception-code.enum.ts diff --git a/prisma/migrations/20230425130853_/migration.sql b/prisma/migrations/20230425130853_/migration.sql deleted file mode 100644 index a0514d2..0000000 --- a/prisma/migrations/20230425130853_/migration.sql +++ /dev/null @@ -1,65 +0,0 @@ --- CreateExtension -CREATE EXTENSION IF NOT EXISTS "postgis"; - --- Required to use postgis extension : --- set the search_path to both public (where is postgis) AND the current schema -SET search_path TO matcher, public; - --- CreateTable -CREATE TABLE "ad" ( - "uuid" UUID NOT NULL, - "driver" BOOLEAN NOT NULL, - "passenger" BOOLEAN NOT NULL, - "frequency" INTEGER NOT NULL, - "fromDate" DATE NOT NULL, - "toDate" DATE NOT NULL, - "monTime" TIMESTAMPTZ NOT NULL, - "tueTime" TIMESTAMPTZ NOT NULL, - "wedTime" TIMESTAMPTZ NOT NULL, - "thuTime" TIMESTAMPTZ NOT NULL, - "friTime" TIMESTAMPTZ NOT NULL, - "satTime" TIMESTAMPTZ NOT NULL, - "sunTime" TIMESTAMPTZ NOT NULL, - "monMargin" INTEGER NOT NULL, - "tueMargin" INTEGER NOT NULL, - "wedMargin" INTEGER NOT NULL, - "thuMargin" INTEGER NOT NULL, - "friMargin" INTEGER NOT NULL, - "satMargin" INTEGER NOT NULL, - "sunMargin" INTEGER NOT NULL, - "driverDuration" INTEGER NOT NULL, - "driverDistance" INTEGER NOT NULL, - "passengerDuration" INTEGER NOT NULL, - "passengerDistance" INTEGER NOT NULL, - "originType" SMALLINT NOT NULL, - "destinationType" SMALLINT NOT NULL, - "waypoints" geography(LINESTRING), - "direction" geography(LINESTRING), - "fwdAzimuth" INTEGER NOT NULL, - "backAzimuth" INTEGER NOT NULL, - "seatsDriver" SMALLINT NOT NULL, - "seatsPassenger" SMALLINT NOT NULL, - "seatsUsed" SMALLINT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "ad_pkey" PRIMARY KEY ("uuid") -); - --- CreateIndex -CREATE INDEX "ad_driver_idx" ON "ad"("driver"); - --- CreateIndex -CREATE INDEX "ad_passenger_idx" ON "ad"("passenger"); - --- CreateIndex -CREATE INDEX "ad_fromDate_idx" ON "ad"("fromDate"); - --- CreateIndex -CREATE INDEX "ad_toDate_idx" ON "ad"("toDate"); - --- CreateIndex -CREATE INDEX "ad_fwdAzimuth_idx" ON "ad"("fwdAzimuth"); - --- CreateIndex -CREATE INDEX "direction_idx" ON "ad" USING GIST ("direction"); diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml deleted file mode 100644 index fbffa92..0000000 --- a/prisma/migrations/migration_lock.toml +++ /dev/null @@ -1,3 +0,0 @@ -# Please do not edit this file manually -# It should be added in your version-control system (i.e. Git) -provider = "postgresql" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 469a604..2086694 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -13,19 +13,20 @@ datasource db { } model Ad { - uuid String @id @default(uuid()) @db.Uuid + uuid String @id @db.Uuid + userUuid String @db.Uuid driver Boolean passenger Boolean - frequency Int + frequency Frequency fromDate DateTime @db.Date toDate DateTime @db.Date - monTime DateTime @db.Timestamptz() - tueTime DateTime @db.Timestamptz() - wedTime DateTime @db.Timestamptz() - thuTime DateTime @db.Timestamptz() - friTime DateTime @db.Timestamptz() - satTime DateTime @db.Timestamptz() - sunTime DateTime @db.Timestamptz() + monTime DateTime? @db.Timestamptz() + tueTime DateTime? @db.Timestamptz() + wedTime DateTime? @db.Timestamptz() + thuTime DateTime? @db.Timestamptz() + friTime DateTime? @db.Timestamptz() + satTime DateTime? @db.Timestamptz() + sunTime DateTime? @db.Timestamptz() monMargin Int tueMargin Int wedMargin Int @@ -33,12 +34,10 @@ model Ad { friMargin Int satMargin Int sunMargin Int - driverDuration Int - driverDistance Int - passengerDuration Int - passengerDistance Int - originType Int @db.SmallInt - destinationType Int @db.SmallInt + driverDuration Int? + driverDistance Int? + passengerDuration Int? + passengerDistance Int? waypoints Unsupported("geography(LINESTRING)")? direction Unsupported("geography(LINESTRING)")? fwdAzimuth Int @@ -46,6 +45,7 @@ model Ad { seatsDriver Int @db.SmallInt seatsPassenger Int @db.SmallInt seatsUsed Int @db.SmallInt + strict Boolean createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt @@ -57,3 +57,8 @@ model Ad { @@index([direction], name: "direction_idx", type: Gist) @@map("ad") } + +enum Frequency { + PUNCTUAL + RECURRENT +} diff --git a/src/modules/ad/ad.module.ts b/src/modules/ad/ad.module.ts index 91717d5..bc23450 100644 --- a/src/modules/ad/ad.module.ts +++ b/src/modules/ad/ad.module.ts @@ -10,11 +10,17 @@ import { CqrsModule } from '@nestjs/cqrs'; import { Messager } from './adapters/secondaries/messager'; import { TimezoneFinder } from './adapters/secondaries/timezone-finder'; import { GeoTimezoneFinder } from '../geography/adapters/secondaries/geo-timezone-finder'; +import { DefaultParamsProvider } from './adapters/secondaries/default-params.provider'; +import { GeorouterCreator } from '../geography/adapters/secondaries/georouter-creator'; +import { GeographyModule } from '../geography/geography.module'; +import { HttpModule } from '@nestjs/axios'; @Module({ imports: [ + GeographyModule, DatabaseModule, CqrsModule, + HttpModule, RabbitMQModule.forRootAsync(RabbitMQModule, { imports: [ConfigModule], useFactory: async (configService: ConfigService) => ({ @@ -46,6 +52,8 @@ import { GeoTimezoneFinder } from '../geography/adapters/secondaries/geo-timezon TimezoneFinder, GeoTimezoneFinder, CreateAdUseCase, + DefaultParamsProvider, + GeorouterCreator, ], exports: [], }) diff --git a/src/modules/ad/adapters/primaries/ad-messager.controller.ts b/src/modules/ad/adapters/primaries/ad-messager.controller.ts index 4028b69..c122475 100644 --- a/src/modules/ad/adapters/primaries/ad-messager.controller.ts +++ b/src/modules/ad/adapters/primaries/ad-messager.controller.ts @@ -8,7 +8,10 @@ import { CreateAdCommand } from '../../commands/create-ad.command'; import { CreateAdRequest } from '../../domain/dtos/create-ad.request'; import { validateOrReject } from 'class-validator'; import { Messager } from '../secondaries/messager'; -import { GeoTimezoneFinder } from 'src/modules/geography/adapters/secondaries/geo-timezone-finder'; +import { GeoTimezoneFinder } from '../../../geography/adapters/secondaries/geo-timezone-finder'; +import { plainToInstance } from 'class-transformer'; +import { DefaultParamsProvider } from '../secondaries/default-params.provider'; +import { GeorouterCreator } from '../../../geography/adapters/secondaries/georouter-creator'; @Controller() export class AdMessagerController { @@ -16,6 +19,8 @@ export class AdMessagerController { private readonly messager: Messager, private readonly commandBus: CommandBus, @InjectMapper() private readonly mapper: Mapper, + private readonly defaultParamsProvider: DefaultParamsProvider, + private readonly georouterCreator: GeorouterCreator, private readonly timezoneFinder: GeoTimezoneFinder, ) {} @@ -23,15 +28,10 @@ export class AdMessagerController { name: 'adCreated', }) async adCreatedHandler(message: string): Promise { - let createAdRequest: CreateAdRequest; try { - // parse message to conform to CreateAdRequest (not a real instance yet) - const parsedMessage: CreateAdRequest = JSON.parse(message); - // create a real instance of CreateAdRequest from parsed message - createAdRequest = this.mapper.map( - parsedMessage, - CreateAdRequest, + const createAdRequest: CreateAdRequest = plainToInstance( CreateAdRequest, + JSON.parse(message), ); // validate instance await validateOrReject(createAdRequest); @@ -48,14 +48,20 @@ export class AdMessagerController { createAdRequest.waypoints[0].lat, )[0]; const ad: Ad = await this.commandBus.execute( - new CreateAdCommand(createAdRequest), + new CreateAdCommand( + createAdRequest, + this.defaultParamsProvider.getParams(), + this.georouterCreator, + this.timezoneFinder, + ), ); console.log(ad); } catch (e) { + console.log(e); this.messager.publish( 'logging.matcher.ad.crit', JSON.stringify({ - createAdRequest, + message, error: e, }), ); diff --git a/src/modules/ad/adapters/secondaries/ad.repository.ts b/src/modules/ad/adapters/secondaries/ad.repository.ts index a57910a..11aa335 100644 --- a/src/modules/ad/adapters/secondaries/ad.repository.ts +++ b/src/modules/ad/adapters/secondaries/ad.repository.ts @@ -2,10 +2,11 @@ import { Injectable } from '@nestjs/common'; import { MatcherRepository } from '../../../database/domain/matcher-repository'; import { Ad } from '../../domain/entities/ad'; import { DatabaseException } from '../../../database/exceptions/database.exception'; +import { Frequency } from '../../domain/types/frequency.enum'; @Injectable() export class AdRepository extends MatcherRepository { - protected _model = 'ad'; + protected model = 'ad'; async createAd(ad: Partial): Promise { try { @@ -44,7 +45,7 @@ type AdFields = { uuid: string; driver: string; passenger: string; - frequency: number; + frequency: Frequency; fromDate: string; toDate: string; monTime: string; diff --git a/src/modules/ad/adapters/secondaries/default-params.provider.ts b/src/modules/ad/adapters/secondaries/default-params.provider.ts new file mode 100644 index 0000000..62e45aa --- /dev/null +++ b/src/modules/ad/adapters/secondaries/default-params.provider.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { IDefaultParams } from '../../domain/types/default-params.type'; + +@Injectable() +export class DefaultParamsProvider { + constructor(private readonly configService: ConfigService) {} + + getParams = (): IDefaultParams => { + return { + DEFAULT_TIMEZONE: this.configService.get('DEFAULT_TIMEZONE'), + GEOROUTER_TYPE: this.configService.get('GEOROUTER_TYPE'), + GEOROUTER_URL: this.configService.get('GEOROUTER_URL'), + }; + }; +} diff --git a/src/modules/ad/adapters/secondaries/messager.ts b/src/modules/ad/adapters/secondaries/messager.ts index 96fa7cc..1888b7d 100644 --- a/src/modules/ad/adapters/secondaries/messager.ts +++ b/src/modules/ad/adapters/secondaries/messager.ts @@ -6,13 +6,13 @@ import { MessageBroker } from './message-broker'; @Injectable() export class Messager extends MessageBroker { constructor( - private readonly _amqpConnection: AmqpConnection, + private readonly amqpConnection: AmqpConnection, configService: ConfigService, ) { super(configService.get('RMQ_EXCHANGE')); } publish = (routingKey: string, message: string): void => { - this._amqpConnection.publish(this.exchange, routingKey, message); + this.amqpConnection.publish(this.exchange, routingKey, message); }; } diff --git a/src/modules/ad/commands/create-ad.command.ts b/src/modules/ad/commands/create-ad.command.ts index b4f1e8d..d1e1d0a 100644 --- a/src/modules/ad/commands/create-ad.command.ts +++ b/src/modules/ad/commands/create-ad.command.ts @@ -1,9 +1,54 @@ +import { IGeorouter } from '../../geography/domain/interfaces/georouter.interface'; +import { ICreateGeorouter } from '../../geography/domain/interfaces/georouter-creator.interface'; import { CreateAdRequest } from '../domain/dtos/create-ad.request'; +import { Geography } from '../domain/entities/geography'; +import { IDefaultParams } from '../domain/types/default-params.type'; +import { Role } from '../domain/types/role.enum'; +import { IFindTimezone } from '../../geography/domain/interfaces/timezone-finder.interface'; export class CreateAdCommand { readonly createAdRequest: CreateAdRequest; + private readonly defaultParams: IDefaultParams; + private readonly georouter: IGeorouter; + private readonly timezoneFinder: IFindTimezone; + roles: Role[]; + geography: Geography; + timezone: string; - constructor(request: CreateAdRequest) { + constructor( + request: CreateAdRequest, + defaultParams: IDefaultParams, + georouterCreator: ICreateGeorouter, + timezoneFinder: IFindTimezone, + ) { this.createAdRequest = request; + this.defaultParams = defaultParams; + this.georouter = georouterCreator.create( + defaultParams.GEOROUTER_TYPE, + defaultParams.GEOROUTER_URL, + ); + this.timezoneFinder = timezoneFinder; + this.setRoles(); + this.setGeography(); } + + private setRoles = (): void => { + this.roles = []; + if (this.createAdRequest.driver) this.roles.push(Role.DRIVER); + if (this.createAdRequest.passenger) this.roles.push(Role.PASSENGER); + }; + + private setGeography = async (): Promise => { + this.geography = new Geography(this.createAdRequest.waypoints, { + timezone: this.defaultParams.DEFAULT_TIMEZONE, + finder: this.timezoneFinder, + }); + if (this.geography.timezones.length > 0) + this.createAdRequest.timezone = this.geography.timezones[0]; + try { + await this.geography.createRoutes(this.roles, this.georouter); + } catch (e) { + console.log(e); + } + }; } diff --git a/src/modules/ad/domain/dtos/create-ad.request.ts b/src/modules/ad/domain/dtos/create-ad.request.ts index a540458..7dce0b1 100644 --- a/src/modules/ad/domain/dtos/create-ad.request.ts +++ b/src/modules/ad/domain/dtos/create-ad.request.ts @@ -3,15 +3,17 @@ import { ArrayMinSize, IsArray, IsBoolean, + IsDate, IsEnum, + IsMilitaryTime, IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; -import { PointType } from '../../../geography/domain/types/point-type.enum'; import { Frequency } from '../types/frequency.enum'; import { Coordinates } from '../../../geography/domain/entities/coordinates'; +import { Type } from 'class-transformer'; export class CreateAdRequest { @IsString() @@ -19,6 +21,11 @@ export class CreateAdRequest { @AutoMap() uuid: string; + @IsString() + @IsNotEmpty() + @AutoMap() + userUuid: string; + @IsBoolean() @AutoMap() driver: boolean; @@ -27,53 +34,54 @@ export class CreateAdRequest { @AutoMap() passenger: boolean; - @IsNotEmpty() @IsEnum(Frequency) @AutoMap() frequency: Frequency; - @IsString() + @Type(() => Date) + @IsDate() @AutoMap() - fromDate: string; + fromDate: Date; - @IsString() + @Type(() => Date) + @IsDate() @AutoMap() - toDate: string; + toDate: Date; @IsOptional() - @IsString() + @IsMilitaryTime() @AutoMap() - monTime?: string | null; + monTime?: string; @IsOptional() - @IsString() + @IsMilitaryTime() @AutoMap() - tueTime?: string | null; + tueTime?: string; @IsOptional() - @IsString() + @IsMilitaryTime() @AutoMap() - wedTime?: string | null; + wedTime?: string; @IsOptional() - @IsString() + @IsMilitaryTime() @AutoMap() - thuTime?: string | null; + thuTime?: string; @IsOptional() - @IsString() + @IsMilitaryTime() @AutoMap() - friTime?: string | null; + friTime?: string; @IsOptional() - @IsString() + @IsMilitaryTime() @AutoMap() - satTime?: string | null; + satTime?: string; @IsOptional() - @IsString() + @IsMilitaryTime() @AutoMap() - sunTime?: string | null; + sunTime?: string; @IsNumber() @AutoMap() @@ -103,19 +111,33 @@ export class CreateAdRequest { @AutoMap() sunMargin: number; - @IsEnum(PointType) - @AutoMap() - originType: PointType; - - @IsEnum(PointType) - @AutoMap() - destinationType: PointType; - + @Type(() => Coordinates) @IsArray() @ArrayMinSize(2) @AutoMap(() => [Coordinates]) waypoints: Coordinates[]; + @AutoMap() + driverDuration?: number; + + @AutoMap() + driverDistance?: number; + + @AutoMap() + passengerDuration?: number; + + @AutoMap() + passengerDistance?: number; + + @AutoMap() + direction: string; + + @AutoMap() + fwdAzimuth: number; + + @AutoMap() + backAzimuth: number; + @IsNumber() @AutoMap() seatsDriver: number; @@ -129,13 +151,9 @@ export class CreateAdRequest { @AutoMap() seatsUsed?: number; - @IsString() + @IsBoolean() @AutoMap() - createdAt: string; - - @IsString() - @AutoMap() - updatedAt: string; + strict: boolean; timezone?: string; } diff --git a/src/modules/ad/domain/entities/ad.completer.ts b/src/modules/ad/domain/entities/ad.completer.ts new file mode 100644 index 0000000..2074621 --- /dev/null +++ b/src/modules/ad/domain/entities/ad.completer.ts @@ -0,0 +1,7 @@ +import { Ad } from './ad'; + +export class AdCompleter { + complete = async (ad: Ad): Promise => { + return ad; + }; +} diff --git a/src/modules/ad/domain/entities/ad.ts b/src/modules/ad/domain/entities/ad.ts index 1e4d1d6..0361c2f 100644 --- a/src/modules/ad/domain/entities/ad.ts +++ b/src/modules/ad/domain/entities/ad.ts @@ -1,6 +1,6 @@ import { AutoMap } from '@automapper/classes'; -import { PointType } from '../../../geography/domain/types/point-type.enum'; import { Coordinates } from '../../../geography/domain/entities/coordinates'; +import { Frequency } from '../types/frequency.enum'; export class Ad { @AutoMap() @@ -13,7 +13,7 @@ export class Ad { passenger: boolean; @AutoMap() - frequency: number; + frequency: Frequency; @AutoMap() fromDate: Date; @@ -64,22 +64,16 @@ export class Ad { sunMargin: number; @AutoMap() - driverDuration: number; + driverDuration?: number; @AutoMap() - driverDistance: number; + driverDistance?: number; @AutoMap() - passengerDuration: number; + passengerDuration?: number; @AutoMap() - passengerDistance: number; - - @AutoMap() - originType: PointType; - - @AutoMap() - destinationType: PointType; + passengerDistance?: number; @AutoMap(() => [Coordinates]) waypoints: Coordinates[]; @@ -102,6 +96,9 @@ export class Ad { @AutoMap() seatsUsed: number; + @AutoMap() + strict: boolean; + @AutoMap() createdAt: Date; diff --git a/src/modules/ad/domain/entities/geography.ts b/src/modules/ad/domain/entities/geography.ts new file mode 100644 index 0000000..39836ac --- /dev/null +++ b/src/modules/ad/domain/entities/geography.ts @@ -0,0 +1,98 @@ +import { Coordinates } from '../../../geography/domain/entities/coordinates'; +import { Route } from '../../../geography/domain/entities/route'; +import { IFindTimezone } from '../../../geography/domain/interfaces/timezone-finder.interface'; +import { Role } from '../types/role.enum'; +import { IGeorouter } from '../../../geography/domain/interfaces/georouter.interface'; +import { Path } from '../../../geography/domain/types/path.type'; +import { Timezoner } from '../../../geography/domain/types/timezoner'; + +export class Geography { + private points: Coordinates[]; + timezones: string[]; + driverRoute: Route; + passengerRoute: Route; + timezoneFinder: IFindTimezone; + + constructor(points: Coordinates[], timezoner: Timezoner) { + this.points = points; + this.timezones = [timezoner.timezone]; + this.timezoneFinder = timezoner.finder; + this.setTimezones(); + } + + createRoutes = async ( + roles: Role[], + georouter: IGeorouter, + ): Promise => { + const paths: Path[] = []; + if (roles.includes(Role.DRIVER) && roles.includes(Role.PASSENGER)) { + if (this.points.length == 2) { + // 2 points => same route for driver and passenger + const commonPath: Path = { + key: RouteKey.COMMON, + points: this.points, + }; + paths.push(commonPath); + } else { + const driverPath: Path = { + key: RouteKey.DRIVER, + points: this.points, + }; + const passengerPath: Path = { + key: RouteKey.PASSENGER, + points: [this.points[0], this.points[this.points.length - 1]], + }; + paths.push(driverPath, passengerPath); + } + } else if (roles.includes(Role.DRIVER)) { + const driverPath: Path = { + key: RouteKey.DRIVER, + points: this.points, + }; + paths.push(driverPath); + } else if (roles.includes(Role.PASSENGER)) { + const passengerPath: Path = { + key: RouteKey.PASSENGER, + points: [this.points[0], this.points[this.points.length - 1]], + }; + paths.push(passengerPath); + } + const routes = await georouter.route(paths, { + withDistance: false, + withPoints: false, + withTime: false, + }); + if (routes.some((route) => route.key == RouteKey.COMMON)) { + this.driverRoute = routes.find( + (route) => route.key == RouteKey.COMMON, + ).route; + this.passengerRoute = routes.find( + (route) => route.key == RouteKey.COMMON, + ).route; + } else { + if (routes.some((route) => route.key == RouteKey.DRIVER)) { + this.driverRoute = routes.find( + (route) => route.key == RouteKey.DRIVER, + ).route; + } + if (routes.some((route) => route.key == RouteKey.PASSENGER)) { + this.passengerRoute = routes.find( + (route) => route.key == RouteKey.PASSENGER, + ).route; + } + } + }; + + private setTimezones = (): void => { + this.timezones = this.timezoneFinder.timezones( + this.points[0].lat, + this.points[0].lon, + ); + }; +} + +export enum RouteKey { + COMMON = 'common', + DRIVER = 'driver', + PASSENGER = 'passenger', +} diff --git a/src/modules/ad/domain/entities/time-converter.ts b/src/modules/ad/domain/entities/time-converter.ts index bc0418a..e60bf76 100644 --- a/src/modules/ad/domain/entities/time-converter.ts +++ b/src/modules/ad/domain/entities/time-converter.ts @@ -1,14 +1,14 @@ import { DateTime, TimeZone } from 'timezonecomplete'; export class TimeConverter { - static toUtcDatetime = ( - date: string, - time: string, - timezone: string, - ): Date => { + static toUtcDatetime = (date: Date, time: string, timezone: string): Date => { try { + if (!date || !time || !timezone) throw new Error(); return new Date( - new DateTime(`${date}T${time}:00`, TimeZone.zone(timezone, false)) + new DateTime( + `${date.toISOString().split('T')[0]}T${time}:00`, + TimeZone.zone(timezone, false), + ) .convert(TimeZone.zone('UTC')) .toIsoString(), ); diff --git a/src/modules/ad/domain/types/default-params.type.ts b/src/modules/ad/domain/types/default-params.type.ts new file mode 100644 index 0000000..89dcb0e --- /dev/null +++ b/src/modules/ad/domain/types/default-params.type.ts @@ -0,0 +1,5 @@ +export type IDefaultParams = { + DEFAULT_TIMEZONE: string; + GEOROUTER_TYPE: string; + GEOROUTER_URL: string; +}; diff --git a/src/modules/ad/domain/types/frequency.enum.ts b/src/modules/ad/domain/types/frequency.enum.ts index ec435e8..0126874 100644 --- a/src/modules/ad/domain/types/frequency.enum.ts +++ b/src/modules/ad/domain/types/frequency.enum.ts @@ -1,4 +1,4 @@ export enum Frequency { - PUNCTUAL = 1, - RECURRENT = 2, + PUNCTUAL = 'PUNCTUAL', + RECURRENT = 'RECURRENT', } diff --git a/src/modules/ad/domain/types/role.enum.ts b/src/modules/ad/domain/types/role.enum.ts new file mode 100644 index 0000000..7522f80 --- /dev/null +++ b/src/modules/ad/domain/types/role.enum.ts @@ -0,0 +1,4 @@ +export enum Role { + DRIVER = 'DRIVER', + PASSENGER = 'PASSENGER', +} diff --git a/src/modules/ad/domain/usecases/create-ad.usecase.ts b/src/modules/ad/domain/usecases/create-ad.usecase.ts index d85bf81..529ed34 100644 --- a/src/modules/ad/domain/usecases/create-ad.usecase.ts +++ b/src/modules/ad/domain/usecases/create-ad.usecase.ts @@ -20,6 +20,16 @@ export class CreateAdUseCase { CreateAdRequest, Ad, ); + adToCreate.driverDistance = command.geography.driverRoute?.distance; + adToCreate.driverDuration = command.geography.driverRoute?.duration; + adToCreate.passengerDistance = command.geography.passengerRoute?.distance; + adToCreate.passengerDuration = command.geography.passengerRoute?.duration; + adToCreate.fwdAzimuth = command.geography.driverRoute + ? command.geography.driverRoute.fwdAzimuth + : command.geography.passengerRoute.fwdAzimuth; + adToCreate.backAzimuth = command.geography.driverRoute + ? command.geography.driverRoute.backAzimuth + : command.geography.passengerRoute.backAzimuth; return adToCreate; // return await this.adRepository.createAd(adToCreate); } catch (error) { diff --git a/src/modules/ad/mappers/ad.profile.ts b/src/modules/ad/mappers/ad.profile.ts index c0b61f2..f491616 100644 --- a/src/modules/ad/mappers/ad.profile.ts +++ b/src/modules/ad/mappers/ad.profile.ts @@ -4,7 +4,6 @@ import { Injectable } from '@nestjs/common'; import { Ad } from '../domain/entities/ad'; import { AdPresenter } from '../adapters/primaries/ad.presenter'; import { CreateAdRequest } from '../domain/dtos/create-ad.request'; -import { Coordinates } from '../../geography/domain/entities/coordinates'; import { TimeConverter } from '../domain/entities/time-converter'; @Injectable() @@ -16,43 +15,10 @@ export class AdProfile extends AutomapperProfile { override get profile() { return (mapper: any) => { createMap(mapper, Ad, AdPresenter); - createMap( - mapper, - CreateAdRequest, - CreateAdRequest, - forMember( - (dest) => dest.waypoints, - mapFrom((source) => - source.waypoints.map( - (waypoint) => - new Coordinates( - waypoint.lon ?? undefined, - waypoint.lat ?? undefined, - ), - ), - ), - ), - ); createMap( mapper, CreateAdRequest, Ad, - forMember( - (dest) => dest.fromDate, - mapFrom((source) => new Date(source.fromDate)), - ), - forMember( - (dest) => dest.toDate, - mapFrom((source) => new Date(source.toDate)), - ), - forMember( - (dest) => dest.createdAt, - mapFrom((source) => new Date(source.createdAt)), - ), - forMember( - (dest) => dest.updatedAt, - mapFrom((source) => new Date(source.updatedAt)), - ), forMember( (dest) => dest.monTime, mapFrom(({ monTime: time, fromDate: date, timezone }) => diff --git a/src/modules/ad/tests/unit/adapters/secondaries/default-params.provider.spec.ts b/src/modules/ad/tests/unit/adapters/secondaries/default-params.provider.spec.ts new file mode 100644 index 0000000..4d32f12 --- /dev/null +++ b/src/modules/ad/tests/unit/adapters/secondaries/default-params.provider.spec.ts @@ -0,0 +1,38 @@ +import { ConfigService } from '@nestjs/config'; +import { Test, TestingModule } from '@nestjs/testing'; +import { DefaultParamsProvider } from '../../../../adapters/secondaries/default-params.provider'; +import { IDefaultParams } from '../../../../domain/types/default-params.type'; + +const mockConfigService = { + get: jest.fn().mockImplementation(() => '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, + ); + }); + + it('should be defined', () => { + expect(defaultParamsProvider).toBeDefined(); + }); + + it('should provide default params', async () => { + const params: IDefaultParams = defaultParamsProvider.getParams(); + expect(params.GEOROUTER_URL).toBe('some_default_value'); + }); +}); diff --git a/src/modules/ad/tests/unit/domain/ad.completer.spec.ts b/src/modules/ad/tests/unit/domain/ad.completer.spec.ts new file mode 100644 index 0000000..4d5db12 --- /dev/null +++ b/src/modules/ad/tests/unit/domain/ad.completer.spec.ts @@ -0,0 +1,42 @@ +import { Frequency } from '../../../domain/types/frequency.enum'; +import { Ad } from '../../../domain/entities/ad'; +import { AdCompleter } from '../../../domain/entities/ad.completer'; +import { Coordinates } from '../../../../geography/domain/entities/coordinates'; + +const ad: Ad = new Ad(); +ad.driver = true; +ad.passenger = false; +ad.frequency = Frequency.RECURRENT; +ad.fromDate = new Date('2023-05-01'); +ad.toDate = new Date('2024-05-01'); +ad.monTime = new Date('2023-05-01T06:00:00.000Z'); +ad.tueTime = new Date('2023-05-01T06:00:00.000Z'); +ad.wedTime = new Date('2023-05-01T06:00:00.000Z'); +ad.thuTime = new Date('2023-05-01T06:00:00.000Z'); +ad.friTime = new Date('2023-05-01T06:00:00.000Z'); +ad.monMargin = 900; +ad.tueMargin = 900; +ad.wedMargin = 900; +ad.thuMargin = 900; +ad.friMargin = 900; +ad.satMargin = 900; +ad.sunMargin = 900; +ad.waypoints = [new Coordinates(6.18, 48.69), new Coordinates(6.44, 48.85)]; +ad.seatsDriver = 3; +ad.seatsPassenger = 1; +ad.strict = false; + +describe('AdCompleter', () => { + it('should be defined', () => { + expect(new AdCompleter()).toBeDefined(); + }); + + describe('complete', () => { + it('should complete an ad', async () => { + const ad: Ad = new Ad(); + const adCompleter: AdCompleter = new AdCompleter(); + const completedAd: Ad = await adCompleter.complete(ad); + expect(completedAd.fwdAzimuth).toBe(45); + }); + }); +}); diff --git a/src/modules/ad/tests/unit/domain/create-ad.usecase.spec.ts b/src/modules/ad/tests/unit/domain/create-ad.usecase.spec.ts index c879974..d7eee5a 100644 --- a/src/modules/ad/tests/unit/domain/create-ad.usecase.spec.ts +++ b/src/modules/ad/tests/unit/domain/create-ad.usecase.spec.ts @@ -1,5 +1,4 @@ import { CreateAdRequest } from '../../../domain/dtos/create-ad.request'; -import { PointType } from '../../../../geography/domain/types/point-type.enum'; import { CreateAdUseCase } from '../../../domain/usecases/create-ad.usecase'; import { Test, TestingModule } from '@nestjs/testing'; import { AutomapperModule } from '@automapper/nestjs'; @@ -8,16 +7,28 @@ import { AdRepository } from '../../../adapters/secondaries/ad.repository'; import { CreateAdCommand } from '../../../commands/create-ad.command'; import { Ad } from '../../../domain/entities/ad'; import { AdProfile } from '../../../mappers/ad.profile'; +import { Frequency } from '../../../domain/types/frequency.enum'; +import { IDefaultParams } from '../../../domain/types/default-params.type'; const mockAdRepository = {}; +const mockGeorouterCreator = { + create: jest.fn().mockImplementation(), +}; + +const defaultParams: IDefaultParams = { + GEOROUTER_TYPE: 'graphhopper', + GEOROUTER_URL: 'http://localhost', +}; + const createAdRequest: CreateAdRequest = { uuid: '77c55dfc-c28b-4026-942e-f94e95401fb1', + userUuid: 'dfd993f6-7889-4876-9570-5e1d7b6e3f42', driver: true, passenger: false, - frequency: 2, - fromDate: '2023-04-26', - toDate: '2024-04-25', + frequency: Frequency.RECURRENT, + fromDate: new Date('2023-04-26'), + toDate: new Date('2024-04-25'), monTime: '07:00', tueTime: '07:00', wedTime: '07:00', @@ -32,12 +43,9 @@ const createAdRequest: CreateAdRequest = { friMargin: 900, satMargin: 900, sunMargin: 900, - originType: PointType.OTHER, - destinationType: PointType.OTHER, seatsDriver: 3, seatsPassenger: 1, - createdAt: '2023-04-01 12:45', - updatedAt: '2023-04-01 12:45', + strict: false, waypoints: [ { lon: 6, lat: 45 }, { lon: 6.5, lat: 45.5 }, @@ -70,7 +78,11 @@ describe('CreateAdUseCase', () => { describe('execute', () => { it('should create an ad', async () => { const ad = await createAdUseCase.execute( - new CreateAdCommand(createAdRequest), + new CreateAdCommand( + createAdRequest, + defaultParams, + mockGeorouterCreator, + ), ); expect(ad).toBeInstanceOf(Ad); }); diff --git a/src/modules/ad/tests/unit/domain/time-converter.spec.ts b/src/modules/ad/tests/unit/domain/time-converter.spec.ts index 5539cd9..00138e4 100644 --- a/src/modules/ad/tests/unit/domain/time-converter.spec.ts +++ b/src/modules/ad/tests/unit/domain/time-converter.spec.ts @@ -8,7 +8,7 @@ describe('TimeConverter', () => { it('should convert a Europe/Paris datetime to utc datetime', () => { expect( TimeConverter.toUtcDatetime( - '2023-05-01', + new Date('2023-05-01'), '07:00', 'Europe/Paris', ).getUTCHours(), @@ -20,16 +20,28 @@ describe('TimeConverter', () => { TimeConverter.toUtcDatetime(undefined, '07:00', 'Europe/Paris'), ).toBeUndefined(); expect( - TimeConverter.toUtcDatetime('2023-13-01', '07:00', 'Europe/Paris'), + TimeConverter.toUtcDatetime( + new Date('2023-13-01'), + '07:00', + 'Europe/Paris', + ), ).toBeUndefined(); expect( - TimeConverter.toUtcDatetime('2023-05-01', undefined, 'Europe/Paris'), + TimeConverter.toUtcDatetime( + new Date('2023-05-01'), + undefined, + 'Europe/Paris', + ), ).toBeUndefined(); expect( - TimeConverter.toUtcDatetime('2023-05-01', 'a', 'Europe/Paris'), + TimeConverter.toUtcDatetime(new Date('2023-05-01'), 'a', 'Europe/Paris'), ).toBeUndefined(); expect( - TimeConverter.toUtcDatetime('2023-13-01', '07:00', 'OlympusMons/Mars'), + TimeConverter.toUtcDatetime( + new Date('2023-13-01'), + '07:00', + 'OlympusMons/Mars', + ), ).toBeUndefined(); }); }); diff --git a/src/modules/database/adapters/secondaries/prisma-repository.abstract.ts b/src/modules/database/adapters/secondaries/prisma-repository.abstract.ts index dee142c..635e966 100644 --- a/src/modules/database/adapters/secondaries/prisma-repository.abstract.ts +++ b/src/modules/database/adapters/secondaries/prisma-repository.abstract.ts @@ -10,7 +10,7 @@ import { PrismaService } from './prisma-service'; */ @Injectable() export abstract class PrismaRepository implements IRepository { - protected _model: string; + protected model: string; constructor(protected readonly _prisma: PrismaService) {} @@ -21,13 +21,13 @@ export abstract class PrismaRepository implements IRepository { include?: any, ): Promise> { const [data, total] = await this._prisma.$transaction([ - this._prisma[this._model].findMany({ + this._prisma[this.model].findMany({ where, include, skip: (page - 1) * perPage, take: perPage, }), - this._prisma[this._model].count({ + this._prisma[this.model].count({ where, }), ]); @@ -39,7 +39,7 @@ export abstract class PrismaRepository implements IRepository { async findOneByUuid(uuid: string): Promise { try { - const entity = await this._prisma[this._model].findUnique({ + const entity = await this._prisma[this.model].findUnique({ where: { uuid }, }); @@ -59,7 +59,7 @@ export abstract class PrismaRepository implements IRepository { async findOne(where: any, include?: any): Promise { try { - const entity = await this._prisma[this._model].findFirst({ + const entity = await this._prisma[this.model].findFirst({ where: where, include: include, }); @@ -81,7 +81,7 @@ export abstract class PrismaRepository implements IRepository { // TODO : Refactor for good clean architecture ? async create(entity: Partial | any, include?: any): Promise { try { - const res = await this._prisma[this._model].create({ + const res = await this._prisma[this.model].create({ data: entity, include: include, }); @@ -102,7 +102,7 @@ export abstract class PrismaRepository implements IRepository { async update(uuid: string, entity: Partial): Promise { try { - const updatedEntity = await this._prisma[this._model].update({ + const updatedEntity = await this._prisma[this.model].update({ where: { uuid }, data: entity, }); @@ -126,7 +126,7 @@ export abstract class PrismaRepository implements IRepository { include?: any, ): Promise { try { - const updatedEntity = await this._prisma[this._model].update({ + const updatedEntity = await this._prisma[this.model].update({ where: where, data: entity, include: include, @@ -148,7 +148,7 @@ export abstract class PrismaRepository implements IRepository { async delete(uuid: string): Promise { try { - const entity = await this._prisma[this._model].delete({ + const entity = await this._prisma[this.model].delete({ where: { uuid }, }); @@ -168,7 +168,7 @@ export abstract class PrismaRepository implements IRepository { async deleteMany(where: any): Promise { try { - const entity = await this._prisma[this._model].deleteMany({ + const entity = await this._prisma[this.model].deleteMany({ where: where, }); @@ -191,7 +191,7 @@ export abstract class PrismaRepository implements IRepository { where: string[], ): Promise> { const query = `SELECT ${include.join(',')} FROM ${ - this._model + this.model } WHERE ${where.join(' AND ')}`; const data: T[] = await this._prisma.$queryRawUnsafe(query); return Promise.resolve({ @@ -202,7 +202,7 @@ export abstract class PrismaRepository implements IRepository { async createWithFields(fields: object): Promise { try { - const command = `INSERT INTO ${this._model} ("${Object.keys(fields).join( + const command = `INSERT INTO ${this.model} ("${Object.keys(fields).join( '","', )}") VALUES (${Object.values(fields).join(',')})`; return await this._prisma.$executeRawUnsafe(command); @@ -223,7 +223,7 @@ export abstract class PrismaRepository implements IRepository { entity['"updatedAt"'] = `to_timestamp(${Date.now()} / 1000.0)`; const values = Object.keys(entity).map((key) => `${key} = ${entity[key]}`); try { - const command = `UPDATE ${this._model} SET ${values.join( + const command = `UPDATE ${this.model} SET ${values.join( ', ', )} WHERE uuid = '${uuid}'`; return await this._prisma.$executeRawUnsafe(command); diff --git a/src/modules/database/tests/unit/prisma-repository.spec.ts b/src/modules/database/tests/unit/prisma-repository.spec.ts index 984c69d..eb3bad0 100644 --- a/src/modules/database/tests/unit/prisma-repository.spec.ts +++ b/src/modules/database/tests/unit/prisma-repository.spec.ts @@ -41,7 +41,7 @@ Array.from({ length: 10 }).forEach(() => { @Injectable() class FakePrismaRepository extends PrismaRepository { - protected _model = 'fake'; + protected model = 'fake'; } class FakePrismaService extends PrismaService { diff --git a/src/modules/geography/adapters/secondaries/georouter-creator.ts b/src/modules/geography/adapters/secondaries/georouter-creator.ts new file mode 100644 index 0000000..f147a62 --- /dev/null +++ b/src/modules/geography/adapters/secondaries/georouter-creator.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@nestjs/common'; +import { ICreateGeorouter } from '../../domain/interfaces/georouter-creator.interface'; +import { IGeorouter } from '../../domain/interfaces/georouter.interface'; +import { GraphhopperGeorouter } from './graphhopper-georouter'; +import { HttpService } from '@nestjs/axios'; +import { Geodesic } from './geodesic'; +import { GeographyException } from '../../exceptions/geography.exception'; +import { ExceptionCode } from '../../..//utils/exception-code.enum'; + +@Injectable() +export class GeorouterCreator implements ICreateGeorouter { + constructor( + private readonly httpService: HttpService, + private readonly geodesic: Geodesic, + ) {} + + create = (type: string, url: string): IGeorouter => { + switch (type) { + case 'graphhopper': + return new GraphhopperGeorouter(url, this.httpService, this.geodesic); + default: + throw new GeographyException( + ExceptionCode.INVALID_ARGUMENT, + 'Unknown geocoder', + ); + } + }; +} diff --git a/src/modules/geography/adapters/secondaries/graphhopper-georouter.ts b/src/modules/geography/adapters/secondaries/graphhopper-georouter.ts new file mode 100644 index 0000000..b573533 --- /dev/null +++ b/src/modules/geography/adapters/secondaries/graphhopper-georouter.ts @@ -0,0 +1,324 @@ +import { HttpService } from '@nestjs/axios'; +import { IGeorouter } from '../../domain/interfaces/georouter.interface'; +import { Injectable } from '@nestjs/common'; +import { catchError, lastValueFrom, map } from 'rxjs'; +import { AxiosError, AxiosResponse } from 'axios'; +import { IGeodesic } from '../../../geography/domain/interfaces/geodesic.interface'; +import { GeorouterSettings } from '../../domain/types/georouter-settings.type'; +import { Path } from '../../domain/types/path.type'; +import { NamedRoute } from '../../domain/types/named-route'; +import { GeographyException } from '../../exceptions/geography.exception'; +import { ExceptionCode } from '../../..//utils/exception-code.enum'; +import { Route } from '../../domain/entities/route'; +import { SpacetimePoint } from '../../domain/entities/spacetime-point'; + +@Injectable() +export class GraphhopperGeorouter implements IGeorouter { + private url: string; + private urlArgs: string[]; + private withTime: boolean; + private withPoints: boolean; + private withDistance: boolean; + private paths: Path[]; + private httpService: HttpService; + private geodesic: IGeodesic; + + constructor(url: string, httpService: HttpService, geodesic: IGeodesic) { + this.url = url + '/route?'; + this.httpService = httpService; + this.geodesic = geodesic; + } + + route = async ( + paths: Path[], + settings: GeorouterSettings, + ): Promise => { + this.setDefaultUrlArgs(); + this.setWithTime(settings.withTime); + this.setWithPoints(settings.withPoints); + this.setWithDistance(settings.withDistance); + this.paths = paths; + return await this.getRoutes(); + }; + + private setDefaultUrlArgs = (): void => { + this.urlArgs = ['vehicle=car', 'weighting=fastest', 'points_encoded=false']; + }; + + private setWithTime = (withTime: boolean): void => { + this.withTime = withTime; + if (withTime) { + this.urlArgs.push('details=time'); + } + }; + + private setWithPoints = (withPoints: boolean): void => { + this.withPoints = withPoints; + if (!withPoints) { + this.urlArgs.push('calc_points=false'); + } + }; + + private setWithDistance = (withDistance: boolean): void => { + this.withDistance = withDistance; + if (withDistance) { + this.urlArgs.push('instructions=true'); + } else { + this.urlArgs.push('instructions=false'); + } + }; + + private getRoutes = async (): Promise => { + const routes = Promise.all( + this.paths.map(async (path) => { + const url: string = [ + this.getUrl(), + '&point=', + path.points + .map((point) => [point.lat, point.lon].join()) + .join('&point='), + ].join(''); + const route = await lastValueFrom( + this.httpService.get(url).pipe( + map((res) => (res.data ? this.createRoute(res) : undefined)), + catchError((error: AxiosError) => { + throw new GeographyException( + ExceptionCode.INTERNAL, + 'Georouter unavailable : ' + error.message, + ); + }), + ), + ); + return { + key: path.key, + route, + }; + }), + ); + return routes; + }; + + private getUrl = (): string => { + return [this.url, this.urlArgs.join('&')].join(''); + }; + + private createRoute = ( + response: AxiosResponse, + ): Route => { + const route = new Route(this.geodesic); + if (response.data.paths && response.data.paths[0]) { + const shortestPath = response.data.paths[0]; + route.distance = shortestPath.distance ?? 0; + route.duration = shortestPath.time ? shortestPath.time / 1000 : 0; + if (shortestPath.points && shortestPath.points.coordinates) { + route.setPoints( + shortestPath.points.coordinates.map((coordinate) => ({ + lon: coordinate[0], + lat: coordinate[1], + })), + ); + if ( + shortestPath.details && + shortestPath.details.time && + shortestPath.snapped_waypoints && + shortestPath.snapped_waypoints.coordinates + ) { + let instructions: GraphhopperInstruction[] = []; + if (shortestPath.instructions) + instructions = shortestPath.instructions; + route.setSpacetimePoints( + this.generateSpacetimePoints( + shortestPath.points.coordinates, + shortestPath.snapped_waypoints.coordinates, + shortestPath.details.time, + instructions, + ), + ); + } + } + } + return route; + }; + + private generateSpacetimePoints = ( + points: Array, + snappedWaypoints: Array, + durations: Array, + instructions: GraphhopperInstruction[], + ): SpacetimePoint[] => { + const indices = this.getIndices(points, snappedWaypoints); + const times = this.getTimes(durations, indices); + const distances = this.getDistances(instructions, indices); + return indices.map( + (index) => + new SpacetimePoint( + { lon: points[index][1], lat: points[index][0] }, + times.find((time) => time.index == index)?.duration, + distances.find((distance) => distance.index == index)?.distance, + ), + ); + }; + + private getIndices = ( + points: Array, + snappedWaypoints: Array, + ): number[] => { + const indices = snappedWaypoints.map((waypoint) => + points.findIndex( + (point) => point[0] == waypoint[0] && point[1] == waypoint[1], + ), + ); + if (indices.find((index) => index == -1) === undefined) return indices; + const missedWaypoints = indices + .map( + (value, index) => + < + { + index: number; + originIndex: number; + waypoint: number[]; + nearest: number; + distance: number; + } + >{ + index: value, + originIndex: index, + waypoint: snappedWaypoints[index], + nearest: undefined, + distance: 999999999, + }, + ) + .filter((element) => element.index == -1); + for (const index in points) { + for (const missedWaypoint of missedWaypoints) { + const inverse = this.geodesic.inverse( + missedWaypoint.waypoint[0], + missedWaypoint.waypoint[1], + points[index][0], + points[index][1], + ); + if (inverse.distance < missedWaypoint.distance) { + missedWaypoint.distance = inverse.distance; + missedWaypoint.nearest = parseInt(index); + } + } + } + for (const missedWaypoint of missedWaypoints) { + indices[missedWaypoint.originIndex] = missedWaypoint.nearest; + } + return indices; + }; + + private getTimes = ( + durations: Array, + indices: number[], + ): Array<{ index: number; duration: number }> => { + const times: Array<{ index: number; duration: number }> = []; + let duration = 0; + for (const [origin, destination, stepDuration] of durations) { + let indexFound = false; + const indexAsOrigin = indices.find((index) => index == origin); + if ( + indexAsOrigin !== undefined && + times.find((time) => origin == time.index) == undefined + ) { + times.push({ + index: indexAsOrigin, + duration: Math.round(stepDuration / 1000), + }); + indexFound = true; + } + if (!indexFound) { + const indexAsDestination = indices.find( + (index) => index == destination, + ); + if ( + indexAsDestination !== undefined && + times.find((time) => destination == time.index) == undefined + ) { + times.push({ + index: indexAsDestination, + duration: Math.round((duration + stepDuration) / 1000), + }); + indexFound = true; + } + } + if (!indexFound) { + const indexInBetween = indices.find( + (index) => origin < index && index < destination, + ); + if (indexInBetween !== undefined) { + times.push({ + index: indexInBetween, + duration: Math.round((duration + stepDuration / 2) / 1000), + }); + } + } + duration += stepDuration; + } + return times; + }; + + private getDistances = ( + instructions: GraphhopperInstruction[], + indices: number[], + ): Array<{ index: number; distance: number }> => { + let distance = 0; + const distances: Array<{ index: number; distance: number }> = [ + { + index: 0, + distance, + }, + ]; + for (const instruction of instructions) { + distance += instruction.distance; + if ( + (instruction.sign == GraphhopperSign.SIGN_WAYPOINT || + instruction.sign == GraphhopperSign.SIGN_FINISH) && + indices.find((index) => index == instruction.interval[0]) !== undefined + ) { + distances.push({ + index: instruction.interval[0], + distance: Math.round(distance), + }); + } + } + return distances; + }; +} + +type GraphhopperResponse = { + paths: [ + { + distance: number; + weight: number; + time: number; + points_encoded: boolean; + bbox: number[]; + points: GraphhopperCoordinates; + snapped_waypoints: GraphhopperCoordinates; + details: { + time: Array; + }; + instructions: GraphhopperInstruction[]; + }, + ]; +}; + +type GraphhopperCoordinates = { + coordinates: Array; +}; + +type GraphhopperInstruction = { + distance: number; + heading: number; + sign: GraphhopperSign; + interval: number[]; + text: string; +}; + +enum GraphhopperSign { + SIGN_START = 0, + SIGN_FINISH = 4, + SIGN_WAYPOINT = 5, +} diff --git a/src/modules/matcher/domain/entities/ecosystem/route.ts b/src/modules/geography/domain/entities/route.ts similarity index 72% rename from src/modules/matcher/domain/entities/ecosystem/route.ts rename to src/modules/geography/domain/entities/route.ts index c42dba9..48b8744 100644 --- a/src/modules/matcher/domain/entities/ecosystem/route.ts +++ b/src/modules/geography/domain/entities/route.ts @@ -1,7 +1,6 @@ -import { IGeodesic } from '../../../../geography/domain/interfaces/geodesic.interface'; -import { Point } from '../../../../geography/domain/types/point.type'; +import { IGeodesic } from '../interfaces/geodesic.interface'; +import { Point } from '../types/point.type'; import { SpacetimePoint } from './spacetime-point'; -import { Waypoint } from './waypoint'; export class Route { distance: number; @@ -9,7 +8,6 @@ export class Route { fwdAzimuth: number; backAzimuth: number; distanceAzimuth: number; - waypoints: Waypoint[]; points: Point[]; spacetimePoints: SpacetimePoint[]; private geodesic: IGeodesic; @@ -20,17 +18,11 @@ export class Route { this.fwdAzimuth = undefined; this.backAzimuth = undefined; this.distanceAzimuth = undefined; - this.waypoints = []; this.points = []; this.spacetimePoints = []; this.geodesic = geodesic; } - setWaypoints = (waypoints: Waypoint[]): void => { - this.waypoints = waypoints; - this.setAzimuth(waypoints.map((waypoint) => waypoint.point)); - }; - setPoints = (points: Point[]): void => { this.points = points; this.setAzimuth(points); @@ -40,7 +32,7 @@ export class Route { this.spacetimePoints = spacetimePoints; }; - private setAzimuth = (points: Point[]): void => { + protected setAzimuth = (points: Point[]): void => { const inverse = this.geodesic.inverse( points[0].lon, points[0].lat, diff --git a/src/modules/geography/domain/entities/spacetime-point.ts b/src/modules/geography/domain/entities/spacetime-point.ts new file mode 100644 index 0000000..97f130d --- /dev/null +++ b/src/modules/geography/domain/entities/spacetime-point.ts @@ -0,0 +1,13 @@ +import { Coordinates } from './coordinates'; + +export class SpacetimePoint { + coordinates: Coordinates; + duration: number; + distance: number; + + constructor(coordinates: Coordinates, duration: number, distance: number) { + this.coordinates = coordinates; + this.duration = duration; + this.distance = distance; + } +} diff --git a/src/modules/geography/domain/interfaces/georouter-creator.interface.ts b/src/modules/geography/domain/interfaces/georouter-creator.interface.ts new file mode 100644 index 0000000..7a6bd25 --- /dev/null +++ b/src/modules/geography/domain/interfaces/georouter-creator.interface.ts @@ -0,0 +1,5 @@ +import { IGeorouter } from './georouter.interface'; + +export interface ICreateGeorouter { + create(type: string, url: string): IGeorouter; +} diff --git a/src/modules/geography/domain/interfaces/georouter.interface.ts b/src/modules/geography/domain/interfaces/georouter.interface.ts new file mode 100644 index 0000000..c2c2e05 --- /dev/null +++ b/src/modules/geography/domain/interfaces/georouter.interface.ts @@ -0,0 +1,7 @@ +import { GeorouterSettings } from '../types/georouter-settings.type'; +import { NamedRoute } from '../types/named-route'; +import { Path } from '../types/path.type'; + +export interface IGeorouter { + route(paths: Path[], settings: GeorouterSettings): Promise; +} diff --git a/src/modules/geography/domain/types/georouter-settings.type.ts b/src/modules/geography/domain/types/georouter-settings.type.ts new file mode 100644 index 0000000..d8f73ae --- /dev/null +++ b/src/modules/geography/domain/types/georouter-settings.type.ts @@ -0,0 +1,5 @@ +export type GeorouterSettings = { + withPoints: boolean; + withTime: boolean; + withDistance: boolean; +}; diff --git a/src/modules/geography/domain/types/named-route.ts b/src/modules/geography/domain/types/named-route.ts new file mode 100644 index 0000000..f1fdb2d --- /dev/null +++ b/src/modules/geography/domain/types/named-route.ts @@ -0,0 +1,6 @@ +import { Route } from '../entities/route'; + +export type NamedRoute = { + key: string; + route: Route; +}; diff --git a/src/modules/geography/domain/types/path.type.ts b/src/modules/geography/domain/types/path.type.ts new file mode 100644 index 0000000..44e03b6 --- /dev/null +++ b/src/modules/geography/domain/types/path.type.ts @@ -0,0 +1,6 @@ +import { Point } from '../../../geography/domain/types/point.type'; + +export type Path = { + key: string; + points: Point[]; +}; diff --git a/src/modules/geography/domain/types/timezoner.ts b/src/modules/geography/domain/types/timezoner.ts new file mode 100644 index 0000000..8764400 --- /dev/null +++ b/src/modules/geography/domain/types/timezoner.ts @@ -0,0 +1,6 @@ +import { IFindTimezone } from '../interfaces/timezone-finder.interface'; + +export type Timezoner = { + timezone: string; + finder: IFindTimezone; +}; diff --git a/src/modules/geography/exceptions/geography.exception.ts b/src/modules/geography/exceptions/geography.exception.ts new file mode 100644 index 0000000..9d07939 --- /dev/null +++ b/src/modules/geography/exceptions/geography.exception.ts @@ -0,0 +1,13 @@ +export class GeographyException implements Error { + name: string; + message: string; + + constructor(private _code: number, private _message: string) { + this.name = 'GeographyException'; + this.message = _message; + } + + get code(): number { + return this._code; + } +} diff --git a/src/modules/geography/tests/unit/georouter-creator.spec.ts b/src/modules/geography/tests/unit/georouter-creator.spec.ts new file mode 100644 index 0000000..03afe6c --- /dev/null +++ b/src/modules/geography/tests/unit/georouter-creator.spec.ts @@ -0,0 +1,47 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { HttpService } from '@nestjs/axios'; +import { GeorouterCreator } from '../../adapters/secondaries/georouter-creator'; +import { Geodesic } from '../../adapters/secondaries/geodesic'; +import { GraphhopperGeorouter } from '../../adapters/secondaries/graphhopper-georouter'; + +const mockHttpService = jest.fn(); +const mockGeodesic = jest.fn(); + +describe('Georouter creator', () => { + let georouterCreator: GeorouterCreator; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [], + providers: [ + GeorouterCreator, + { + provide: HttpService, + useValue: mockHttpService, + }, + { + provide: Geodesic, + useValue: mockGeodesic, + }, + ], + }).compile(); + + georouterCreator = module.get(GeorouterCreator); + }); + + it('should be defined', () => { + expect(georouterCreator).toBeDefined(); + }); + it('should create a graphhopper georouter', () => { + const georouter = georouterCreator.create( + 'graphhopper', + 'http://localhost', + ); + expect(georouter).toBeInstanceOf(GraphhopperGeorouter); + }); + it('should throw an exception if georouter type is unknown', () => { + expect(() => + georouterCreator.create('unknown', 'http://localhost'), + ).toThrow(); + }); +}); diff --git a/src/modules/geography/tests/unit/graphhopper-georouter.spec.ts b/src/modules/geography/tests/unit/graphhopper-georouter.spec.ts new file mode 100644 index 0000000..e0e45f9 --- /dev/null +++ b/src/modules/geography/tests/unit/graphhopper-georouter.spec.ts @@ -0,0 +1,456 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { HttpService } from '@nestjs/axios'; +import { of } from 'rxjs'; +import { AxiosError } from 'axios'; +import { GeorouterCreator } from '../../adapters/secondaries/georouter-creator'; +import { IGeorouter } from '../../domain/interfaces/georouter.interface'; +import { Geodesic } from '../../adapters/secondaries/geodesic'; + +const mockHttpService = { + get: jest + .fn() + .mockImplementationOnce(() => { + throw new AxiosError('Axios error !'); + }) + .mockImplementationOnce(() => { + return of({ + status: 200, + data: { + paths: [ + { + distance: 50000, + time: 1800000, + snapped_waypoints: { + coordinates: [ + [0, 0], + [10, 10], + ], + }, + }, + ], + }, + }); + }) + .mockImplementationOnce(() => { + return of({ + status: 200, + data: { + paths: [ + { + distance: 50000, + time: 1800000, + points: { + coordinates: [ + [0, 0], + [1, 1], + [2, 2], + [3, 3], + [4, 4], + [5, 5], + [6, 6], + [7, 7], + [8, 8], + [9, 9], + [10, 10], + ], + }, + snapped_waypoints: { + coordinates: [ + [0, 0], + [10, 10], + ], + }, + }, + ], + }, + }); + }) + .mockImplementationOnce(() => { + return of({ + status: 200, + data: { + paths: [ + { + distance: 50000, + time: 1800000, + points: { + coordinates: [ + [0, 0], + [1, 1], + [2, 2], + [3, 3], + [4, 4], + [5, 5], + [6, 6], + [7, 7], + [8, 8], + [9, 9], + [10, 10], + ], + }, + details: { + time: [ + [0, 1, 180000], + [1, 2, 180000], + [2, 3, 180000], + [3, 4, 180000], + [4, 5, 180000], + [5, 6, 180000], + [6, 7, 180000], + [7, 9, 360000], + [9, 10, 180000], + ], + }, + snapped_waypoints: { + coordinates: [ + [0, 0], + [10, 10], + ], + }, + }, + ], + }, + }); + }) + .mockImplementationOnce(() => { + return of({ + status: 200, + data: { + paths: [ + { + distance: 50000, + time: 1800000, + points: { + coordinates: [ + [0, 0], + [1, 1], + [2, 2], + [3, 3], + [4, 4], + [7, 7], + [8, 8], + [9, 9], + [10, 10], + ], + }, + snapped_waypoints: { + coordinates: [ + [0, 0], + [5, 5], + [10, 10], + ], + }, + details: { + time: [ + [0, 1, 180000], + [1, 2, 180000], + [2, 3, 180000], + [3, 4, 180000], + [4, 7, 540000], + [7, 9, 360000], + [9, 10, 180000], + ], + }, + }, + ], + }, + }); + }) + .mockImplementationOnce(() => { + return of({ + status: 200, + data: { + paths: [ + { + distance: 50000, + time: 1800000, + points: { + coordinates: [ + [0, 0], + [1, 1], + [2, 2], + [3, 3], + [4, 4], + [5, 5], + [6, 6], + [7, 7], + [8, 8], + [9, 9], + [10, 10], + ], + }, + snapped_waypoints: { + coordinates: [ + [0, 0], + [5, 5], + [10, 10], + ], + }, + details: { + time: [ + [0, 1, 180000], + [1, 2, 180000], + [2, 3, 180000], + [3, 4, 180000], + [4, 7, 540000], + [7, 9, 360000], + [9, 10, 180000], + ], + }, + instructions: [ + { + distance: 25000, + sign: 0, + interval: [0, 5], + text: 'Some instructions', + time: 900000, + }, + { + distance: 0, + sign: 5, + interval: [5, 5], + text: 'Waypoint 1', + time: 0, + }, + { + distance: 25000, + sign: 2, + interval: [5, 10], + text: 'Some instructions', + time: 900000, + }, + { + distance: 0.0, + sign: 4, + interval: [10, 10], + text: 'Arrive at destination', + time: 0, + }, + ], + }, + ], + }, + }); + }), +}; + +const mockGeodesic = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + inverse: jest.fn().mockImplementation(() => ({ + azimuth: 45, + distance: 50000, + })), +}; + +describe('Graphhopper Georouter', () => { + let georouterCreator: GeorouterCreator; + let graphhopperGeorouter: IGeorouter; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [], + providers: [ + GeorouterCreator, + { + provide: HttpService, + useValue: mockHttpService, + }, + { + provide: Geodesic, + useValue: mockGeodesic, + }, + ], + }).compile(); + + georouterCreator = module.get(GeorouterCreator); + graphhopperGeorouter = georouterCreator.create( + 'graphhopper', + 'http://localhost', + ); + }); + + it('should be defined', () => { + expect(graphhopperGeorouter).toBeDefined(); + }); + + describe('route function', () => { + it('should fail on axios error', async () => { + await expect( + graphhopperGeorouter.route( + [ + { + key: 'route1', + points: [ + { + lat: 0, + lon: 0, + }, + { + lat: 1, + lon: 1, + }, + ], + }, + ], + { + withDistance: false, + withPoints: false, + withTime: false, + }, + ), + ).rejects.toBeInstanceOf(Error); + }); + + it('should create one route with all settings to false', async () => { + const routes = await graphhopperGeorouter.route( + [ + { + key: 'route1', + points: [ + { + lat: 0, + lon: 0, + }, + { + lat: 10, + lon: 10, + }, + ], + }, + ], + { + withDistance: false, + withPoints: false, + withTime: false, + }, + ); + expect(routes).toHaveLength(1); + expect(routes[0].route.distance).toBe(50000); + }); + + it('should create one route with points', async () => { + const routes = await graphhopperGeorouter.route( + [ + { + key: 'route1', + points: [ + { + lat: 0, + lon: 0, + }, + { + lat: 10, + lon: 10, + }, + ], + }, + ], + { + withDistance: false, + withPoints: true, + withTime: false, + }, + ); + expect(routes).toHaveLength(1); + expect(routes[0].route.distance).toBe(50000); + expect(routes[0].route.duration).toBe(1800); + expect(routes[0].route.fwdAzimuth).toBe(45); + expect(routes[0].route.backAzimuth).toBe(225); + expect(routes[0].route.points.length).toBe(11); + }); + + it('should create one route with points and time', async () => { + const routes = await graphhopperGeorouter.route( + [ + { + key: 'route1', + points: [ + { + lat: 0, + lon: 0, + }, + { + lat: 10, + lon: 10, + }, + ], + }, + ], + { + withDistance: false, + withPoints: true, + withTime: true, + }, + ); + expect(routes).toHaveLength(1); + expect(routes[0].route.spacetimePoints.length).toBe(2); + expect(routes[0].route.spacetimePoints[1].duration).toBe(1800); + expect(routes[0].route.spacetimePoints[1].distance).toBeUndefined(); + }); + + it('should create one route with points and missed waypoints extrapolations', async () => { + const routes = await graphhopperGeorouter.route( + [ + { + key: 'route1', + points: [ + { + lat: 0, + lon: 0, + }, + { + lat: 5, + lon: 5, + }, + { + lat: 10, + lon: 10, + }, + ], + }, + ], + { + withDistance: false, + withPoints: true, + withTime: true, + }, + ); + expect(routes).toHaveLength(1); + expect(routes[0].route.spacetimePoints.length).toBe(3); + expect(routes[0].route.distance).toBe(50000); + expect(routes[0].route.duration).toBe(1800); + expect(routes[0].route.fwdAzimuth).toBe(45); + expect(routes[0].route.backAzimuth).toBe(225); + expect(routes[0].route.points.length).toBe(9); + }); + + it('should create one route with points, time and distance', async () => { + const routes = await graphhopperGeorouter.route( + [ + { + key: 'route1', + points: [ + { + lat: 0, + lon: 0, + }, + { + lat: 10, + lon: 10, + }, + ], + }, + ], + { + withDistance: true, + withPoints: true, + withTime: true, + }, + ); + expect(routes).toHaveLength(1); + expect(routes[0].route.spacetimePoints.length).toBe(3); + expect(routes[0].route.spacetimePoints[1].duration).toBe(990); + expect(routes[0].route.spacetimePoints[1].distance).toBe(25000); + }); + }); +}); diff --git a/src/modules/geography/tests/unit/route.spec.ts b/src/modules/geography/tests/unit/route.spec.ts new file mode 100644 index 0000000..7a8c1e4 --- /dev/null +++ b/src/modules/geography/tests/unit/route.spec.ts @@ -0,0 +1,48 @@ +import { Route } from '../../domain/entities/route'; +import { SpacetimePoint } from '../../domain/entities/spacetime-point'; + +const mockGeodesic = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + inverse: jest.fn().mockImplementation((lon1, lat1, lon2, lat2) => { + return lon1 == 0 + ? { + azimuth: 45, + distance: 50000, + } + : { + azimuth: -45, + distance: 60000, + }; + }), +}; + +describe('Route entity', () => { + it('should be defined', () => { + const route = new Route(mockGeodesic); + expect(route).toBeDefined(); + }); + it('should set points and geodesic values for a route', () => { + const route = new Route(mockGeodesic); + route.setPoints([ + { + lon: 10, + lat: 10, + }, + { + lon: 20, + lat: 20, + }, + ]); + expect(route.points.length).toBe(2); + expect(route.fwdAzimuth).toBe(315); + expect(route.backAzimuth).toBe(135); + expect(route.distanceAzimuth).toBe(60000); + }); + it('should set spacetimePoints for a route', () => { + const route = new Route(mockGeodesic); + const spacetimePoint1 = new SpacetimePoint({ lon: 0, lat: 0 }, 0, 0); + const spacetimePoint2 = new SpacetimePoint({ lon: 10, lat: 10 }, 500, 5000); + route.setSpacetimePoints([spacetimePoint1, spacetimePoint2]); + expect(route.spacetimePoints.length).toBe(2); + }); +}); diff --git a/src/modules/matcher/adapters/primaries/matcher.controller.ts b/src/modules/matcher/adapters/primaries/matcher.controller.ts index 0bbabbf..bc926b2 100644 --- a/src/modules/matcher/adapters/primaries/matcher.controller.ts +++ b/src/modules/matcher/adapters/primaries/matcher.controller.ts @@ -25,7 +25,7 @@ export class MatcherController { constructor( private readonly queryBus: QueryBus, private readonly defaultParamsProvider: DefaultParamsProvider, - @InjectMapper() private readonly _mapper: Mapper, + @InjectMapper() private readonly mapper: Mapper, private readonly georouterCreator: GeorouterCreator, private readonly timezoneFinder: GeoTimezoneFinder, private readonly timeConverter: TimeConverter, @@ -45,7 +45,7 @@ export class MatcherController { ); return Promise.resolve({ data: matchCollection.data.map((match: Match) => - this._mapper.map(match, Match, MatchPresenter), + this.mapper.map(match, Match, MatchPresenter), ), total: matchCollection.total, }); diff --git a/src/modules/matcher/adapters/primaries/matcher.proto b/src/modules/matcher/adapters/primaries/matcher.proto index f18aaff..898e6ee 100644 --- a/src/modules/matcher/adapters/primaries/matcher.proto +++ b/src/modules/matcher/adapters/primaries/matcher.proto @@ -7,29 +7,28 @@ service MatcherService { } message MatchRequest { - Mode mode = 1; - string uuid = 2; - repeated Coordinates waypoints = 3; - string departure = 4; - string fromDate = 5; - Schedule schedule = 6; - bool driver = 7; - bool passenger = 8; - string toDate = 9; - int32 marginDuration = 10; - MarginDurations marginDurations = 11; - int32 seatsPassenger = 12; - int32 seatsDriver = 13; - bool strict = 14; - Algorithm algorithm = 15; - int32 remoteness = 16; - bool useProportion = 17; - int32 proportion = 18; - bool useAzimuth = 19; - int32 azimuthMargin = 20; - float maxDetourDistanceRatio = 21; - float maxDetourDurationRatio = 22; - repeated int32 exclusions = 23; + string uuid = 1; + repeated Coordinates waypoints = 2; + string departure = 3; + string fromDate = 4; + Schedule schedule = 5; + bool driver = 6; + bool passenger = 7; + string toDate = 8; + int32 marginDuration = 9; + MarginDurations marginDurations = 10; + int32 seatsPassenger = 11; + int32 seatsDriver = 12; + bool strict = 13; + Algorithm algorithm = 14; + int32 remoteness = 15; + bool useProportion = 16; + int32 proportion = 17; + bool useAzimuth = 18; + int32 azimuthMargin = 19; + float maxDetourDistanceRatio = 20; + float maxDetourDurationRatio = 21; + repeated int32 exclusions = 22; } message Coordinates { @@ -69,9 +68,3 @@ message Matches { repeated Match data = 1; int32 total = 2; } - -enum Mode { - MATCH = 0; - PUBLISH = 1; - PUBLISH_AND_MATCH = 2; -} diff --git a/src/modules/matcher/adapters/secondaries/default-params.provider.ts b/src/modules/matcher/adapters/secondaries/default-params.provider.ts index 5bb3158..d331919 100644 --- a/src/modules/matcher/adapters/secondaries/default-params.provider.ts +++ b/src/modules/matcher/adapters/secondaries/default-params.provider.ts @@ -14,21 +14,21 @@ export class DefaultParamsProvider { DEFAULT_TIMEZONE: this.configService.get('DEFAULT_TIMEZONE'), DEFAULT_SEATS: parseInt(this.configService.get('DEFAULT_SEATS')), DEFAULT_ALGORITHM_SETTINGS: { - algorithm: this.configService.get('ALGORITHM'), - strict: !!parseInt(this.configService.get('STRICT_ALGORITHM')), - remoteness: parseInt(this.configService.get('REMOTENESS')), - useProportion: !!parseInt(this.configService.get('USE_PROPORTION')), - proportion: parseInt(this.configService.get('PROPORTION')), - useAzimuth: !!parseInt(this.configService.get('USE_AZIMUTH')), - azimuthMargin: parseInt(this.configService.get('AZIMUTH_MARGIN')), - maxDetourDistanceRatio: parseFloat( + ALGORITHM: this.configService.get('ALGORITHM'), + STRICT: !!parseInt(this.configService.get('STRICT_ALGORITHM')), + REMOTENESS: parseInt(this.configService.get('REMOTENESS')), + USE_PROPORTION: !!parseInt(this.configService.get('USE_PROPORTION')), + PROPORTION: parseInt(this.configService.get('PROPORTION')), + USE_AZIMUTH: !!parseInt(this.configService.get('USE_AZIMUTH')), + AZIMUTH_MARGIN: parseInt(this.configService.get('AZIMUTH_MARGIN')), + MAX_DETOUR_DISTANCE_RATIO: parseFloat( this.configService.get('MAX_DETOUR_DISTANCE_RATIO'), ), - maxDetourDurationRatio: parseFloat( + MAX_DETOUR_DURATION_RATIO: parseFloat( this.configService.get('MAX_DETOUR_DURATION_RATIO'), ), - georouterType: this.configService.get('GEOROUTER_TYPE'), - georouterUrl: this.configService.get('GEOROUTER_URL'), + GEOROUTER_TYPE: this.configService.get('GEOROUTER_TYPE'), + GEOROUTER_URL: this.configService.get('GEOROUTER_URL'), }, }; }; diff --git a/src/modules/matcher/adapters/secondaries/graphhopper-georouter.ts b/src/modules/matcher/adapters/secondaries/graphhopper-georouter.ts index 935eddb..472a333 100644 --- a/src/modules/matcher/adapters/secondaries/graphhopper-georouter.ts +++ b/src/modules/matcher/adapters/secondaries/graphhopper-georouter.ts @@ -7,7 +7,7 @@ import { catchError, lastValueFrom, map } from 'rxjs'; import { AxiosError, AxiosResponse } from 'axios'; import { IGeodesic } from '../../../geography/domain/interfaces/geodesic.interface'; import { NamedRoute } from '../../domain/entities/ecosystem/named-route'; -import { Route } from '../../domain/entities/ecosystem/route'; +import { MatcherRoute } from '../../domain/entities/ecosystem/matcher-route'; import { SpacetimePoint } from '../../domain/entities/ecosystem/spacetime-point'; import { MatcherException, @@ -106,8 +106,8 @@ export class GraphhopperGeorouter implements IGeorouter { private createRoute = ( response: AxiosResponse, - ): Route => { - const route = new Route(this.geodesic); + ): MatcherRoute => { + const route = new MatcherRoute(this.geodesic); if (response.data.paths && response.data.paths[0]) { const shortestPath = response.data.paths[0]; route.distance = shortestPath.distance ?? 0; diff --git a/src/modules/matcher/domain/entities/ecosystem/algorithm-settings.ts b/src/modules/matcher/domain/entities/ecosystem/algorithm-settings.ts index 158ddfd..0e5dd92 100644 --- a/src/modules/matcher/domain/entities/ecosystem/algorithm-settings.ts +++ b/src/modules/matcher/domain/entities/ecosystem/algorithm-settings.ts @@ -27,33 +27,33 @@ export class AlgorithmSettings { ) { this.algorithmSettingsRequest = algorithmSettingsRequest; this.algorithmType = - algorithmSettingsRequest.algorithm ?? defaultAlgorithmSettings.algorithm; + algorithmSettingsRequest.algorithm ?? defaultAlgorithmSettings.ALGORITHM; this.strict = - algorithmSettingsRequest.strict ?? defaultAlgorithmSettings.strict; + algorithmSettingsRequest.strict ?? defaultAlgorithmSettings.STRICT; this.remoteness = algorithmSettingsRequest.remoteness ? Math.abs(algorithmSettingsRequest.remoteness) - : defaultAlgorithmSettings.remoteness; + : defaultAlgorithmSettings.REMOTENESS; this.useProportion = algorithmSettingsRequest.useProportion ?? - defaultAlgorithmSettings.useProportion; + defaultAlgorithmSettings.USE_PROPORTION; this.proportion = algorithmSettingsRequest.proportion ? Math.abs(algorithmSettingsRequest.proportion) - : defaultAlgorithmSettings.proportion; + : defaultAlgorithmSettings.PROPORTION; this.useAzimuth = algorithmSettingsRequest.useAzimuth ?? - defaultAlgorithmSettings.useAzimuth; + defaultAlgorithmSettings.USE_AZIMUTH; this.azimuthMargin = algorithmSettingsRequest.azimuthMargin ? Math.abs(algorithmSettingsRequest.azimuthMargin) - : defaultAlgorithmSettings.azimuthMargin; + : defaultAlgorithmSettings.AZIMUTH_MARGIN; this.maxDetourDistanceRatio = algorithmSettingsRequest.maxDetourDistanceRatio ?? - defaultAlgorithmSettings.maxDetourDistanceRatio; + defaultAlgorithmSettings.MAX_DETOUR_DISTANCE_RATIO; this.maxDetourDurationRatio = algorithmSettingsRequest.maxDetourDurationRatio ?? - defaultAlgorithmSettings.maxDetourDurationRatio; + defaultAlgorithmSettings.MAX_DETOUR_DURATION_RATIO; this.georouter = georouterCreator.create( - defaultAlgorithmSettings.georouterType, - defaultAlgorithmSettings.georouterUrl, + defaultAlgorithmSettings.GEOROUTER_TYPE, + defaultAlgorithmSettings.GEOROUTER_URL, ); if (this.strict) { this.restrict = frequency; diff --git a/src/modules/matcher/domain/entities/ecosystem/geography.ts b/src/modules/matcher/domain/entities/ecosystem/geography.ts index 9afd432..abf9be7 100644 --- a/src/modules/matcher/domain/entities/ecosystem/geography.ts +++ b/src/modules/matcher/domain/entities/ecosystem/geography.ts @@ -5,7 +5,7 @@ import { import { IRequestGeography } from '../../interfaces/geography-request.interface'; import { PointType } from '../../../../geography/domain/types/point-type.enum'; import { Point } from '../../../../geography/domain/types/point.type'; -import { Route } from './route'; +import { MatcherRoute } from './matcher-route'; import { Role } from '../../types/role.enum'; import { IGeorouter } from '../../interfaces/georouter.interface'; import { Waypoint } from './waypoint'; @@ -23,8 +23,8 @@ export class Geography { originType: PointType; destinationType: PointType; timezones: string[]; - driverRoute: Route; - passengerRoute: Route; + driverRoute: MatcherRoute; + passengerRoute: MatcherRoute; timezoneFinder: IFindTimezone; constructor( diff --git a/src/modules/matcher/domain/entities/ecosystem/matcher-route.ts b/src/modules/matcher/domain/entities/ecosystem/matcher-route.ts new file mode 100644 index 0000000..197741d --- /dev/null +++ b/src/modules/matcher/domain/entities/ecosystem/matcher-route.ts @@ -0,0 +1,16 @@ +import { Route } from '../../../../geography/domain/entities/route'; +import { IGeodesic } from '../../../../geography/domain/interfaces/geodesic.interface'; +import { Waypoint } from './waypoint'; + +export class MatcherRoute extends Route { + waypoints: Waypoint[]; + + constructor(geodesic: IGeodesic) { + super(geodesic); + } + + setWaypoints = (waypoints: Waypoint[]): void => { + this.waypoints = waypoints; + this.setAzimuth(waypoints.map((waypoint) => waypoint.point)); + }; +} diff --git a/src/modules/matcher/domain/entities/ecosystem/named-route.ts b/src/modules/matcher/domain/entities/ecosystem/named-route.ts index c57f928..c026769 100644 --- a/src/modules/matcher/domain/entities/ecosystem/named-route.ts +++ b/src/modules/matcher/domain/entities/ecosystem/named-route.ts @@ -1,6 +1,6 @@ -import { Route } from './route'; +import { MatcherRoute } from './matcher-route'; export type NamedRoute = { key: string; - route: Route; + route: MatcherRoute; }; diff --git a/src/modules/matcher/domain/types/default-algorithm-settings.type.ts b/src/modules/matcher/domain/types/default-algorithm-settings.type.ts index a9edb47..98fa3b1 100644 --- a/src/modules/matcher/domain/types/default-algorithm-settings.type.ts +++ b/src/modules/matcher/domain/types/default-algorithm-settings.type.ts @@ -1,15 +1,15 @@ import { AlgorithmType } from './algorithm.enum'; export type DefaultAlgorithmSettings = { - algorithm: AlgorithmType; - strict: boolean; - remoteness: number; - useProportion: boolean; - proportion: number; - useAzimuth: boolean; - azimuthMargin: number; - maxDetourDistanceRatio: number; - maxDetourDurationRatio: number; - georouterType: string; - georouterUrl: string; + ALGORITHM: AlgorithmType; + STRICT: boolean; + REMOTENESS: number; + USE_PROPORTION: boolean; + PROPORTION: number; + USE_AZIMUTH: boolean; + AZIMUTH_MARGIN: number; + MAX_DETOUR_DISTANCE_RATIO: number; + MAX_DETOUR_DURATION_RATIO: number; + GEOROUTER_TYPE: string; + GEOROUTER_URL: string; }; diff --git a/src/modules/matcher/domain/usecases/match.usecase.ts b/src/modules/matcher/domain/usecases/match.usecase.ts index c834224..8af7355 100644 --- a/src/modules/matcher/domain/usecases/match.usecase.ts +++ b/src/modules/matcher/domain/usecases/match.usecase.ts @@ -4,28 +4,28 @@ import { QueryHandler } from '@nestjs/cqrs'; import { Messager } from '../../adapters/secondaries/messager'; import { MatchQuery } from '../../queries/match.query'; import { Match } from '../entities/ecosystem/match'; -import { ICollection } from '../../../database/src/interfaces/collection.interface'; +import { ICollection } from '../../../database/interfaces/collection.interface'; import { Matcher } from '../entities/engine/matcher'; @QueryHandler(MatchQuery) export class MatchUseCase { constructor( - private readonly _matcher: Matcher, - private readonly _messager: Messager, - @InjectMapper() private readonly _mapper: Mapper, + private readonly matcher: Matcher, + private readonly messager: Messager, + @InjectMapper() private readonly mapper: Mapper, ) {} execute = async (matchQuery: MatchQuery): Promise> => { try { - const data: Match[] = await this._matcher.match(matchQuery); - this._messager.publish('matcher.match', 'match !'); + const data: Match[] = await this.matcher.match(matchQuery); + this.messager.publish('matcher.match', 'match !'); return { data, total: data.length, }; } catch (error) { const err: Error = error; - this._messager.publish( + this.messager.publish( 'logging.matcher.match.crit', JSON.stringify({ matchQuery, diff --git a/src/modules/matcher/matcher.module.ts b/src/modules/matcher/matcher.module.ts index 39e1dfc..fe84110 100644 --- a/src/modules/matcher/matcher.module.ts +++ b/src/modules/matcher/matcher.module.ts @@ -19,6 +19,7 @@ import { AlgorithmFactoryCreator } from './domain/entities/engine/factory/algori import { TimezoneFinder } from './adapters/secondaries/timezone-finder'; import { GeoTimezoneFinder } from '../geography/adapters/secondaries/geo-timezone-finder'; import { GeographyModule } from '../geography/geography.module'; +import { TimeConverter } from './adapters/secondaries/time-converter'; @Module({ imports: [ @@ -62,6 +63,7 @@ import { GeographyModule } from '../geography/geography.module'; GeorouterCreator, MatcherGeodesic, TimezoneFinder, + TimeConverter, Matcher, AlgorithmFactoryCreator, GeoTimezoneFinder, diff --git a/src/modules/matcher/tests/unit/domain/ecosystem/geography.spec.ts b/src/modules/matcher/tests/unit/domain/ecosystem/geography.spec.ts index 82925c7..3480b2b 100644 --- a/src/modules/matcher/tests/unit/domain/ecosystem/geography.spec.ts +++ b/src/modules/matcher/tests/unit/domain/ecosystem/geography.spec.ts @@ -5,7 +5,7 @@ import { } from '../../../../domain/entities/ecosystem/geography'; import { Role } from '../../../../domain/types/role.enum'; import { NamedRoute } from '../../../../domain/entities/ecosystem/named-route'; -import { Route } from '../../../../domain/entities/ecosystem/route'; +import { MatcherRoute } from '../../../../domain/entities/ecosystem/matcher-route'; import { IGeodesic } from '../../../../../geography/domain/interfaces/geodesic.interface'; import { PointType } from '../../../../../geography/domain/types/point-type.enum'; @@ -31,7 +31,7 @@ const mockGeorouter = { return [ { key: RouteKey.COMMON, - route: new Route(mockGeodesic), + route: new MatcherRoute(mockGeodesic), }, ]; }) @@ -39,11 +39,11 @@ const mockGeorouter = { return [ { key: RouteKey.DRIVER, - route: new Route(mockGeodesic), + route: new MatcherRoute(mockGeodesic), }, { key: RouteKey.PASSENGER, - route: new Route(mockGeodesic), + route: new MatcherRoute(mockGeodesic), }, ]; }) @@ -51,7 +51,7 @@ const mockGeorouter = { return [ { key: RouteKey.DRIVER, - route: new Route(mockGeodesic), + route: new MatcherRoute(mockGeodesic), }, ]; }) @@ -59,7 +59,7 @@ const mockGeorouter = { return [ { key: RouteKey.PASSENGER, - route: new Route(mockGeodesic), + route: new MatcherRoute(mockGeodesic), }, ]; }), diff --git a/src/modules/matcher/tests/unit/domain/ecosystem/route.spec.ts b/src/modules/matcher/tests/unit/domain/ecosystem/matcher-route.spec.ts similarity index 84% rename from src/modules/matcher/tests/unit/domain/ecosystem/route.spec.ts rename to src/modules/matcher/tests/unit/domain/ecosystem/matcher-route.spec.ts index 70c9460..feb1b6e 100644 --- a/src/modules/matcher/tests/unit/domain/ecosystem/route.spec.ts +++ b/src/modules/matcher/tests/unit/domain/ecosystem/matcher-route.spec.ts @@ -1,4 +1,4 @@ -import { Route } from '../../../../domain/entities/ecosystem/route'; +import { MatcherRoute } from '../../../../domain/entities/ecosystem/matcher-route'; import { SpacetimePoint } from '../../../../domain/entities/ecosystem/spacetime-point'; import { Waypoint } from '../../../../domain/entities/ecosystem/waypoint'; @@ -17,13 +17,13 @@ const mockGeodesic = { }), }; -describe('Route entity', () => { +describe('Matcher route entity', () => { it('should be defined', () => { - const route = new Route(mockGeodesic); + const route = new MatcherRoute(mockGeodesic); expect(route).toBeDefined(); }); it('should set waypoints and geodesic values for a route', () => { - const route = new Route(mockGeodesic); + const route = new MatcherRoute(mockGeodesic); const waypoint1: Waypoint = new Waypoint({ lon: 0, lat: 0, @@ -39,7 +39,7 @@ describe('Route entity', () => { expect(route.distanceAzimuth).toBe(50000); }); it('should set points and geodesic values for a route', () => { - const route = new Route(mockGeodesic); + const route = new MatcherRoute(mockGeodesic); route.setPoints([ { lon: 10, @@ -56,7 +56,7 @@ describe('Route entity', () => { expect(route.distanceAzimuth).toBe(60000); }); it('should set spacetimePoints for a route', () => { - const route = new Route(mockGeodesic); + const route = new MatcherRoute(mockGeodesic); const spacetimePoint1 = new SpacetimePoint({ lon: 0, lat: 0 }, 0, 0); const spacetimePoint2 = new SpacetimePoint({ lon: 10, lat: 10 }, 500, 5000); route.setSpacetimePoints([spacetimePoint1, spacetimePoint2]); diff --git a/src/modules/matcher/tests/unit/domain/engine/algorithm-factory-creator.spec.ts b/src/modules/matcher/tests/unit/domain/engine/algorithm-factory-creator.spec.ts index e9c076b..7c715a6 100644 --- a/src/modules/matcher/tests/unit/domain/engine/algorithm-factory-creator.spec.ts +++ b/src/modules/matcher/tests/unit/domain/engine/algorithm-factory-creator.spec.ts @@ -24,17 +24,17 @@ const defaultParams: IDefaultParams = { DEFAULT_TIMEZONE: 'Europe/Paris', DEFAULT_SEATS: 3, DEFAULT_ALGORITHM_SETTINGS: { - algorithm: AlgorithmType.CLASSIC, - strict: false, - remoteness: 15000, - useProportion: true, - proportion: 0.3, - useAzimuth: true, - azimuthMargin: 10, - maxDetourDistanceRatio: 0.3, - maxDetourDurationRatio: 0.3, - georouterType: 'graphhopper', - georouterUrl: 'http://localhost', + ALGORITHM: AlgorithmType.CLASSIC, + STRICT: false, + REMOTENESS: 15000, + USE_PROPORTION: true, + PROPORTION: 0.3, + USE_AZIMUTH: true, + AZIMUTH_MARGIN: 10, + MAX_DETOUR_DISTANCE_RATIO: 0.3, + MAX_DETOUR_DURATION_RATIO: 0.3, + GEOROUTER_TYPE: 'graphhopper', + GEOROUTER_URL: 'http://localhost', }, }; diff --git a/src/modules/matcher/tests/unit/domain/engine/algorithm-factory.abstract.spec.ts b/src/modules/matcher/tests/unit/domain/engine/algorithm-factory.abstract.spec.ts index f4b62c2..74e2494 100644 --- a/src/modules/matcher/tests/unit/domain/engine/algorithm-factory.abstract.spec.ts +++ b/src/modules/matcher/tests/unit/domain/engine/algorithm-factory.abstract.spec.ts @@ -26,17 +26,17 @@ const defaultParams: IDefaultParams = { DEFAULT_TIMEZONE: 'Europe/Paris', DEFAULT_SEATS: 3, DEFAULT_ALGORITHM_SETTINGS: { - algorithm: AlgorithmType.CLASSIC, - strict: false, - remoteness: 15000, - useProportion: true, - proportion: 0.3, - useAzimuth: true, - azimuthMargin: 10, - maxDetourDistanceRatio: 0.3, - maxDetourDurationRatio: 0.3, - georouterType: 'graphhopper', - georouterUrl: 'http://localhost', + ALGORITHM: AlgorithmType.CLASSIC, + STRICT: false, + REMOTENESS: 15000, + USE_PROPORTION: true, + PROPORTION: 0.3, + USE_AZIMUTH: true, + AZIMUTH_MARGIN: 10, + MAX_DETOUR_DISTANCE_RATIO: 0.3, + MAX_DETOUR_DURATION_RATIO: 0.3, + GEOROUTER_TYPE: 'graphhopper', + GEOROUTER_URL: 'http://localhost', }, }; diff --git a/src/modules/matcher/tests/unit/domain/engine/classic-algorithm-factory.spec.ts b/src/modules/matcher/tests/unit/domain/engine/classic-algorithm-factory.spec.ts index 070b8ee..4859189 100644 --- a/src/modules/matcher/tests/unit/domain/engine/classic-algorithm-factory.spec.ts +++ b/src/modules/matcher/tests/unit/domain/engine/classic-algorithm-factory.spec.ts @@ -24,17 +24,17 @@ const defaultParams: IDefaultParams = { DEFAULT_TIMEZONE: 'Europe/Paris', DEFAULT_SEATS: 3, DEFAULT_ALGORITHM_SETTINGS: { - algorithm: AlgorithmType.CLASSIC, - strict: false, - remoteness: 15000, - useProportion: true, - proportion: 0.3, - useAzimuth: true, - azimuthMargin: 10, - maxDetourDistanceRatio: 0.3, - maxDetourDurationRatio: 0.3, - georouterType: 'graphhopper', - georouterUrl: 'http://localhost', + ALGORITHM: AlgorithmType.CLASSIC, + STRICT: false, + REMOTENESS: 15000, + USE_PROPORTION: true, + PROPORTION: 0.3, + USE_AZIMUTH: true, + AZIMUTH_MARGIN: 10, + MAX_DETOUR_DISTANCE_RATIO: 0.3, + MAX_DETOUR_DURATION_RATIO: 0.3, + GEOROUTER_TYPE: 'graphhopper', + GEOROUTER_URL: 'http://localhost', }, }; diff --git a/src/modules/matcher/tests/unit/domain/engine/classic-geo.filter.processor.spec.ts b/src/modules/matcher/tests/unit/domain/engine/classic-geo.filter.processor.spec.ts index a6272d6..ea3b506 100644 --- a/src/modules/matcher/tests/unit/domain/engine/classic-geo.filter.processor.spec.ts +++ b/src/modules/matcher/tests/unit/domain/engine/classic-geo.filter.processor.spec.ts @@ -24,17 +24,17 @@ const defaultParams: IDefaultParams = { DEFAULT_TIMEZONE: 'Europe/Paris', DEFAULT_SEATS: 3, DEFAULT_ALGORITHM_SETTINGS: { - algorithm: AlgorithmType.CLASSIC, - strict: false, - remoteness: 15000, - useProportion: true, - proportion: 0.3, - useAzimuth: true, - azimuthMargin: 10, - maxDetourDistanceRatio: 0.3, - maxDetourDurationRatio: 0.3, - georouterType: 'graphhopper', - georouterUrl: 'http://localhost', + ALGORITHM: AlgorithmType.CLASSIC, + STRICT: false, + REMOTENESS: 15000, + USE_PROPORTION: true, + PROPORTION: 0.3, + USE_AZIMUTH: true, + AZIMUTH_MARGIN: 10, + MAX_DETOUR_DISTANCE_RATIO: 0.3, + MAX_DETOUR_DURATION_RATIO: 0.3, + GEOROUTER_TYPE: 'graphhopper', + GEOROUTER_URL: 'http://localhost', }, }; diff --git a/src/modules/matcher/tests/unit/domain/engine/classic-time.filter.processor.spec.ts b/src/modules/matcher/tests/unit/domain/engine/classic-time.filter.processor.spec.ts index 825a186..c489684 100644 --- a/src/modules/matcher/tests/unit/domain/engine/classic-time.filter.processor.spec.ts +++ b/src/modules/matcher/tests/unit/domain/engine/classic-time.filter.processor.spec.ts @@ -24,17 +24,17 @@ const defaultParams: IDefaultParams = { DEFAULT_TIMEZONE: 'Europe/Paris', DEFAULT_SEATS: 3, DEFAULT_ALGORITHM_SETTINGS: { - algorithm: AlgorithmType.CLASSIC, - strict: false, - remoteness: 15000, - useProportion: true, - proportion: 0.3, - useAzimuth: true, - azimuthMargin: 10, - maxDetourDistanceRatio: 0.3, - maxDetourDurationRatio: 0.3, - georouterType: 'graphhopper', - georouterUrl: 'http://localhost', + ALGORITHM: AlgorithmType.CLASSIC, + STRICT: false, + REMOTENESS: 15000, + USE_PROPORTION: true, + PROPORTION: 0.3, + USE_AZIMUTH: true, + AZIMUTH_MARGIN: 10, + MAX_DETOUR_DISTANCE_RATIO: 0.3, + MAX_DETOUR_DURATION_RATIO: 0.3, + GEOROUTER_TYPE: 'graphhopper', + GEOROUTER_URL: 'http://localhost', }, }; diff --git a/src/modules/matcher/tests/unit/domain/engine/classic-waypoint.completer.processor.spec.ts b/src/modules/matcher/tests/unit/domain/engine/classic-waypoint.completer.processor.spec.ts index 961db3f..68cfca7 100644 --- a/src/modules/matcher/tests/unit/domain/engine/classic-waypoint.completer.processor.spec.ts +++ b/src/modules/matcher/tests/unit/domain/engine/classic-waypoint.completer.processor.spec.ts @@ -24,17 +24,17 @@ const defaultParams: IDefaultParams = { DEFAULT_TIMEZONE: 'Europe/Paris', DEFAULT_SEATS: 3, DEFAULT_ALGORITHM_SETTINGS: { - algorithm: AlgorithmType.CLASSIC, - strict: false, - remoteness: 15000, - useProportion: true, - proportion: 0.3, - useAzimuth: true, - azimuthMargin: 10, - maxDetourDistanceRatio: 0.3, - maxDetourDurationRatio: 0.3, - georouterType: 'graphhopper', - georouterUrl: 'http://localhost', + ALGORITHM: AlgorithmType.CLASSIC, + STRICT: false, + REMOTENESS: 15000, + USE_PROPORTION: true, + PROPORTION: 0.3, + USE_AZIMUTH: true, + AZIMUTH_MARGIN: 10, + MAX_DETOUR_DISTANCE_RATIO: 0.3, + MAX_DETOUR_DURATION_RATIO: 0.3, + GEOROUTER_TYPE: 'graphhopper', + GEOROUTER_URL: 'http://localhost', }, }; diff --git a/src/modules/matcher/tests/unit/domain/engine/classic.selector.spec.ts b/src/modules/matcher/tests/unit/domain/engine/classic.selector.spec.ts index d20cd56..19dcfdb 100644 --- a/src/modules/matcher/tests/unit/domain/engine/classic.selector.spec.ts +++ b/src/modules/matcher/tests/unit/domain/engine/classic.selector.spec.ts @@ -24,17 +24,17 @@ const defaultParams: IDefaultParams = { DEFAULT_TIMEZONE: 'Europe/Paris', DEFAULT_SEATS: 3, DEFAULT_ALGORITHM_SETTINGS: { - algorithm: AlgorithmType.CLASSIC, - strict: false, - remoteness: 15000, - useProportion: true, - proportion: 0.3, - useAzimuth: true, - azimuthMargin: 10, - maxDetourDistanceRatio: 0.3, - maxDetourDurationRatio: 0.3, - georouterType: 'graphhopper', - georouterUrl: 'http://localhost', + ALGORITHM: AlgorithmType.CLASSIC, + STRICT: false, + REMOTENESS: 15000, + USE_PROPORTION: true, + PROPORTION: 0.3, + USE_AZIMUTH: true, + AZIMUTH_MARGIN: 10, + MAX_DETOUR_DISTANCE_RATIO: 0.3, + MAX_DETOUR_DURATION_RATIO: 0.3, + GEOROUTER_TYPE: 'graphhopper', + GEOROUTER_URL: 'http://localhost', }, }; diff --git a/src/modules/matcher/tests/unit/domain/engine/completer.abstract.spec.ts b/src/modules/matcher/tests/unit/domain/engine/completer.abstract.spec.ts index 3dc02f4..7d4bdb9 100644 --- a/src/modules/matcher/tests/unit/domain/engine/completer.abstract.spec.ts +++ b/src/modules/matcher/tests/unit/domain/engine/completer.abstract.spec.ts @@ -24,17 +24,17 @@ const defaultParams: IDefaultParams = { DEFAULT_TIMEZONE: 'Europe/Paris', DEFAULT_SEATS: 3, DEFAULT_ALGORITHM_SETTINGS: { - algorithm: AlgorithmType.CLASSIC, - strict: false, - remoteness: 15000, - useProportion: true, - proportion: 0.3, - useAzimuth: true, - azimuthMargin: 10, - maxDetourDistanceRatio: 0.3, - maxDetourDurationRatio: 0.3, - georouterType: 'graphhopper', - georouterUrl: 'http://localhost', + ALGORITHM: AlgorithmType.CLASSIC, + STRICT: false, + REMOTENESS: 15000, + USE_PROPORTION: true, + PROPORTION: 0.3, + USE_AZIMUTH: true, + AZIMUTH_MARGIN: 10, + MAX_DETOUR_DISTANCE_RATIO: 0.3, + MAX_DETOUR_DURATION_RATIO: 0.3, + GEOROUTER_TYPE: 'graphhopper', + GEOROUTER_URL: 'http://localhost', }, }; diff --git a/src/modules/matcher/tests/unit/domain/engine/filter.abstract.spec.ts b/src/modules/matcher/tests/unit/domain/engine/filter.abstract.spec.ts index 09fef45..3e6ceb6 100644 --- a/src/modules/matcher/tests/unit/domain/engine/filter.abstract.spec.ts +++ b/src/modules/matcher/tests/unit/domain/engine/filter.abstract.spec.ts @@ -24,17 +24,17 @@ const defaultParams: IDefaultParams = { DEFAULT_TIMEZONE: 'Europe/Paris', DEFAULT_SEATS: 3, DEFAULT_ALGORITHM_SETTINGS: { - algorithm: AlgorithmType.CLASSIC, - strict: false, - remoteness: 15000, - useProportion: true, - proportion: 0.3, - useAzimuth: true, - azimuthMargin: 10, - maxDetourDistanceRatio: 0.3, - maxDetourDurationRatio: 0.3, - georouterType: 'graphhopper', - georouterUrl: 'http://localhost', + ALGORITHM: AlgorithmType.CLASSIC, + STRICT: false, + REMOTENESS: 15000, + USE_PROPORTION: true, + PROPORTION: 0.3, + USE_AZIMUTH: true, + AZIMUTH_MARGIN: 10, + MAX_DETOUR_DISTANCE_RATIO: 0.3, + MAX_DETOUR_DURATION_RATIO: 0.3, + GEOROUTER_TYPE: 'graphhopper', + GEOROUTER_URL: 'http://localhost', }, }; diff --git a/src/modules/matcher/tests/unit/domain/engine/journey.completer.processor.spec.ts b/src/modules/matcher/tests/unit/domain/engine/journey.completer.processor.spec.ts index 5f6c135..85bd6d8 100644 --- a/src/modules/matcher/tests/unit/domain/engine/journey.completer.processor.spec.ts +++ b/src/modules/matcher/tests/unit/domain/engine/journey.completer.processor.spec.ts @@ -24,17 +24,17 @@ const defaultParams: IDefaultParams = { DEFAULT_TIMEZONE: 'Europe/Paris', DEFAULT_SEATS: 3, DEFAULT_ALGORITHM_SETTINGS: { - algorithm: AlgorithmType.CLASSIC, - strict: false, - remoteness: 15000, - useProportion: true, - proportion: 0.3, - useAzimuth: true, - azimuthMargin: 10, - maxDetourDistanceRatio: 0.3, - maxDetourDurationRatio: 0.3, - georouterType: 'graphhopper', - georouterUrl: 'http://localhost', + ALGORITHM: AlgorithmType.CLASSIC, + STRICT: false, + REMOTENESS: 15000, + USE_PROPORTION: true, + PROPORTION: 0.3, + USE_AZIMUTH: true, + AZIMUTH_MARGIN: 10, + MAX_DETOUR_DISTANCE_RATIO: 0.3, + MAX_DETOUR_DURATION_RATIO: 0.3, + GEOROUTER_TYPE: 'graphhopper', + GEOROUTER_URL: 'http://localhost', }, }; diff --git a/src/modules/matcher/tests/unit/domain/engine/matcher.spec.ts b/src/modules/matcher/tests/unit/domain/engine/matcher.spec.ts index c4ce2fd..5b73254 100644 --- a/src/modules/matcher/tests/unit/domain/engine/matcher.spec.ts +++ b/src/modules/matcher/tests/unit/domain/engine/matcher.spec.ts @@ -36,17 +36,17 @@ const defaultParams: IDefaultParams = { DEFAULT_TIMEZONE: 'Europe/Paris', DEFAULT_SEATS: 3, DEFAULT_ALGORITHM_SETTINGS: { - algorithm: AlgorithmType.CLASSIC, - strict: false, - remoteness: 15000, - useProportion: true, - proportion: 0.3, - useAzimuth: true, - azimuthMargin: 10, - maxDetourDistanceRatio: 0.3, - maxDetourDurationRatio: 0.3, - georouterType: 'graphhopper', - georouterUrl: 'http://localhost', + ALGORITHM: AlgorithmType.CLASSIC, + STRICT: false, + REMOTENESS: 15000, + USE_PROPORTION: true, + PROPORTION: 0.3, + USE_AZIMUTH: true, + AZIMUTH_MARGIN: 10, + MAX_DETOUR_DISTANCE_RATIO: 0.3, + MAX_DETOUR_DURATION_RATIO: 0.3, + GEOROUTER_TYPE: 'graphhopper', + GEOROUTER_URL: 'http://localhost', }, }; diff --git a/src/modules/matcher/tests/unit/domain/engine/processor.abstract.spec.ts b/src/modules/matcher/tests/unit/domain/engine/processor.abstract.spec.ts index 34948a6..c83eb82 100644 --- a/src/modules/matcher/tests/unit/domain/engine/processor.abstract.spec.ts +++ b/src/modules/matcher/tests/unit/domain/engine/processor.abstract.spec.ts @@ -24,17 +24,17 @@ const defaultParams: IDefaultParams = { DEFAULT_TIMEZONE: 'Europe/Paris', DEFAULT_SEATS: 3, DEFAULT_ALGORITHM_SETTINGS: { - algorithm: AlgorithmType.CLASSIC, - strict: false, - remoteness: 15000, - useProportion: true, - proportion: 0.3, - useAzimuth: true, - azimuthMargin: 10, - maxDetourDistanceRatio: 0.3, - maxDetourDurationRatio: 0.3, - georouterType: 'graphhopper', - georouterUrl: 'http://localhost', + ALGORITHM: AlgorithmType.CLASSIC, + STRICT: false, + REMOTENESS: 15000, + USE_PROPORTION: true, + PROPORTION: 0.3, + USE_AZIMUTH: true, + AZIMUTH_MARGIN: 10, + MAX_DETOUR_DISTANCE_RATIO: 0.3, + MAX_DETOUR_DURATION_RATIO: 0.3, + GEOROUTER_TYPE: 'graphhopper', + GEOROUTER_URL: 'http://localhost', }, }; diff --git a/src/modules/matcher/tests/unit/domain/engine/route.completer.processor.spec.ts b/src/modules/matcher/tests/unit/domain/engine/route.completer.processor.spec.ts index a1ac083..cdd2a5a 100644 --- a/src/modules/matcher/tests/unit/domain/engine/route.completer.processor.spec.ts +++ b/src/modules/matcher/tests/unit/domain/engine/route.completer.processor.spec.ts @@ -24,17 +24,17 @@ const defaultParams: IDefaultParams = { DEFAULT_TIMEZONE: 'Europe/Paris', DEFAULT_SEATS: 3, DEFAULT_ALGORITHM_SETTINGS: { - algorithm: AlgorithmType.CLASSIC, - strict: false, - remoteness: 15000, - useProportion: true, - proportion: 0.3, - useAzimuth: true, - azimuthMargin: 10, - maxDetourDistanceRatio: 0.3, - maxDetourDurationRatio: 0.3, - georouterType: 'graphhopper', - georouterUrl: 'http://localhost', + ALGORITHM: AlgorithmType.CLASSIC, + STRICT: false, + REMOTENESS: 15000, + USE_PROPORTION: true, + PROPORTION: 0.3, + USE_AZIMUTH: true, + AZIMUTH_MARGIN: 10, + MAX_DETOUR_DISTANCE_RATIO: 0.3, + MAX_DETOUR_DURATION_RATIO: 0.3, + GEOROUTER_TYPE: 'graphhopper', + GEOROUTER_URL: 'http://localhost', }, }; diff --git a/src/modules/matcher/tests/unit/domain/engine/selector.abstract.spec.ts b/src/modules/matcher/tests/unit/domain/engine/selector.abstract.spec.ts index a681135..677e43f 100644 --- a/src/modules/matcher/tests/unit/domain/engine/selector.abstract.spec.ts +++ b/src/modules/matcher/tests/unit/domain/engine/selector.abstract.spec.ts @@ -24,17 +24,17 @@ const defaultParams: IDefaultParams = { DEFAULT_TIMEZONE: 'Europe/Paris', DEFAULT_SEATS: 3, DEFAULT_ALGORITHM_SETTINGS: { - algorithm: AlgorithmType.CLASSIC, - strict: false, - remoteness: 15000, - useProportion: true, - proportion: 0.3, - useAzimuth: true, - azimuthMargin: 10, - maxDetourDistanceRatio: 0.3, - maxDetourDurationRatio: 0.3, - georouterType: 'graphhopper', - georouterUrl: 'http://localhost', + ALGORITHM: AlgorithmType.CLASSIC, + STRICT: false, + REMOTENESS: 15000, + USE_PROPORTION: true, + PROPORTION: 0.3, + USE_AZIMUTH: true, + AZIMUTH_MARGIN: 10, + MAX_DETOUR_DISTANCE_RATIO: 0.3, + MAX_DETOUR_DURATION_RATIO: 0.3, + GEOROUTER_TYPE: 'graphhopper', + GEOROUTER_URL: 'http://localhost', }, }; diff --git a/src/modules/matcher/tests/unit/domain/match.usecase.spec.ts b/src/modules/matcher/tests/unit/domain/match.usecase.spec.ts index 10d97aa..8ed9acf 100644 --- a/src/modules/matcher/tests/unit/domain/match.usecase.spec.ts +++ b/src/modules/matcher/tests/unit/domain/match.usecase.spec.ts @@ -49,17 +49,17 @@ const defaultParams: IDefaultParams = { DEFAULT_TIMEZONE: 'Europe/Paris', DEFAULT_SEATS: 3, DEFAULT_ALGORITHM_SETTINGS: { - algorithm: AlgorithmType.CLASSIC, - strict: false, - remoteness: 15000, - useProportion: true, - proportion: 0.3, - useAzimuth: true, - azimuthMargin: 10, - maxDetourDistanceRatio: 0.3, - maxDetourDurationRatio: 0.3, - georouterType: 'graphhopper', - georouterUrl: 'http://localhost', + ALGORITHM: AlgorithmType.CLASSIC, + STRICT: false, + REMOTENESS: 15000, + USE_PROPORTION: true, + PROPORTION: 0.3, + USE_AZIMUTH: true, + AZIMUTH_MARGIN: 10, + MAX_DETOUR_DISTANCE_RATIO: 0.3, + MAX_DETOUR_DURATION_RATIO: 0.3, + GEOROUTER_TYPE: 'graphhopper', + GEOROUTER_URL: 'http://localhost', }, }; diff --git a/src/modules/matcher/tests/unit/queries/match.query.spec.ts b/src/modules/matcher/tests/unit/queries/match.query.spec.ts index bc14991..b11a0e5 100644 --- a/src/modules/matcher/tests/unit/queries/match.query.spec.ts +++ b/src/modules/matcher/tests/unit/queries/match.query.spec.ts @@ -13,17 +13,17 @@ const defaultParams: IDefaultParams = { DEFAULT_TIMEZONE: 'Europe/Paris', DEFAULT_SEATS: 3, DEFAULT_ALGORITHM_SETTINGS: { - algorithm: AlgorithmType.CLASSIC, - strict: false, - remoteness: 15000, - useProportion: true, - proportion: 0.3, - useAzimuth: true, - azimuthMargin: 10, - maxDetourDistanceRatio: 0.3, - maxDetourDurationRatio: 0.3, - georouterType: 'graphhopper', - georouterUrl: 'http://localhost', + ALGORITHM: AlgorithmType.CLASSIC, + STRICT: false, + REMOTENESS: 15000, + USE_PROPORTION: true, + PROPORTION: 0.3, + USE_AZIMUTH: true, + AZIMUTH_MARGIN: 10, + MAX_DETOUR_DISTANCE_RATIO: 0.3, + MAX_DETOUR_DURATION_RATIO: 0.3, + GEOROUTER_TYPE: 'graphhopper', + GEOROUTER_URL: 'http://localhost', }, }; diff --git a/src/modules/utils/exception-code.enum.ts b/src/modules/utils/exception-code.enum.ts new file mode 100644 index 0000000..59554ae --- /dev/null +++ b/src/modules/utils/exception-code.enum.ts @@ -0,0 +1,19 @@ +export enum ExceptionCode { + OK = 0, + CANCELLED = 1, + UNKNOWN = 2, + INVALID_ARGUMENT = 3, + DEADLINE_EXCEEDED = 4, + NOT_FOUND = 5, + ALREADY_EXISTS = 6, + PERMISSION_DENIED = 7, + RESOURCE_EXHAUSTED = 8, + FAILED_PRECONDITION = 9, + ABORTED = 10, + OUT_OF_RANGE = 11, + UNIMPLEMENTED = 12, + INTERNAL = 13, + UNAVAILABLE = 14, + DATA_LOSS = 15, + UNAUTHENTICATED = 16, +}