diff --git a/prisma/migrations/20230406093419_init/migration.sql b/prisma/migrations/20230406093419_init/migration.sql deleted file mode 100644 index 836b706..0000000 --- a/prisma/migrations/20230406093419_init/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 and territory (where is postgis) AND the current schema -SET search_path TO matcher, territory, public; - --- CreateTable -CREATE TABLE "ad" ( - "uuid" UUID NOT NULL, - "driver" BOOLEAN NOT NULL, - "passenger" BOOLEAN NOT NULL, - "frequency" INTEGER NOT NULL, - "from_date" DATE NOT NULL, - "to_date" DATE NOT NULL, - "mon_time" TIMESTAMPTZ NOT NULL, - "tue_time" TIMESTAMPTZ NOT NULL, - "wed_time" TIMESTAMPTZ NOT NULL, - "thu_time" TIMESTAMPTZ NOT NULL, - "fri_time" TIMESTAMPTZ NOT NULL, - "sat_time" TIMESTAMPTZ NOT NULL, - "sun_time" TIMESTAMPTZ NOT NULL, - "mon_margin" INTEGER NOT NULL, - "tue_margin" INTEGER NOT NULL, - "wed_margin" INTEGER NOT NULL, - "thu_margin" INTEGER NOT NULL, - "fri_margin" INTEGER NOT NULL, - "sat_margin" INTEGER NOT NULL, - "sun_margin" INTEGER NOT NULL, - "driver_duration" INTEGER NOT NULL, - "driver_distance" INTEGER NOT NULL, - "passenger_duration" INTEGER NOT NULL, - "passenger_distance" INTEGER NOT NULL, - "origin_type" SMALLINT NOT NULL, - "destination_type" SMALLINT NOT NULL, - "waypoints" geography(LINESTRING) NOT NULL, - "direction" geography(LINESTRING) NOT NULL, - "fwd_azimuth" INTEGER NOT NULL, - "back_azimuth" INTEGER NOT NULL, - "seats_driver" SMALLINT NOT NULL, - "seats_passenger" SMALLINT NOT NULL, - "seats_used" 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_from_date_idx" ON "ad"("from_date"); - --- CreateIndex -CREATE INDEX "ad_to_date_idx" ON "ad"("to_date"); - --- CreateIndex -CREATE INDEX "ad_fwd_azimuth_idx" ON "ad"("fwd_azimuth"); - --- CreateIndex -CREATE INDEX "direction_idx" ON "ad" USING GIST ("direction"); diff --git a/prisma/migrations/20230425130853_/migration.sql b/prisma/migrations/20230425130853_/migration.sql new file mode 100644 index 0000000..8c7b5e6 --- /dev/null +++ b/prisma/migrations/20230425130853_/migration.sql @@ -0,0 +1,65 @@ +-- CreateExtension +CREATE EXTENSION IF NOT EXISTS "postgis"; + +-- Required to use postgis extension : +-- set the search_path to both public and territory (where is postgis) AND the current schema +SET search_path TO matcher, territory, 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/schema.prisma b/prisma/schema.prisma index f9b52df..469a604 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -13,47 +13,47 @@ datasource db { } model Ad { - uuid String @id @default(uuid()) @db.Uuid - driver Boolean - passenger Boolean - frequency Int - from_date DateTime @db.Date - to_date DateTime @db.Date - mon_time DateTime @db.Timestamptz() - tue_time DateTime @db.Timestamptz() - wed_time DateTime @db.Timestamptz() - thu_time DateTime @db.Timestamptz() - fri_time DateTime @db.Timestamptz() - sat_time DateTime @db.Timestamptz() - sun_time DateTime @db.Timestamptz() - mon_margin Int - tue_margin Int - wed_margin Int - thu_margin Int - fri_margin Int - sat_margin Int - sun_margin Int - driver_duration Int - driver_distance Int - passenger_duration Int - passenger_distance Int - origin_type Int @db.SmallInt - destination_type Int @db.SmallInt - waypoints Unsupported("geography(LINESTRING)") - direction Unsupported("geography(LINESTRING)") - fwd_azimuth Int - back_azimuth Int - seats_driver Int @db.SmallInt - seats_passenger Int @db.SmallInt - seats_used Int @db.SmallInt - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt + uuid String @id @default(uuid()) @db.Uuid + driver Boolean + passenger Boolean + frequency Int + 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() + monMargin Int + tueMargin Int + wedMargin Int + thuMargin Int + friMargin Int + satMargin Int + sunMargin Int + driverDuration Int + driverDistance Int + passengerDuration Int + passengerDistance Int + originType Int @db.SmallInt + destinationType Int @db.SmallInt + waypoints Unsupported("geography(LINESTRING)")? + direction Unsupported("geography(LINESTRING)")? + fwdAzimuth Int + backAzimuth Int + seatsDriver Int @db.SmallInt + seatsPassenger Int @db.SmallInt + seatsUsed Int @db.SmallInt + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt @@index([driver]) @@index([passenger]) - @@index([from_date]) - @@index([to_date]) - @@index([fwd_azimuth]) + @@index([fromDate]) + @@index([toDate]) + @@index([fwdAzimuth]) @@index([direction], name: "direction_idx", type: Gist) @@map("ad") } diff --git a/src/modules/ad/ad.modules.ts b/src/modules/ad/ad.modules.ts index 7b98c89..e59f7fd 100644 --- a/src/modules/ad/ad.modules.ts +++ b/src/modules/ad/ad.modules.ts @@ -2,9 +2,16 @@ import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq'; import { Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { AdMessagerController } from './adapters/primaries/ad-messager.controller'; +import { AdProfile } from './mappers/ad.profile'; +import { CreateAdUseCase } from './domain/usecases/create-ad.usecase'; +import { AdRepository } from './adapters/secondaries/ad.repository'; +import { DatabaseModule } from '../database/database.module'; +import { CqrsModule } from '@nestjs/cqrs'; @Module({ imports: [ + DatabaseModule, + CqrsModule, RabbitMQModule.forRootAsync(RabbitMQModule, { imports: [ConfigModule], useFactory: async (configService: ConfigService) => ({ @@ -18,9 +25,7 @@ import { AdMessagerController } from './adapters/primaries/ad-messager.controlle adCreated: { exchange: configService.get('RMQ_EXCHANGE'), routingKey: 'ad.created', - queue: `${configService.get( - 'RMQ_EXCHANGE', - )}-matcher-ad-created`, + queue: 'matcher-ad-created', }, }, uri: configService.get('RMQ_URI'), @@ -31,7 +36,7 @@ import { AdMessagerController } from './adapters/primaries/ad-messager.controlle }), ], controllers: [AdMessagerController], - providers: [], + providers: [AdProfile, AdRepository, CreateAdUseCase], exports: [], }) export class AdModule {} diff --git a/src/modules/ad/adapters/primaries/ad-messager.controller.ts b/src/modules/ad/adapters/primaries/ad-messager.controller.ts index 6d8e1ab..5a09dd4 100644 --- a/src/modules/ad/adapters/primaries/ad-messager.controller.ts +++ b/src/modules/ad/adapters/primaries/ad-messager.controller.ts @@ -1,19 +1,32 @@ import { RabbitSubscribe } from '@golevelup/nestjs-rabbitmq'; import { Controller } from '@nestjs/common'; -// import { Ad } from '../../domain/entities/ad'; +import { Ad } from '../../domain/entities/ad'; +import { InjectMapper } from '@automapper/nestjs'; +import { Mapper } from '@automapper/core'; +import { CommandBus } from '@nestjs/cqrs'; +import { CreateAdCommand } from '../../commands/create-ad.command'; +import { AdPresenter } from './ad.presenter'; +import { CreateAdRequest } from '../../domain/dtos/create-ad.request'; @Controller() export class AdMessagerController { + constructor( + private readonly _commandBus: CommandBus, + @InjectMapper() private readonly _mapper: Mapper, + ) {} + @RabbitSubscribe({ name: 'adCreated', }) - public adCreatedHandler(message: string) { - console.log(JSON.parse(message)); - // try { - // const createdAd: Ad = JSON.parse(message); - // console.log(createdAd); - // } catch (e) { - // console.log('error', e); - // } + async adCreatedHandler(message: string): Promise { + try { + const createAdRequest: CreateAdRequest = JSON.parse(message); + const ad: Ad = await this._commandBus.execute( + new CreateAdCommand(createAdRequest), + ); + return this._mapper.map(ad, Ad, AdPresenter); + } catch (e) { + console.log('error', e); + } } } diff --git a/src/modules/matcher/domain/entities/ecosystem/ad.ts b/src/modules/ad/adapters/primaries/ad.presenter.ts similarity index 74% rename from src/modules/matcher/domain/entities/ecosystem/ad.ts rename to src/modules/ad/adapters/primaries/ad.presenter.ts index 0350f1a..1a4d67c 100644 --- a/src/modules/matcher/domain/entities/ecosystem/ad.ts +++ b/src/modules/ad/adapters/primaries/ad.presenter.ts @@ -1,6 +1,6 @@ import { AutoMap } from '@automapper/classes'; -export class Ad { +export class AdPresenter { @AutoMap() uuid: string; } diff --git a/src/modules/ad/adapters/secondaries/ad.repository.ts b/src/modules/ad/adapters/secondaries/ad.repository.ts new file mode 100644 index 0000000..b100584 --- /dev/null +++ b/src/modules/ad/adapters/secondaries/ad.repository.ts @@ -0,0 +1,79 @@ +import { Injectable } from '@nestjs/common'; +import { MatcherRepository } from '../../../database/src/domain/matcher-repository'; +import { Ad } from '../../domain/entities/ad'; +import { DatabaseException } from '../../../database/src/exceptions/database.exception'; + +@Injectable() +export class AdRepository extends MatcherRepository { + protected _model = 'ad'; + + async createAd(ad: Partial): Promise { + try { + const affectedRowNumber = await this.createWithFields( + this.createFields(ad), + ); + if (affectedRowNumber == 1) { + return this.findOneByUuid(ad.uuid); + } + throw new DatabaseException(); + } catch (e) { + throw e; + } + } + + private createFields(ad: Partial): Partial { + return { + uuid: `'${ad.uuid}'`, + driver: ad.driver ? 'true' : 'false', + passenger: ad.passenger ? 'true' : 'false', + frequency: ad.frequency, + fromDate: `'${ad.fromDate}'`, + toDate: `'${ad.toDate}'`, + monTime: `'${ad.monTime}'`, + tueTime: `'${ad.tueTime}'`, + wedTime: `'${ad.wedTime}'`, + thuTime: `'${ad.thuTime}'`, + friTime: `'${ad.friTime}'`, + satTime: `'${ad.satTime}'`, + sunTime: `'${ad.sunTime}'`, + }; + } +} + +type AdFields = { + uuid: string; + driver: string; + passenger: string; + frequency: number; + fromDate: string; + toDate: string; + monTime: string; + tueTime: string; + wedTime: string; + thuTime: string; + friTime: string; + satTime: string; + sunTime: string; + monMargin: number; + tueMargin: number; + wedMargin: number; + thuMargin: number; + friMargin: number; + satMargin: number; + sunMargin: number; + driverDuration: number; + driverDistance: number; + passengerDuration: number; + passengerDistance: number; + originType: number; + destinationType: number; + waypoints: string; + direction: string; + fwdAzimuth: number; + backAzimuth: number; + seatsDriver: number; + seatsPassenger: number; + seatsUsed: number; + createdAt: string; + updatedAt: string; +}; diff --git a/src/modules/ad/commands/create-ad.command.ts b/src/modules/ad/commands/create-ad.command.ts new file mode 100644 index 0000000..b4f1e8d --- /dev/null +++ b/src/modules/ad/commands/create-ad.command.ts @@ -0,0 +1,9 @@ +import { CreateAdRequest } from '../domain/dtos/create-ad.request'; + +export class CreateAdCommand { + readonly createAdRequest: CreateAdRequest; + + constructor(request: CreateAdRequest) { + this.createAdRequest = request; + } +} diff --git a/src/modules/ad/domain/dtos/create-ad.request.ts b/src/modules/ad/domain/dtos/create-ad.request.ts new file mode 100644 index 0000000..1b0e230 --- /dev/null +++ b/src/modules/ad/domain/dtos/create-ad.request.ts @@ -0,0 +1,116 @@ +import { AutoMap } from '@automapper/classes'; +import { IsBoolean, IsNotEmpty, IsNumber, IsString } from 'class-validator'; + +export class CreateAdRequest { + @IsString() + @IsNotEmpty() + @AutoMap() + uuid: string; + + @IsBoolean() + @AutoMap() + driver: boolean; + + @IsBoolean() + @AutoMap() + passenger: boolean; + + @IsNumber() + @AutoMap() + frequency: number; + + @IsString() + @AutoMap() + fromDate: string; + + @IsString() + @AutoMap() + toDate: string; + + @IsString() + @AutoMap() + monTime: string; + + @IsString() + @AutoMap() + tueTime: string; + + @IsString() + @AutoMap() + wedTime: string; + + @IsString() + @AutoMap() + thuTime: string; + + @IsString() + @AutoMap() + friTime: string; + + @IsString() + @AutoMap() + satTime: string; + + @IsString() + @AutoMap() + sunTime: string; + + @IsNumber() + @AutoMap() + monMargin: number; + + @IsNumber() + @AutoMap() + tueMargin: number; + + @IsNumber() + @AutoMap() + wedMargin: number; + + @IsNumber() + @AutoMap() + thuMargin: number; + + @IsNumber() + @AutoMap() + friMargin: number; + + @IsNumber() + @AutoMap() + satMargin: number; + + @IsNumber() + @AutoMap() + sunMargin: number; + + @IsNumber() + @AutoMap() + originType: number; + + @IsNumber() + @AutoMap() + destinationType: number; + + @AutoMap() + waypoints: []; + + @IsNumber() + @AutoMap() + seatsDriver: number; + + @IsNumber() + @AutoMap() + seatsPassenger: number; + + @IsNumber() + @AutoMap() + seatsUsed: number; + + @IsString() + @AutoMap() + createdAt: string; + + @IsString() + @AutoMap() + updatedAt: string; +} diff --git a/src/modules/ad/domain/entities/ad.ts b/src/modules/ad/domain/entities/ad.ts index b4725ad..e8998ec 100644 --- a/src/modules/ad/domain/entities/ad.ts +++ b/src/modules/ad/domain/entities/ad.ts @@ -4,38 +4,105 @@ export class Ad { @AutoMap() uuid: string; + @AutoMap() driver: boolean; + + @AutoMap() passenger: boolean; + + @AutoMap() frequency: number; + + @AutoMap() fromDate: string; + + @AutoMap() toDate: string; + + @AutoMap() monTime: string; + + @AutoMap() tueTime: string; + + @AutoMap() wedTime: string; + + @AutoMap() thuTime: string; + + @AutoMap() friTime: string; + + @AutoMap() satTime: string; + + @AutoMap() sunTime: string; + + @AutoMap() monMargin: number; + + @AutoMap() tueMargin: number; + + @AutoMap() wedMargin: number; + + @AutoMap() thuMargin: number; + + @AutoMap() friMargin: number; + + @AutoMap() satMargin: number; + + @AutoMap() sunMargin: number; + + @AutoMap() driverDuration: number; + + @AutoMap() driverDistance: number; + + @AutoMap() passengerDuration: number; + + @AutoMap() passengerDistance: number; + + @AutoMap() originType: number; + + @AutoMap() destinationType: number; + + @AutoMap() waypoints: []; + + @AutoMap() direction: string; + + @AutoMap() fwdAzimuth: number; + + @AutoMap() backAzimuth: number; + + @AutoMap() seatsDriver: number; + + @AutoMap() seatsPassenger: number; + + @AutoMap() seatsUsed: number; + + @AutoMap() createdAt: string; + + @AutoMap() updatedAt: string; } diff --git a/src/modules/ad/domain/usecases/create-ad.usecase.ts b/src/modules/ad/domain/usecases/create-ad.usecase.ts new file mode 100644 index 0000000..b9b3b9e --- /dev/null +++ b/src/modules/ad/domain/usecases/create-ad.usecase.ts @@ -0,0 +1,17 @@ +import { CommandHandler } from '@nestjs/cqrs'; +import { CreateAdCommand } from '../../commands/create-ad.command'; +import { Ad } from '../entities/ad'; +import { AdRepository } from '../../adapters/secondaries/ad.repository'; + +@CommandHandler(CreateAdCommand) +export class CreateAdUseCase { + constructor(private readonly adRepository: AdRepository) {} + + async execute(command: CreateAdCommand): Promise { + try { + return await this.adRepository.createAd(command.createAdRequest); + } catch (error) { + throw error; + } + } +} diff --git a/src/modules/ad/mappers/ad.profile.ts b/src/modules/ad/mappers/ad.profile.ts new file mode 100644 index 0000000..289adbc --- /dev/null +++ b/src/modules/ad/mappers/ad.profile.ts @@ -0,0 +1,18 @@ +import { createMap, Mapper } from '@automapper/core'; +import { AutomapperProfile, InjectMapper } from '@automapper/nestjs'; +import { Injectable } from '@nestjs/common'; +import { Ad } from '../domain/entities/ad'; +import { AdPresenter } from '../adapters/primaries/ad.presenter'; + +@Injectable() +export class AdProfile extends AutomapperProfile { + constructor(@InjectMapper() mapper: Mapper) { + super(mapper); + } + + override get profile() { + return (mapper: any) => { + createMap(mapper, Ad, AdPresenter); + }; + } +} diff --git a/src/modules/database/database.module.ts b/src/modules/database/database.module.ts index 61328fa..2d18636 100644 --- a/src/modules/database/database.module.ts +++ b/src/modules/database/database.module.ts @@ -1,9 +1,9 @@ import { Module } from '@nestjs/common'; import { PrismaService } from './src/adapters/secondaries/prisma-service'; -import { MatcherRepository } from './src/domain/matcher-repository'; +import { AdRepository } from '../ad/adapters/secondaries/ad.repository'; @Module({ - providers: [PrismaService, MatcherRepository], - exports: [PrismaService, MatcherRepository], + providers: [PrismaService, AdRepository], + exports: [PrismaService, AdRepository], }) export class DatabaseModule {} diff --git a/src/modules/database/src/adapters/secondaries/prisma-repository.abstract.ts b/src/modules/database/src/adapters/secondaries/prisma-repository.abstract.ts index fa2ba59..e93f896 100644 --- a/src/modules/database/src/adapters/secondaries/prisma-repository.abstract.ts +++ b/src/modules/database/src/adapters/secondaries/prisma-repository.abstract.ts @@ -202,9 +202,9 @@ export abstract class PrismaRepository implements IRepository { async createWithFields(fields: object): Promise { try { - const command = `INSERT INTO ${this._model} (${Object.keys(fields).join( - ',', - )}) VALUES (${Object.values(fields).join(',')})`; + const command = `INSERT INTO ${this._model} ("${Object.keys(fields).join( + '","', + )}") VALUES (${Object.values(fields).join(',')})`; return await this._prisma.$executeRawUnsafe(command); } catch (e) { if (e instanceof Prisma.PrismaClientKnownRequestError) { @@ -219,7 +219,7 @@ export abstract class PrismaRepository implements IRepository { } } - async updateWithFields(uuid: string, entity: Partial): Promise { + async updateWithFields(uuid: string, entity: object): Promise { entity['"updatedAt"'] = `to_timestamp(${Date.now()} / 1000.0)`; const values = Object.keys(entity).map((key) => `${key} = ${entity[key]}`); try { diff --git a/src/modules/geography/adapters/secondaries/geo-timezone-finder.ts b/src/modules/geography/adapters/secondaries/geo-timezone-finder.ts new file mode 100644 index 0000000..1ca02c2 --- /dev/null +++ b/src/modules/geography/adapters/secondaries/geo-timezone-finder.ts @@ -0,0 +1,8 @@ +import { Injectable } from '@nestjs/common'; +import { IFindTimezone } from '../../domain/interfaces/timezone-finder.interface'; +import { find } from 'geo-tz'; + +@Injectable() +export class GeoTimezoneFinder implements IFindTimezone { + timezones = (lon: number, lat: number): Array => find(lat, lon); +} diff --git a/src/modules/geography/adapters/secondaries/geodesic.ts b/src/modules/geography/adapters/secondaries/geodesic.ts new file mode 100644 index 0000000..835df8e --- /dev/null +++ b/src/modules/geography/adapters/secondaries/geodesic.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@nestjs/common'; +import { Geodesic as Geolib, GeodesicClass } from 'geographiclib-geodesic'; +import { IGeodesic } from '../../domain/interfaces/geodesic.interface'; + +@Injectable() +export class Geodesic implements IGeodesic { + private geod: GeodesicClass; + + constructor() { + this.geod = Geolib.WGS84; + } + + inverse = ( + lon1: number, + lat1: number, + lon2: number, + lat2: number, + ): { azimuth: number; distance: number } => { + const { azi2: azimuth, s12: distance } = this.geod.Inverse( + lat1, + lon1, + lat2, + lon2, + ); + return { azimuth, distance }; + }; +} diff --git a/src/modules/matcher/domain/interfaces/geodesic.interface.ts b/src/modules/geography/domain/interfaces/geodesic.interface.ts similarity index 100% rename from src/modules/matcher/domain/interfaces/geodesic.interface.ts rename to src/modules/geography/domain/interfaces/geodesic.interface.ts diff --git a/src/modules/geography/domain/interfaces/timezone-finder.interface.ts b/src/modules/geography/domain/interfaces/timezone-finder.interface.ts new file mode 100644 index 0000000..6f22169 --- /dev/null +++ b/src/modules/geography/domain/interfaces/timezone-finder.interface.ts @@ -0,0 +1,3 @@ +export interface IFindTimezone { + timezones(lon: number, lat: number): Array; +} diff --git a/src/modules/geography/geography.module.ts b/src/modules/geography/geography.module.ts new file mode 100644 index 0000000..d4be2c8 --- /dev/null +++ b/src/modules/geography/geography.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { GeoTimezoneFinder } from './adapters/secondaries/geo-timezone-finder'; +import { Geodesic } from './adapters/secondaries/geodesic'; + +@Module({ + providers: [GeoTimezoneFinder, Geodesic], + exports: [GeoTimezoneFinder, Geodesic], +}) +export class GeographyModule {} diff --git a/src/modules/geography/tests/unit/geo-timezone-finder.spec.ts b/src/modules/geography/tests/unit/geo-timezone-finder.spec.ts new file mode 100644 index 0000000..285761f --- /dev/null +++ b/src/modules/geography/tests/unit/geo-timezone-finder.spec.ts @@ -0,0 +1,14 @@ +import { GeoTimezoneFinder } from '../../adapters/secondaries/geo-timezone-finder'; + +describe('Geo TZ Finder', () => { + it('should be defined', () => { + const timezoneFinder: GeoTimezoneFinder = new GeoTimezoneFinder(); + expect(timezoneFinder).toBeDefined(); + }); + it('should get timezone for Nancy(France) as Europe/Paris', () => { + const timezoneFinder: GeoTimezoneFinder = new GeoTimezoneFinder(); + const timezones = timezoneFinder.timezones(6.179373, 48.687913); + expect(timezones.length).toBe(1); + expect(timezones[0]).toBe('Europe/Paris'); + }); +}); diff --git a/src/modules/geography/tests/unit/geodesic.spec.ts b/src/modules/geography/tests/unit/geodesic.spec.ts new file mode 100644 index 0000000..750d7d4 --- /dev/null +++ b/src/modules/geography/tests/unit/geodesic.spec.ts @@ -0,0 +1,14 @@ +import { Geodesic } from '../../adapters/secondaries/geodesic'; + +describe('Matcher geodesic', () => { + it('should be defined', () => { + const geodesic: Geodesic = new Geodesic(); + expect(geodesic).toBeDefined(); + }); + it('should get inverse values', () => { + const geodesic: Geodesic = new Geodesic(); + const inv = geodesic.inverse(0, 0, 1, 1); + expect(Math.round(inv.azimuth)).toBe(45); + expect(Math.round(inv.distance)).toBe(156900); + }); +}); diff --git a/src/modules/health/domain/usecases/prisma.health-indicator.usecase.ts b/src/modules/health/domain/usecases/prisma.health-indicator.usecase.ts index 0b788eb..cb04b3d 100644 --- a/src/modules/health/domain/usecases/prisma.health-indicator.usecase.ts +++ b/src/modules/health/domain/usecases/prisma.health-indicator.usecase.ts @@ -4,7 +4,7 @@ import { HealthIndicator, HealthIndicatorResult, } from '@nestjs/terminus'; -import { AdRepository } from '../../../matcher/adapters/secondaries/ad.repository'; +import { AdRepository } from '../../../ad/adapters/secondaries/ad.repository'; @Injectable() export class PrismaHealthIndicatorUseCase extends HealthIndicator { diff --git a/src/modules/health/health.module.ts b/src/modules/health/health.module.ts index db4980d..3a62c64 100644 --- a/src/modules/health/health.module.ts +++ b/src/modules/health/health.module.ts @@ -7,7 +7,7 @@ import { TerminusModule } from '@nestjs/terminus'; import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { Messager } from './adapters/secondaries/messager'; -import { AdRepository } from '../matcher/adapters/secondaries/ad.repository'; +import { AdRepository } from '../ad/adapters/secondaries/ad.repository'; @Module({ imports: [ diff --git a/src/modules/health/tests/unit/prisma.health-indicator.usecase.spec.ts b/src/modules/health/tests/unit/prisma.health-indicator.usecase.spec.ts index 7d3cf42..8c30654 100644 --- a/src/modules/health/tests/unit/prisma.health-indicator.usecase.spec.ts +++ b/src/modules/health/tests/unit/prisma.health-indicator.usecase.spec.ts @@ -1,7 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { PrismaHealthIndicatorUseCase } from '../../domain/usecases/prisma.health-indicator.usecase'; import { HealthCheckError, HealthIndicatorResult } from '@nestjs/terminus'; -import { AdRepository } from '../../../matcher/adapters/secondaries/ad.repository'; +import { AdRepository } from '../../../ad/adapters/secondaries/ad.repository'; import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'; const mockAdRepository = { diff --git a/src/modules/matcher/adapters/primaries/matcher.controller.ts b/src/modules/matcher/adapters/primaries/matcher.controller.ts index d1859ff..22b3b4a 100644 --- a/src/modules/matcher/adapters/primaries/matcher.controller.ts +++ b/src/modules/matcher/adapters/primaries/matcher.controller.ts @@ -11,6 +11,7 @@ import { MatchPresenter } from '../secondaries/match.presenter'; import { DefaultParamsProvider } from '../secondaries/default-params.provider'; import { GeorouterCreator } from '../secondaries/georouter-creator'; import { Match } from '../../domain/entities/ecosystem/match'; +import { GeoTimezoneFinder } from '../../../geography/adapters/secondaries/geo-timezone-finder'; @UsePipes( new RpcValidationPipe({ @@ -21,20 +22,22 @@ import { Match } from '../../domain/entities/ecosystem/match'; @Controller() export class MatcherController { constructor( - private readonly _queryBus: QueryBus, - private readonly _defaultParamsProvider: DefaultParamsProvider, + private readonly queryBus: QueryBus, + private readonly defaultParamsProvider: DefaultParamsProvider, @InjectMapper() private readonly _mapper: Mapper, - private readonly _georouterCreator: GeorouterCreator, + private readonly georouterCreator: GeorouterCreator, + private readonly timezoneFinder: GeoTimezoneFinder, ) {} @GrpcMethod('MatcherService', 'Match') async match(data: MatchRequest): Promise> { try { - const matchCollection = await this._queryBus.execute( + const matchCollection = await this.queryBus.execute( new MatchQuery( data, - this._defaultParamsProvider.getParams(), - this._georouterCreator, + this.defaultParamsProvider.getParams(), + this.georouterCreator, + this.timezoneFinder, ), ); return Promise.resolve({ diff --git a/src/modules/matcher/adapters/secondaries/ad.repository.ts b/src/modules/matcher/adapters/secondaries/ad.repository.ts deleted file mode 100644 index 9915f1f..0000000 --- a/src/modules/matcher/adapters/secondaries/ad.repository.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { MatcherRepository } from '../../../database/src/domain/matcher-repository'; -import { Ad } from '../../domain/entities/ecosystem/ad'; - -@Injectable() -export class AdRepository extends MatcherRepository { - protected _model = 'ad'; -} diff --git a/src/modules/matcher/adapters/secondaries/geodesic.ts b/src/modules/matcher/adapters/secondaries/geodesic.ts index 3743ac6..deb304a 100644 --- a/src/modules/matcher/adapters/secondaries/geodesic.ts +++ b/src/modules/matcher/adapters/secondaries/geodesic.ts @@ -1,27 +1,16 @@ import { Injectable } from '@nestjs/common'; -import { IGeodesic } from '../../domain/interfaces/geodesic.interface'; -import { Geodesic, GeodesicClass } from 'geographiclib-geodesic'; +import { Geodesic } from '../../../geography/adapters/secondaries/geodesic'; +import { IGeodesic } from '../../../geography/domain/interfaces/geodesic.interface'; @Injectable() export class MatcherGeodesic implements IGeodesic { - private geod: GeodesicClass; - - constructor() { - this.geod = Geodesic.WGS84; - } + constructor(private readonly geodesic: Geodesic) {} inverse = ( lon1: number, lat1: number, lon2: number, lat2: number, - ): { azimuth: number; distance: number } => { - const { azi2: azimuth, s12: distance } = this.geod.Inverse( - lat1, - lon1, - lat2, - lon2, - ); - return { azimuth, distance }; - }; + ): { azimuth: number; distance: number } => + this.geodesic.inverse(lon1, lat1, lon2, lat2); } diff --git a/src/modules/matcher/adapters/secondaries/graphhopper-georouter.ts b/src/modules/matcher/adapters/secondaries/graphhopper-georouter.ts index 33c79d8..9d85e85 100644 --- a/src/modules/matcher/adapters/secondaries/graphhopper-georouter.ts +++ b/src/modules/matcher/adapters/secondaries/graphhopper-georouter.ts @@ -5,7 +5,7 @@ import { Path } from '../../domain/types/path.type'; import { Injectable } from '@nestjs/common'; import { catchError, lastValueFrom, map } from 'rxjs'; import { AxiosError, AxiosResponse } from 'axios'; -import { IGeodesic } from '../../domain/interfaces/geodesic.interface'; +import { IGeodesic } from '../../../geography/domain/interfaces/geodesic.interface'; import { NamedRoute } from '../../domain/entities/ecosystem/named-route'; import { Route } from '../../domain/entities/ecosystem/route'; import { SpacetimePoint } from '../../domain/entities/ecosystem/spacetime-point'; diff --git a/src/modules/matcher/adapters/secondaries/timezone-finder.ts b/src/modules/matcher/adapters/secondaries/timezone-finder.ts new file mode 100644 index 0000000..5ce3306 --- /dev/null +++ b/src/modules/matcher/adapters/secondaries/timezone-finder.ts @@ -0,0 +1,11 @@ +import { Injectable } from '@nestjs/common'; +import { GeoTimezoneFinder } from '../../../geography/adapters/secondaries/geo-timezone-finder'; +import { IFindTimezone } from '../../../geography/domain/interfaces/timezone-finder.interface'; + +@Injectable() +export class TimezoneFinder implements IFindTimezone { + constructor(private readonly geoTimezoneFinder: GeoTimezoneFinder) {} + + timezones = (lon: number, lat: number): Array => + this.geoTimezoneFinder.timezones(lon, lat); +} diff --git a/src/modules/matcher/domain/entities/ecosystem/geography.ts b/src/modules/matcher/domain/entities/ecosystem/geography.ts index 892e904..56a08a5 100644 --- a/src/modules/matcher/domain/entities/ecosystem/geography.ts +++ b/src/modules/matcher/domain/entities/ecosystem/geography.ts @@ -5,7 +5,6 @@ import { import { IRequestGeography } from '../../interfaces/geography-request.interface'; import { PointType } from '../../types/geography.enum'; import { Point } from '../../types/point.type'; -import { find } from 'geo-tz'; import { Route } from './route'; import { Role } from '../../types/role.enum'; import { IGeorouter } from '../../interfaces/georouter.interface'; @@ -14,6 +13,8 @@ import { Actor } from './actor'; import { Person } from './person'; import { Step } from '../../types/step.enum'; import { Path } from '../../types/path.type'; +import { IFindTimezone } from '../../../../geography/domain/interfaces/timezone-finder.interface'; +import { Timezoner } from './timezoner'; export class Geography { private geographyRequest: IRequestGeography; @@ -24,10 +25,11 @@ export class Geography { timezones: Array; driverRoute: Route; passengerRoute: Route; + timezoneFinder: IFindTimezone; constructor( geographyRequest: IRequestGeography, - defaultTimezone: string, + timezoner: Timezoner, person: Person, ) { this.geographyRequest = geographyRequest; @@ -35,7 +37,8 @@ export class Geography { this.points = []; this.originType = undefined; this.destinationType = undefined; - this.timezones = [defaultTimezone]; + this.timezones = [timezoner.timezone]; + this.timezoneFinder = timezoner.finder; } init = (): void => { @@ -147,7 +150,7 @@ export class Geography { }; private setTimezones = (): void => { - this.timezones = find( + this.timezones = this.timezoneFinder.timezones( this.geographyRequest.waypoints[0].lat, this.geographyRequest.waypoints[0].lon, ); diff --git a/src/modules/matcher/domain/entities/ecosystem/route.ts b/src/modules/matcher/domain/entities/ecosystem/route.ts index d2b1238..2bb27b8 100644 --- a/src/modules/matcher/domain/entities/ecosystem/route.ts +++ b/src/modules/matcher/domain/entities/ecosystem/route.ts @@ -1,4 +1,4 @@ -import { IGeodesic } from '../../interfaces/geodesic.interface'; +import { IGeodesic } from '../../../../geography/domain/interfaces/geodesic.interface'; import { Point } from '../../types/point.type'; import { SpacetimePoint } from './spacetime-point'; import { Waypoint } from './waypoint'; diff --git a/src/modules/matcher/domain/entities/ecosystem/timezoner.ts b/src/modules/matcher/domain/entities/ecosystem/timezoner.ts new file mode 100644 index 0000000..29f6e0b --- /dev/null +++ b/src/modules/matcher/domain/entities/ecosystem/timezoner.ts @@ -0,0 +1,6 @@ +import { IFindTimezone } from '../../../../geography/domain/interfaces/timezone-finder.interface'; + +export type Timezoner = { + timezone: string; + finder: IFindTimezone; +}; diff --git a/src/modules/matcher/matcher.module.ts b/src/modules/matcher/matcher.module.ts index aacb6b2..39e1dfc 100644 --- a/src/modules/matcher/matcher.module.ts +++ b/src/modules/matcher/matcher.module.ts @@ -5,7 +5,6 @@ import { CqrsModule } from '@nestjs/cqrs'; import { DatabaseModule } from '../database/database.module'; import { MatcherController } from './adapters/primaries/matcher.controller'; import { MatchProfile } from './mappers/match.profile'; -import { AdRepository } from './adapters/secondaries/ad.repository'; import { MatchUseCase } from './domain/usecases/match.usecase'; import { Messager } from './adapters/secondaries/messager'; import { CacheModule } from '@nestjs/cache-manager'; @@ -17,9 +16,13 @@ import { HttpModule } from '@nestjs/axios'; import { MatcherGeodesic } from './adapters/secondaries/geodesic'; import { Matcher } from './domain/entities/engine/matcher'; import { AlgorithmFactoryCreator } from './domain/entities/engine/factory/algorithm-factory-creator'; +import { TimezoneFinder } from './adapters/secondaries/timezone-finder'; +import { GeoTimezoneFinder } from '../geography/adapters/secondaries/geo-timezone-finder'; +import { GeographyModule } from '../geography/geography.module'; @Module({ imports: [ + GeographyModule, DatabaseModule, CqrsModule, HttpModule, @@ -53,14 +56,15 @@ import { AlgorithmFactoryCreator } from './domain/entities/engine/factory/algori controllers: [MatcherController], providers: [ MatchProfile, - AdRepository, Messager, DefaultParamsProvider, MatchUseCase, GeorouterCreator, MatcherGeodesic, + TimezoneFinder, Matcher, AlgorithmFactoryCreator, + GeoTimezoneFinder, ], exports: [], }) diff --git a/src/modules/matcher/queries/match.query.ts b/src/modules/matcher/queries/match.query.ts index b5c62fc..3f7c8dd 100644 --- a/src/modules/matcher/queries/match.query.ts +++ b/src/modules/matcher/queries/match.query.ts @@ -8,11 +8,12 @@ import { Time } from '../domain/entities/ecosystem/time'; import { IDefaultParams } from '../domain/types/default-params.type'; import { IGeorouter } from '../domain/interfaces/georouter.interface'; import { ICreateGeorouter } from '../domain/interfaces/georouter-creator.interface'; +import { IFindTimezone } from '../../geography/domain/interfaces/timezone-finder.interface'; export class MatchQuery { - private readonly _matchRequest: MatchRequest; - private readonly _defaultParams: IDefaultParams; - private readonly _georouterCreator: ICreateGeorouter; + private readonly matchRequest: MatchRequest; + private readonly defaultParams: IDefaultParams; + private readonly georouterCreator: ICreateGeorouter; person: Person; roles: Array; time: Time; @@ -21,83 +22,89 @@ export class MatchQuery { requirement: Requirement; algorithmSettings: AlgorithmSettings; georouter: IGeorouter; + timezoneFinder: IFindTimezone; constructor( matchRequest: MatchRequest, defaultParams: IDefaultParams, georouterCreator: ICreateGeorouter, + timezoneFinder: IFindTimezone, ) { - this._matchRequest = matchRequest; - this._defaultParams = defaultParams; - this._georouterCreator = georouterCreator; - this._setPerson(); - this._setRoles(); - this._setTime(); - this._setGeography(); - this._setRequirement(); - this._setAlgorithmSettings(); - this._setExclusions(); + this.matchRequest = matchRequest; + this.defaultParams = defaultParams; + this.georouterCreator = georouterCreator; + this.timezoneFinder = timezoneFinder; + this.setPerson(); + this.setRoles(); + this.setTime(); + this.setGeography(); + this.setRequirement(); + this.setAlgorithmSettings(); + this.setExclusions(); } createRoutes = (): void => { this.geography.createRoutes(this.roles, this.algorithmSettings.georouter); }; - _setPerson = (): void => { + private setPerson = (): void => { this.person = new Person( - this._matchRequest, - this._defaultParams.DEFAULT_IDENTIFIER, - this._defaultParams.MARGIN_DURATION, + this.matchRequest, + this.defaultParams.DEFAULT_IDENTIFIER, + this.defaultParams.MARGIN_DURATION, ); this.person.init(); }; - _setRoles = (): void => { + private setRoles = (): void => { this.roles = []; - if (this._matchRequest.driver) this.roles.push(Role.DRIVER); - if (this._matchRequest.passenger) this.roles.push(Role.PASSENGER); + if (this.matchRequest.driver) this.roles.push(Role.DRIVER); + if (this.matchRequest.passenger) this.roles.push(Role.PASSENGER); if (this.roles.length == 0) this.roles.push(Role.PASSENGER); }; - _setTime = (): void => { + private setTime = (): void => { this.time = new Time( - this._matchRequest, - this._defaultParams.MARGIN_DURATION, - this._defaultParams.VALIDITY_DURATION, + this.matchRequest, + this.defaultParams.MARGIN_DURATION, + this.defaultParams.VALIDITY_DURATION, ); this.time.init(); }; - _setGeography = (): void => { + private setGeography = (): void => { this.geography = new Geography( - this._matchRequest, - this._defaultParams.DEFAULT_TIMEZONE, + this.matchRequest, + { + timezone: this.defaultParams.DEFAULT_TIMEZONE, + finder: this.timezoneFinder, + }, this.person, ); this.geography.init(); }; - _setRequirement = (): void => { + private setRequirement = (): void => { this.requirement = new Requirement( - this._matchRequest, - this._defaultParams.DEFAULT_SEATS, + this.matchRequest, + this.defaultParams.DEFAULT_SEATS, ); }; - _setAlgorithmSettings = (): void => { + private setAlgorithmSettings = (): void => { this.algorithmSettings = new AlgorithmSettings( - this._matchRequest, - this._defaultParams.DEFAULT_ALGORITHM_SETTINGS, + this.matchRequest, + this.defaultParams.DEFAULT_ALGORITHM_SETTINGS, this.time.frequency, - this._georouterCreator, + this.georouterCreator, ); }; - _setExclusions = (): void => { + private setExclusions = (): void => { this.exclusions = []; - if (this._matchRequest.identifier) - this.exclusions.push(this._matchRequest.identifier); - if (this._matchRequest.exclusions) - this.exclusions.push(...this._matchRequest.exclusions); + if (this.matchRequest.identifier) + this.exclusions.push(this.matchRequest.identifier); + if (this.matchRequest.exclusions) + this.exclusions.push(...this.matchRequest.exclusions); }; } diff --git a/src/modules/matcher/tests/unit/adapters/secondaries/geo-timezone-finder.spec.ts b/src/modules/matcher/tests/unit/adapters/secondaries/geo-timezone-finder.spec.ts new file mode 100644 index 0000000..5dccb46 --- /dev/null +++ b/src/modules/matcher/tests/unit/adapters/secondaries/geo-timezone-finder.spec.ts @@ -0,0 +1,35 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { TimezoneFinder } from '../../../../adapters/secondaries/timezone-finder'; +import { GeoTimezoneFinder } from '../../../../../geography/adapters/secondaries/geo-timezone-finder'; + +const mockGeoTimezoneFinder = { + timezones: jest.fn().mockImplementation(() => ['Europe/Paris']), +}; + +describe('Geo TZ Finder', () => { + let timezoneFinder: TimezoneFinder; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [], + providers: [ + TimezoneFinder, + { + provide: GeoTimezoneFinder, + useValue: mockGeoTimezoneFinder, + }, + ], + }).compile(); + + timezoneFinder = module.get(TimezoneFinder); + }); + + it('should be defined', () => { + expect(timezoneFinder).toBeDefined(); + }); + it('should get timezone for Nancy(France) as Europe/Paris', () => { + const timezones = timezoneFinder.timezones(6.179373, 48.687913); + expect(timezones.length).toBe(1); + expect(timezones[0]).toBe('Europe/Paris'); + }); +}); diff --git a/src/modules/matcher/tests/unit/adapters/secondaries/geodesic.spec.ts b/src/modules/matcher/tests/unit/adapters/secondaries/geodesic.spec.ts index 9e08335..6e878a9 100644 --- a/src/modules/matcher/tests/unit/adapters/secondaries/geodesic.spec.ts +++ b/src/modules/matcher/tests/unit/adapters/secondaries/geodesic.spec.ts @@ -1,14 +1,38 @@ +import { Test, TestingModule } from '@nestjs/testing'; import { MatcherGeodesic } from '../../../../adapters/secondaries/geodesic'; +import { Geodesic } from '../../../../../geography/adapters/secondaries/geodesic'; + +const mockGeodesic = { + inverse: jest.fn().mockImplementation(() => ({ + azimuth: 45, + distance: 50000, + })), +}; describe('Matcher geodesic', () => { + let matcherGeodesic: MatcherGeodesic; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [], + providers: [ + MatcherGeodesic, + { + provide: Geodesic, + useValue: mockGeodesic, + }, + ], + }).compile(); + + matcherGeodesic = module.get(MatcherGeodesic); + }); + it('should be defined', () => { - const geodesic: MatcherGeodesic = new MatcherGeodesic(); - expect(geodesic).toBeDefined(); + expect(matcherGeodesic).toBeDefined(); }); it('should get inverse values', () => { - const geodesic: MatcherGeodesic = new MatcherGeodesic(); - const inv = geodesic.inverse(0, 0, 1, 1); + const inv = matcherGeodesic.inverse(0, 0, 1, 1); expect(Math.round(inv.azimuth)).toBe(45); - expect(Math.round(inv.distance)).toBe(156900); + expect(Math.round(inv.distance)).toBe(50000); }); }); 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 9f93de6..2503dcc 100644 --- a/src/modules/matcher/tests/unit/domain/ecosystem/geography.spec.ts +++ b/src/modules/matcher/tests/unit/domain/ecosystem/geography.spec.ts @@ -6,7 +6,7 @@ import { import { Role } from '../../../../domain/types/role.enum'; import { NamedRoute } from '../../../../domain/entities/ecosystem/named-route'; import { Route } from '../../../../domain/entities/ecosystem/route'; -import { IGeodesic } from '../../../../domain/interfaces/geodesic.interface'; +import { IGeodesic } from '../../../../../geography/domain/interfaces/geodesic.interface'; import { PointType } from '../../../../domain/types/geography.enum'; const person: Person = new Person( @@ -65,6 +65,10 @@ const mockGeorouter = { }), }; +const mockTimezoneFinder = { + timezones: jest.fn().mockImplementation(), +}; + describe('Geography entity', () => { it('should be defined', () => { const geography = new Geography( @@ -80,7 +84,10 @@ describe('Geography entity', () => { }, ], }, - 'Europe/Paris', + { + timezone: 'Europe/Paris', + finder: mockTimezoneFinder, + }, person, ); expect(geography).toBeDefined(); @@ -103,7 +110,10 @@ describe('Geography entity', () => { }, ], }, - 'Europe/Paris', + { + timezone: 'Europe/Paris', + finder: mockTimezoneFinder, + }, person, ); geography.init(); @@ -115,7 +125,10 @@ describe('Geography entity', () => { { waypoints: [], }, - 'Europe/Paris', + { + timezone: 'Europe/Paris', + finder: mockTimezoneFinder, + }, person, ); expect(() => geography.init()).toThrow(); @@ -130,7 +143,10 @@ describe('Geography entity', () => { }, ], }, - 'Europe/Paris', + { + timezone: 'Europe/Paris', + finder: mockTimezoneFinder, + }, person, ); expect(() => geography.init()).toThrow(); @@ -149,7 +165,10 @@ describe('Geography entity', () => { }, ], }, - 'Europe/Paris', + { + timezone: 'Europe/Paris', + finder: mockTimezoneFinder, + }, person, ); expect(() => geography.init()).toThrow(); @@ -168,7 +187,10 @@ describe('Geography entity', () => { }, ], }, - 'Europe/Paris', + { + timezone: 'Europe/Paris', + finder: mockTimezoneFinder, + }, person, ); expect(() => geography.init()).toThrow(); @@ -190,7 +212,10 @@ describe('Geography entity', () => { }, ], }, - 'Europe/Paris', + { + timezone: 'Europe/Paris', + finder: mockTimezoneFinder, + }, person, ); geography.init(); @@ -220,7 +245,10 @@ describe('Geography entity', () => { }, ], }, - 'Europe/Paris', + { + timezone: 'Europe/Paris', + finder: mockTimezoneFinder, + }, person, ); geography.init(); @@ -246,7 +274,10 @@ describe('Geography entity', () => { }, ], }, - 'Europe/Paris', + { + timezone: 'Europe/Paris', + finder: mockTimezoneFinder, + }, person, ); geography.init(); @@ -269,7 +300,10 @@ describe('Geography entity', () => { }, ], }, - 'Europe/Paris', + { + timezone: 'Europe/Paris', + finder: mockTimezoneFinder, + }, person, ); geography.init(); 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 79bf1fc..1a816ee 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 @@ -9,6 +9,10 @@ const mockGeorouterCreator = { create: jest.fn().mockImplementation(), }; +const mockTimezoneFinder = { + timezones: jest.fn().mockImplementation(), +}; + const defaultParams: IDefaultParams = { DEFAULT_IDENTIFIER: 0, MARGIN_DURATION: 900, @@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery( matchRequest, defaultParams, mockGeorouterCreator, + mockTimezoneFinder, ); describe('AlgorithmFactoryCreator', () => { 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 7ec1886..f17f5a4 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 @@ -11,6 +11,10 @@ const mockGeorouterCreator = { create: jest.fn().mockImplementation(), }; +const mockTimezoneFinder = { + timezones: jest.fn().mockImplementation(), +}; + const defaultParams: IDefaultParams = { DEFAULT_IDENTIFIER: 0, MARGIN_DURATION: 900, @@ -48,6 +52,7 @@ const matchQuery: MatchQuery = new MatchQuery( matchRequest, defaultParams, mockGeorouterCreator, + mockTimezoneFinder, ); class FakeSelector extends Selector { 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 45d8a31..690fee3 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 @@ -9,6 +9,10 @@ const mockGeorouterCreator = { create: jest.fn().mockImplementation(), }; +const mockTimezoneFinder = { + timezones: jest.fn().mockImplementation(), +}; + const defaultParams: IDefaultParams = { DEFAULT_IDENTIFIER: 0, MARGIN_DURATION: 900, @@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery( matchRequest, defaultParams, mockGeorouterCreator, + mockTimezoneFinder, ); describe('ClassicAlgorithmFactory', () => { 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 abd06b8..fda1087 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 @@ -9,6 +9,10 @@ const mockGeorouterCreator = { create: jest.fn().mockImplementation(), }; +const mockTimezoneFinder = { + timezones: jest.fn().mockImplementation(), +}; + const defaultParams: IDefaultParams = { DEFAULT_IDENTIFIER: 0, MARGIN_DURATION: 900, @@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery( matchRequest, defaultParams, mockGeorouterCreator, + mockTimezoneFinder, ); describe('ClassicGeoFilter', () => { 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 fe92e70..6c703af 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 @@ -9,6 +9,10 @@ const mockGeorouterCreator = { create: jest.fn().mockImplementation(), }; +const mockTimezoneFinder = { + timezones: jest.fn().mockImplementation(), +}; + const defaultParams: IDefaultParams = { DEFAULT_IDENTIFIER: 0, MARGIN_DURATION: 900, @@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery( matchRequest, defaultParams, mockGeorouterCreator, + mockTimezoneFinder, ); describe('ClassicTimeFilter', () => { 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 500193e..9a52ce4 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 @@ -9,6 +9,10 @@ const mockGeorouterCreator = { create: jest.fn().mockImplementation(), }; +const mockTimezoneFinder = { + timezones: jest.fn().mockImplementation(), +}; + const defaultParams: IDefaultParams = { DEFAULT_IDENTIFIER: 0, MARGIN_DURATION: 900, @@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery( matchRequest, defaultParams, mockGeorouterCreator, + mockTimezoneFinder, ); describe('ClassicWaypointCompleter', () => { 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 8eb2954..77d616b 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 @@ -9,6 +9,10 @@ const mockGeorouterCreator = { create: jest.fn().mockImplementation(), }; +const mockTimezoneFinder = { + timezones: jest.fn().mockImplementation(), +}; + const defaultParams: IDefaultParams = { DEFAULT_IDENTIFIER: 0, MARGIN_DURATION: 900, @@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery( matchRequest, defaultParams, mockGeorouterCreator, + mockTimezoneFinder, ); describe('ClassicSelector', () => { 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 f94bbcf..15fad37 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 @@ -9,6 +9,10 @@ const mockGeorouterCreator = { create: jest.fn().mockImplementation(), }; +const mockTimezoneFinder = { + timezones: jest.fn().mockImplementation(), +}; + const defaultParams: IDefaultParams = { DEFAULT_IDENTIFIER: 0, MARGIN_DURATION: 900, @@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery( matchRequest, defaultParams, mockGeorouterCreator, + mockTimezoneFinder, ); class FakeCompleter extends Completer { 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 dfb1e64..6b96578 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 @@ -9,6 +9,10 @@ const mockGeorouterCreator = { create: jest.fn().mockImplementation(), }; +const mockTimezoneFinder = { + timezones: jest.fn().mockImplementation(), +}; + const defaultParams: IDefaultParams = { DEFAULT_IDENTIFIER: 0, MARGIN_DURATION: 900, @@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery( matchRequest, defaultParams, mockGeorouterCreator, + mockTimezoneFinder, ); class FakeFilter extends Filter { 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 9eb9a58..eb79edf 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 @@ -9,6 +9,10 @@ const mockGeorouterCreator = { create: jest.fn().mockImplementation(), }; +const mockTimezoneFinder = { + timezones: jest.fn().mockImplementation(), +}; + const defaultParams: IDefaultParams = { DEFAULT_IDENTIFIER: 0, MARGIN_DURATION: 900, @@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery( matchRequest, defaultParams, mockGeorouterCreator, + mockTimezoneFinder, ); describe('JourneyCompleter', () => { 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 0f1aca7..1240682 100644 --- a/src/modules/matcher/tests/unit/domain/engine/matcher.spec.ts +++ b/src/modules/matcher/tests/unit/domain/engine/matcher.spec.ts @@ -21,6 +21,10 @@ const mockGeorouterCreator = { create: jest.fn().mockImplementation(), }; +const mockTimezoneFinder = { + timezones: jest.fn().mockImplementation(), +}; + const defaultParams: IDefaultParams = { DEFAULT_IDENTIFIER: 0, MARGIN_DURATION: 900, @@ -58,6 +62,7 @@ const matchQuery: MatchQuery = new MatchQuery( matchRequest, defaultParams, mockGeorouterCreator, + mockTimezoneFinder, ); describe('Matcher', () => { 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 66bfefb..65f6a00 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 @@ -9,6 +9,10 @@ const mockGeorouterCreator = { create: jest.fn().mockImplementation(), }; +const mockTimezoneFinder = { + timezones: jest.fn().mockImplementation(), +}; + const defaultParams: IDefaultParams = { DEFAULT_IDENTIFIER: 0, MARGIN_DURATION: 900, @@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery( matchRequest, defaultParams, mockGeorouterCreator, + mockTimezoneFinder, ); class FakeProcessor extends Processor { 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 4863945..7076539 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 @@ -9,6 +9,10 @@ const mockGeorouterCreator = { create: jest.fn().mockImplementation(), }; +const mockTimezoneFinder = { + timezones: jest.fn().mockImplementation(), +}; + const defaultParams: IDefaultParams = { DEFAULT_IDENTIFIER: 0, MARGIN_DURATION: 900, @@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery( matchRequest, defaultParams, mockGeorouterCreator, + mockTimezoneFinder, ); describe('RouteCompleter', () => { 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 01f9eb7..d8830cb 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 @@ -9,6 +9,10 @@ const mockGeorouterCreator = { create: jest.fn().mockImplementation(), }; +const mockTimezoneFinder = { + timezones: jest.fn().mockImplementation(), +}; + const defaultParams: IDefaultParams = { DEFAULT_IDENTIFIER: 0, MARGIN_DURATION: 900, @@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery( matchRequest, defaultParams, mockGeorouterCreator, + mockTimezoneFinder, ); class FakeSelector extends Selector { 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 c5e0fda..1ca097b 100644 --- a/src/modules/matcher/tests/unit/domain/match.usecase.spec.ts +++ b/src/modules/matcher/tests/unit/domain/match.usecase.spec.ts @@ -34,6 +34,10 @@ const mockGeorouterCreator = { create: jest.fn().mockImplementation(), }; +const mockTimezoneFinder = { + timezones: jest.fn().mockImplementation(), +}; + const defaultParams: IDefaultParams = { DEFAULT_IDENTIFIER: 0, MARGIN_DURATION: 900, @@ -97,7 +101,12 @@ describe('MatchUseCase', () => { describe('execute', () => { it('should return matches', async () => { const matches = await matchUseCase.execute( - new MatchQuery(matchRequest, defaultParams, mockGeorouterCreator), + new MatchQuery( + matchRequest, + defaultParams, + mockGeorouterCreator, + mockTimezoneFinder, + ), ); expect(matches.total).toBe(3); }); @@ -105,7 +114,12 @@ describe('MatchUseCase', () => { it('should throw an exception when error occurs', async () => { await expect( matchUseCase.execute( - new MatchQuery(matchRequest, defaultParams, mockGeorouterCreator), + new MatchQuery( + matchRequest, + defaultParams, + mockGeorouterCreator, + mockTimezoneFinder, + ), ), ).rejects.toBeInstanceOf(MatcherException); }); 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 f761bcf..92dae34 100644 --- a/src/modules/matcher/tests/unit/queries/match.query.spec.ts +++ b/src/modules/matcher/tests/unit/queries/match.query.spec.ts @@ -30,6 +30,10 @@ const mockGeorouterCreator = { create: jest.fn().mockImplementation(), }; +const mockTimezoneFinder = { + timezones: jest.fn().mockImplementation(), +}; + describe('Match query', () => { it('should be defined', () => { const matchRequest: MatchRequest = new MatchRequest(); @@ -48,6 +52,7 @@ describe('Match query', () => { matchRequest, defaultParams, mockGeorouterCreator, + mockTimezoneFinder, ); expect(matchQuery).toBeDefined(); }); @@ -71,6 +76,7 @@ describe('Match query', () => { matchRequest, defaultParams, mockGeorouterCreator, + mockTimezoneFinder, ); expect(matchQuery.exclusions.length).toBe(4); }); @@ -93,6 +99,7 @@ describe('Match query', () => { matchRequest, defaultParams, mockGeorouterCreator, + mockTimezoneFinder, ); expect(matchQuery.roles).toEqual([Role.DRIVER]); }); @@ -115,6 +122,7 @@ describe('Match query', () => { matchRequest, defaultParams, mockGeorouterCreator, + mockTimezoneFinder, ); expect(matchQuery.roles).toEqual([Role.PASSENGER]); }); @@ -138,6 +146,7 @@ describe('Match query', () => { matchRequest, defaultParams, mockGeorouterCreator, + mockTimezoneFinder, ); expect(matchQuery.roles.length).toBe(2); expect(matchQuery.roles).toContain(Role.PASSENGER); @@ -163,6 +172,7 @@ describe('Match query', () => { matchRequest, defaultParams, mockGeorouterCreator, + mockTimezoneFinder, ); expect(matchQuery.requirement.seatsDriver).toBe(1); expect(matchQuery.requirement.seatsPassenger).toBe(2); @@ -194,6 +204,7 @@ describe('Match query', () => { matchRequest, defaultParams, mockGeorouterCreator, + mockTimezoneFinder, ); expect(matchQuery.algorithmSettings.algorithmType).toBe( AlgorithmType.CLASSIC,