create ad WIP, extract geography methods to dedicated module

This commit is contained in:
sbriat 2023-04-25 17:49:47 +02:00
parent ca693087d2
commit aeead7fb62
53 changed files with 824 additions and 222 deletions

View File

@ -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");

View File

@ -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");

View File

@ -13,47 +13,47 @@ datasource db {
} }
model Ad { model Ad {
uuid String @id @default(uuid()) @db.Uuid uuid String @id @default(uuid()) @db.Uuid
driver Boolean driver Boolean
passenger Boolean passenger Boolean
frequency Int frequency Int
from_date DateTime @db.Date fromDate DateTime @db.Date
to_date DateTime @db.Date toDate DateTime @db.Date
mon_time DateTime @db.Timestamptz() monTime DateTime @db.Timestamptz()
tue_time DateTime @db.Timestamptz() tueTime DateTime @db.Timestamptz()
wed_time DateTime @db.Timestamptz() wedTime DateTime @db.Timestamptz()
thu_time DateTime @db.Timestamptz() thuTime DateTime @db.Timestamptz()
fri_time DateTime @db.Timestamptz() friTime DateTime @db.Timestamptz()
sat_time DateTime @db.Timestamptz() satTime DateTime @db.Timestamptz()
sun_time DateTime @db.Timestamptz() sunTime DateTime @db.Timestamptz()
mon_margin Int monMargin Int
tue_margin Int tueMargin Int
wed_margin Int wedMargin Int
thu_margin Int thuMargin Int
fri_margin Int friMargin Int
sat_margin Int satMargin Int
sun_margin Int sunMargin Int
driver_duration Int driverDuration Int
driver_distance Int driverDistance Int
passenger_duration Int passengerDuration Int
passenger_distance Int passengerDistance Int
origin_type Int @db.SmallInt originType Int @db.SmallInt
destination_type Int @db.SmallInt destinationType Int @db.SmallInt
waypoints Unsupported("geography(LINESTRING)") waypoints Unsupported("geography(LINESTRING)")?
direction Unsupported("geography(LINESTRING)") direction Unsupported("geography(LINESTRING)")?
fwd_azimuth Int fwdAzimuth Int
back_azimuth Int backAzimuth Int
seats_driver Int @db.SmallInt seatsDriver Int @db.SmallInt
seats_passenger Int @db.SmallInt seatsPassenger Int @db.SmallInt
seats_used Int @db.SmallInt seatsUsed Int @db.SmallInt
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt updatedAt DateTime @default(now()) @updatedAt
@@index([driver]) @@index([driver])
@@index([passenger]) @@index([passenger])
@@index([from_date]) @@index([fromDate])
@@index([to_date]) @@index([toDate])
@@index([fwd_azimuth]) @@index([fwdAzimuth])
@@index([direction], name: "direction_idx", type: Gist) @@index([direction], name: "direction_idx", type: Gist)
@@map("ad") @@map("ad")
} }

View File

@ -2,9 +2,16 @@ import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq';
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config'; import { ConfigModule, ConfigService } from '@nestjs/config';
import { AdMessagerController } from './adapters/primaries/ad-messager.controller'; 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({ @Module({
imports: [ imports: [
DatabaseModule,
CqrsModule,
RabbitMQModule.forRootAsync(RabbitMQModule, { RabbitMQModule.forRootAsync(RabbitMQModule, {
imports: [ConfigModule], imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({ useFactory: async (configService: ConfigService) => ({
@ -18,9 +25,7 @@ import { AdMessagerController } from './adapters/primaries/ad-messager.controlle
adCreated: { adCreated: {
exchange: configService.get<string>('RMQ_EXCHANGE'), exchange: configService.get<string>('RMQ_EXCHANGE'),
routingKey: 'ad.created', routingKey: 'ad.created',
queue: `${configService.get<string>( queue: 'matcher-ad-created',
'RMQ_EXCHANGE',
)}-matcher-ad-created`,
}, },
}, },
uri: configService.get<string>('RMQ_URI'), uri: configService.get<string>('RMQ_URI'),
@ -31,7 +36,7 @@ import { AdMessagerController } from './adapters/primaries/ad-messager.controlle
}), }),
], ],
controllers: [AdMessagerController], controllers: [AdMessagerController],
providers: [], providers: [AdProfile, AdRepository, CreateAdUseCase],
exports: [], exports: [],
}) })
export class AdModule {} export class AdModule {}

View File

@ -1,19 +1,32 @@
import { RabbitSubscribe } from '@golevelup/nestjs-rabbitmq'; import { RabbitSubscribe } from '@golevelup/nestjs-rabbitmq';
import { Controller } from '@nestjs/common'; 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() @Controller()
export class AdMessagerController { export class AdMessagerController {
constructor(
private readonly _commandBus: CommandBus,
@InjectMapper() private readonly _mapper: Mapper,
) {}
@RabbitSubscribe({ @RabbitSubscribe({
name: 'adCreated', name: 'adCreated',
}) })
public adCreatedHandler(message: string) { async adCreatedHandler(message: string): Promise<AdPresenter> {
console.log(JSON.parse(message)); try {
// try { const createAdRequest: CreateAdRequest = JSON.parse(message);
// const createdAd: Ad = JSON.parse(message); const ad: Ad = await this._commandBus.execute(
// console.log(createdAd); new CreateAdCommand(createAdRequest),
// } catch (e) { );
// console.log('error', e); return this._mapper.map(ad, Ad, AdPresenter);
// } } catch (e) {
console.log('error', e);
}
} }
} }

View File

@ -1,6 +1,6 @@
import { AutoMap } from '@automapper/classes'; import { AutoMap } from '@automapper/classes';
export class Ad { export class AdPresenter {
@AutoMap() @AutoMap()
uuid: string; uuid: string;
} }

View File

@ -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<Ad> {
protected _model = 'ad';
async createAd(ad: Partial<Ad>): Promise<Ad> {
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<Ad>): Partial<AdFields> {
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;
};

View File

@ -0,0 +1,9 @@
import { CreateAdRequest } from '../domain/dtos/create-ad.request';
export class CreateAdCommand {
readonly createAdRequest: CreateAdRequest;
constructor(request: CreateAdRequest) {
this.createAdRequest = request;
}
}

View File

@ -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;
}

View File

@ -4,38 +4,105 @@ export class Ad {
@AutoMap() @AutoMap()
uuid: string; uuid: string;
@AutoMap()
driver: boolean; driver: boolean;
@AutoMap()
passenger: boolean; passenger: boolean;
@AutoMap()
frequency: number; frequency: number;
@AutoMap()
fromDate: string; fromDate: string;
@AutoMap()
toDate: string; toDate: string;
@AutoMap()
monTime: string; monTime: string;
@AutoMap()
tueTime: string; tueTime: string;
@AutoMap()
wedTime: string; wedTime: string;
@AutoMap()
thuTime: string; thuTime: string;
@AutoMap()
friTime: string; friTime: string;
@AutoMap()
satTime: string; satTime: string;
@AutoMap()
sunTime: string; sunTime: string;
@AutoMap()
monMargin: number; monMargin: number;
@AutoMap()
tueMargin: number; tueMargin: number;
@AutoMap()
wedMargin: number; wedMargin: number;
@AutoMap()
thuMargin: number; thuMargin: number;
@AutoMap()
friMargin: number; friMargin: number;
@AutoMap()
satMargin: number; satMargin: number;
@AutoMap()
sunMargin: number; sunMargin: number;
@AutoMap()
driverDuration: number; driverDuration: number;
@AutoMap()
driverDistance: number; driverDistance: number;
@AutoMap()
passengerDuration: number; passengerDuration: number;
@AutoMap()
passengerDistance: number; passengerDistance: number;
@AutoMap()
originType: number; originType: number;
@AutoMap()
destinationType: number; destinationType: number;
@AutoMap()
waypoints: []; waypoints: [];
@AutoMap()
direction: string; direction: string;
@AutoMap()
fwdAzimuth: number; fwdAzimuth: number;
@AutoMap()
backAzimuth: number; backAzimuth: number;
@AutoMap()
seatsDriver: number; seatsDriver: number;
@AutoMap()
seatsPassenger: number; seatsPassenger: number;
@AutoMap()
seatsUsed: number; seatsUsed: number;
@AutoMap()
createdAt: string; createdAt: string;
@AutoMap()
updatedAt: string; updatedAt: string;
} }

View File

@ -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<Ad> {
try {
return await this.adRepository.createAd(command.createAdRequest);
} catch (error) {
throw error;
}
}
}

View File

@ -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);
};
}
}

View File

@ -1,9 +1,9 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { PrismaService } from './src/adapters/secondaries/prisma-service'; import { PrismaService } from './src/adapters/secondaries/prisma-service';
import { MatcherRepository } from './src/domain/matcher-repository'; import { AdRepository } from '../ad/adapters/secondaries/ad.repository';
@Module({ @Module({
providers: [PrismaService, MatcherRepository], providers: [PrismaService, AdRepository],
exports: [PrismaService, MatcherRepository], exports: [PrismaService, AdRepository],
}) })
export class DatabaseModule {} export class DatabaseModule {}

View File

@ -202,9 +202,9 @@ export abstract class PrismaRepository<T> implements IRepository<T> {
async createWithFields(fields: object): Promise<number> { async createWithFields(fields: object): Promise<number> {
try { 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(',')})`; )}") VALUES (${Object.values(fields).join(',')})`;
return await this._prisma.$executeRawUnsafe(command); return await this._prisma.$executeRawUnsafe(command);
} catch (e) { } catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError) { if (e instanceof Prisma.PrismaClientKnownRequestError) {
@ -219,7 +219,7 @@ export abstract class PrismaRepository<T> implements IRepository<T> {
} }
} }
async updateWithFields(uuid: string, entity: Partial<T>): Promise<number> { async updateWithFields(uuid: string, entity: object): Promise<number> {
entity['"updatedAt"'] = `to_timestamp(${Date.now()} / 1000.0)`; entity['"updatedAt"'] = `to_timestamp(${Date.now()} / 1000.0)`;
const values = Object.keys(entity).map((key) => `${key} = ${entity[key]}`); const values = Object.keys(entity).map((key) => `${key} = ${entity[key]}`);
try { try {

View File

@ -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<string> => find(lat, lon);
}

View File

@ -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 };
};
}

View File

@ -0,0 +1,3 @@
export interface IFindTimezone {
timezones(lon: number, lat: number): Array<string>;
}

View File

@ -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 {}

View File

@ -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');
});
});

View File

@ -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);
});
});

View File

@ -4,7 +4,7 @@ import {
HealthIndicator, HealthIndicator,
HealthIndicatorResult, HealthIndicatorResult,
} from '@nestjs/terminus'; } from '@nestjs/terminus';
import { AdRepository } from '../../../matcher/adapters/secondaries/ad.repository'; import { AdRepository } from '../../../ad/adapters/secondaries/ad.repository';
@Injectable() @Injectable()
export class PrismaHealthIndicatorUseCase extends HealthIndicator { export class PrismaHealthIndicatorUseCase extends HealthIndicator {

View File

@ -7,7 +7,7 @@ import { TerminusModule } from '@nestjs/terminus';
import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq'; import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq';
import { ConfigModule, ConfigService } from '@nestjs/config'; import { ConfigModule, ConfigService } from '@nestjs/config';
import { Messager } from './adapters/secondaries/messager'; import { Messager } from './adapters/secondaries/messager';
import { AdRepository } from '../matcher/adapters/secondaries/ad.repository'; import { AdRepository } from '../ad/adapters/secondaries/ad.repository';
@Module({ @Module({
imports: [ imports: [

View File

@ -1,7 +1,7 @@
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { PrismaHealthIndicatorUseCase } from '../../domain/usecases/prisma.health-indicator.usecase'; import { PrismaHealthIndicatorUseCase } from '../../domain/usecases/prisma.health-indicator.usecase';
import { HealthCheckError, HealthIndicatorResult } from '@nestjs/terminus'; 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'; import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';
const mockAdRepository = { const mockAdRepository = {

View File

@ -11,6 +11,7 @@ import { MatchPresenter } from '../secondaries/match.presenter';
import { DefaultParamsProvider } from '../secondaries/default-params.provider'; import { DefaultParamsProvider } from '../secondaries/default-params.provider';
import { GeorouterCreator } from '../secondaries/georouter-creator'; import { GeorouterCreator } from '../secondaries/georouter-creator';
import { Match } from '../../domain/entities/ecosystem/match'; import { Match } from '../../domain/entities/ecosystem/match';
import { GeoTimezoneFinder } from '../../../geography/adapters/secondaries/geo-timezone-finder';
@UsePipes( @UsePipes(
new RpcValidationPipe({ new RpcValidationPipe({
@ -21,20 +22,22 @@ import { Match } from '../../domain/entities/ecosystem/match';
@Controller() @Controller()
export class MatcherController { export class MatcherController {
constructor( constructor(
private readonly _queryBus: QueryBus, private readonly queryBus: QueryBus,
private readonly _defaultParamsProvider: DefaultParamsProvider, private readonly defaultParamsProvider: DefaultParamsProvider,
@InjectMapper() private readonly _mapper: Mapper, @InjectMapper() private readonly _mapper: Mapper,
private readonly _georouterCreator: GeorouterCreator, private readonly georouterCreator: GeorouterCreator,
private readonly timezoneFinder: GeoTimezoneFinder,
) {} ) {}
@GrpcMethod('MatcherService', 'Match') @GrpcMethod('MatcherService', 'Match')
async match(data: MatchRequest): Promise<ICollection<Match>> { async match(data: MatchRequest): Promise<ICollection<Match>> {
try { try {
const matchCollection = await this._queryBus.execute( const matchCollection = await this.queryBus.execute(
new MatchQuery( new MatchQuery(
data, data,
this._defaultParamsProvider.getParams(), this.defaultParamsProvider.getParams(),
this._georouterCreator, this.georouterCreator,
this.timezoneFinder,
), ),
); );
return Promise.resolve({ return Promise.resolve({

View File

@ -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<Ad> {
protected _model = 'ad';
}

View File

@ -1,27 +1,16 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { IGeodesic } from '../../domain/interfaces/geodesic.interface'; import { Geodesic } from '../../../geography/adapters/secondaries/geodesic';
import { Geodesic, GeodesicClass } from 'geographiclib-geodesic'; import { IGeodesic } from '../../../geography/domain/interfaces/geodesic.interface';
@Injectable() @Injectable()
export class MatcherGeodesic implements IGeodesic { export class MatcherGeodesic implements IGeodesic {
private geod: GeodesicClass; constructor(private readonly geodesic: Geodesic) {}
constructor() {
this.geod = Geodesic.WGS84;
}
inverse = ( inverse = (
lon1: number, lon1: number,
lat1: number, lat1: number,
lon2: number, lon2: number,
lat2: number, lat2: number,
): { azimuth: number; distance: number } => { ): { azimuth: number; distance: number } =>
const { azi2: azimuth, s12: distance } = this.geod.Inverse( this.geodesic.inverse(lon1, lat1, lon2, lat2);
lat1,
lon1,
lat2,
lon2,
);
return { azimuth, distance };
};
} }

View File

@ -5,7 +5,7 @@ import { Path } from '../../domain/types/path.type';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { catchError, lastValueFrom, map } from 'rxjs'; import { catchError, lastValueFrom, map } from 'rxjs';
import { AxiosError, AxiosResponse } from 'axios'; 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 { NamedRoute } from '../../domain/entities/ecosystem/named-route';
import { Route } from '../../domain/entities/ecosystem/route'; import { Route } from '../../domain/entities/ecosystem/route';
import { SpacetimePoint } from '../../domain/entities/ecosystem/spacetime-point'; import { SpacetimePoint } from '../../domain/entities/ecosystem/spacetime-point';

View File

@ -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<string> =>
this.geoTimezoneFinder.timezones(lon, lat);
}

View File

@ -5,7 +5,6 @@ import {
import { IRequestGeography } from '../../interfaces/geography-request.interface'; import { IRequestGeography } from '../../interfaces/geography-request.interface';
import { PointType } from '../../types/geography.enum'; import { PointType } from '../../types/geography.enum';
import { Point } from '../../types/point.type'; import { Point } from '../../types/point.type';
import { find } from 'geo-tz';
import { Route } from './route'; import { Route } from './route';
import { Role } from '../../types/role.enum'; import { Role } from '../../types/role.enum';
import { IGeorouter } from '../../interfaces/georouter.interface'; import { IGeorouter } from '../../interfaces/georouter.interface';
@ -14,6 +13,8 @@ import { Actor } from './actor';
import { Person } from './person'; import { Person } from './person';
import { Step } from '../../types/step.enum'; import { Step } from '../../types/step.enum';
import { Path } from '../../types/path.type'; import { Path } from '../../types/path.type';
import { IFindTimezone } from '../../../../geography/domain/interfaces/timezone-finder.interface';
import { Timezoner } from './timezoner';
export class Geography { export class Geography {
private geographyRequest: IRequestGeography; private geographyRequest: IRequestGeography;
@ -24,10 +25,11 @@ export class Geography {
timezones: Array<string>; timezones: Array<string>;
driverRoute: Route; driverRoute: Route;
passengerRoute: Route; passengerRoute: Route;
timezoneFinder: IFindTimezone;
constructor( constructor(
geographyRequest: IRequestGeography, geographyRequest: IRequestGeography,
defaultTimezone: string, timezoner: Timezoner,
person: Person, person: Person,
) { ) {
this.geographyRequest = geographyRequest; this.geographyRequest = geographyRequest;
@ -35,7 +37,8 @@ export class Geography {
this.points = []; this.points = [];
this.originType = undefined; this.originType = undefined;
this.destinationType = undefined; this.destinationType = undefined;
this.timezones = [defaultTimezone]; this.timezones = [timezoner.timezone];
this.timezoneFinder = timezoner.finder;
} }
init = (): void => { init = (): void => {
@ -147,7 +150,7 @@ export class Geography {
}; };
private setTimezones = (): void => { private setTimezones = (): void => {
this.timezones = find( this.timezones = this.timezoneFinder.timezones(
this.geographyRequest.waypoints[0].lat, this.geographyRequest.waypoints[0].lat,
this.geographyRequest.waypoints[0].lon, this.geographyRequest.waypoints[0].lon,
); );

View File

@ -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 { Point } from '../../types/point.type';
import { SpacetimePoint } from './spacetime-point'; import { SpacetimePoint } from './spacetime-point';
import { Waypoint } from './waypoint'; import { Waypoint } from './waypoint';

View File

@ -0,0 +1,6 @@
import { IFindTimezone } from '../../../../geography/domain/interfaces/timezone-finder.interface';
export type Timezoner = {
timezone: string;
finder: IFindTimezone;
};

View File

@ -5,7 +5,6 @@ import { CqrsModule } from '@nestjs/cqrs';
import { DatabaseModule } from '../database/database.module'; import { DatabaseModule } from '../database/database.module';
import { MatcherController } from './adapters/primaries/matcher.controller'; import { MatcherController } from './adapters/primaries/matcher.controller';
import { MatchProfile } from './mappers/match.profile'; import { MatchProfile } from './mappers/match.profile';
import { AdRepository } from './adapters/secondaries/ad.repository';
import { MatchUseCase } from './domain/usecases/match.usecase'; import { MatchUseCase } from './domain/usecases/match.usecase';
import { Messager } from './adapters/secondaries/messager'; import { Messager } from './adapters/secondaries/messager';
import { CacheModule } from '@nestjs/cache-manager'; import { CacheModule } from '@nestjs/cache-manager';
@ -17,9 +16,13 @@ import { HttpModule } from '@nestjs/axios';
import { MatcherGeodesic } from './adapters/secondaries/geodesic'; import { MatcherGeodesic } from './adapters/secondaries/geodesic';
import { Matcher } from './domain/entities/engine/matcher'; import { Matcher } from './domain/entities/engine/matcher';
import { AlgorithmFactoryCreator } from './domain/entities/engine/factory/algorithm-factory-creator'; 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({ @Module({
imports: [ imports: [
GeographyModule,
DatabaseModule, DatabaseModule,
CqrsModule, CqrsModule,
HttpModule, HttpModule,
@ -53,14 +56,15 @@ import { AlgorithmFactoryCreator } from './domain/entities/engine/factory/algori
controllers: [MatcherController], controllers: [MatcherController],
providers: [ providers: [
MatchProfile, MatchProfile,
AdRepository,
Messager, Messager,
DefaultParamsProvider, DefaultParamsProvider,
MatchUseCase, MatchUseCase,
GeorouterCreator, GeorouterCreator,
MatcherGeodesic, MatcherGeodesic,
TimezoneFinder,
Matcher, Matcher,
AlgorithmFactoryCreator, AlgorithmFactoryCreator,
GeoTimezoneFinder,
], ],
exports: [], exports: [],
}) })

View File

@ -8,11 +8,12 @@ import { Time } from '../domain/entities/ecosystem/time';
import { IDefaultParams } from '../domain/types/default-params.type'; import { IDefaultParams } from '../domain/types/default-params.type';
import { IGeorouter } from '../domain/interfaces/georouter.interface'; import { IGeorouter } from '../domain/interfaces/georouter.interface';
import { ICreateGeorouter } from '../domain/interfaces/georouter-creator.interface'; import { ICreateGeorouter } from '../domain/interfaces/georouter-creator.interface';
import { IFindTimezone } from '../../geography/domain/interfaces/timezone-finder.interface';
export class MatchQuery { export class MatchQuery {
private readonly _matchRequest: MatchRequest; private readonly matchRequest: MatchRequest;
private readonly _defaultParams: IDefaultParams; private readonly defaultParams: IDefaultParams;
private readonly _georouterCreator: ICreateGeorouter; private readonly georouterCreator: ICreateGeorouter;
person: Person; person: Person;
roles: Array<Role>; roles: Array<Role>;
time: Time; time: Time;
@ -21,83 +22,89 @@ export class MatchQuery {
requirement: Requirement; requirement: Requirement;
algorithmSettings: AlgorithmSettings; algorithmSettings: AlgorithmSettings;
georouter: IGeorouter; georouter: IGeorouter;
timezoneFinder: IFindTimezone;
constructor( constructor(
matchRequest: MatchRequest, matchRequest: MatchRequest,
defaultParams: IDefaultParams, defaultParams: IDefaultParams,
georouterCreator: ICreateGeorouter, georouterCreator: ICreateGeorouter,
timezoneFinder: IFindTimezone,
) { ) {
this._matchRequest = matchRequest; this.matchRequest = matchRequest;
this._defaultParams = defaultParams; this.defaultParams = defaultParams;
this._georouterCreator = georouterCreator; this.georouterCreator = georouterCreator;
this._setPerson(); this.timezoneFinder = timezoneFinder;
this._setRoles(); this.setPerson();
this._setTime(); this.setRoles();
this._setGeography(); this.setTime();
this._setRequirement(); this.setGeography();
this._setAlgorithmSettings(); this.setRequirement();
this._setExclusions(); this.setAlgorithmSettings();
this.setExclusions();
} }
createRoutes = (): void => { createRoutes = (): void => {
this.geography.createRoutes(this.roles, this.algorithmSettings.georouter); this.geography.createRoutes(this.roles, this.algorithmSettings.georouter);
}; };
_setPerson = (): void => { private setPerson = (): void => {
this.person = new Person( this.person = new Person(
this._matchRequest, this.matchRequest,
this._defaultParams.DEFAULT_IDENTIFIER, this.defaultParams.DEFAULT_IDENTIFIER,
this._defaultParams.MARGIN_DURATION, this.defaultParams.MARGIN_DURATION,
); );
this.person.init(); this.person.init();
}; };
_setRoles = (): void => { private setRoles = (): void => {
this.roles = []; this.roles = [];
if (this._matchRequest.driver) this.roles.push(Role.DRIVER); if (this.matchRequest.driver) this.roles.push(Role.DRIVER);
if (this._matchRequest.passenger) this.roles.push(Role.PASSENGER); if (this.matchRequest.passenger) this.roles.push(Role.PASSENGER);
if (this.roles.length == 0) 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.time = new Time(
this._matchRequest, this.matchRequest,
this._defaultParams.MARGIN_DURATION, this.defaultParams.MARGIN_DURATION,
this._defaultParams.VALIDITY_DURATION, this.defaultParams.VALIDITY_DURATION,
); );
this.time.init(); this.time.init();
}; };
_setGeography = (): void => { private setGeography = (): void => {
this.geography = new Geography( this.geography = new Geography(
this._matchRequest, this.matchRequest,
this._defaultParams.DEFAULT_TIMEZONE, {
timezone: this.defaultParams.DEFAULT_TIMEZONE,
finder: this.timezoneFinder,
},
this.person, this.person,
); );
this.geography.init(); this.geography.init();
}; };
_setRequirement = (): void => { private setRequirement = (): void => {
this.requirement = new Requirement( this.requirement = new Requirement(
this._matchRequest, this.matchRequest,
this._defaultParams.DEFAULT_SEATS, this.defaultParams.DEFAULT_SEATS,
); );
}; };
_setAlgorithmSettings = (): void => { private setAlgorithmSettings = (): void => {
this.algorithmSettings = new AlgorithmSettings( this.algorithmSettings = new AlgorithmSettings(
this._matchRequest, this.matchRequest,
this._defaultParams.DEFAULT_ALGORITHM_SETTINGS, this.defaultParams.DEFAULT_ALGORITHM_SETTINGS,
this.time.frequency, this.time.frequency,
this._georouterCreator, this.georouterCreator,
); );
}; };
_setExclusions = (): void => { private setExclusions = (): void => {
this.exclusions = []; this.exclusions = [];
if (this._matchRequest.identifier) if (this.matchRequest.identifier)
this.exclusions.push(this._matchRequest.identifier); this.exclusions.push(this.matchRequest.identifier);
if (this._matchRequest.exclusions) if (this.matchRequest.exclusions)
this.exclusions.push(...this._matchRequest.exclusions); this.exclusions.push(...this.matchRequest.exclusions);
}; };
} }

View File

@ -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>(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');
});
});

View File

@ -1,14 +1,38 @@
import { Test, TestingModule } from '@nestjs/testing';
import { MatcherGeodesic } from '../../../../adapters/secondaries/geodesic'; 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', () => { 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>(MatcherGeodesic);
});
it('should be defined', () => { it('should be defined', () => {
const geodesic: MatcherGeodesic = new MatcherGeodesic(); expect(matcherGeodesic).toBeDefined();
expect(geodesic).toBeDefined();
}); });
it('should get inverse values', () => { it('should get inverse values', () => {
const geodesic: MatcherGeodesic = new MatcherGeodesic(); const inv = matcherGeodesic.inverse(0, 0, 1, 1);
const inv = geodesic.inverse(0, 0, 1, 1);
expect(Math.round(inv.azimuth)).toBe(45); expect(Math.round(inv.azimuth)).toBe(45);
expect(Math.round(inv.distance)).toBe(156900); expect(Math.round(inv.distance)).toBe(50000);
}); });
}); });

View File

@ -6,7 +6,7 @@ import {
import { Role } from '../../../../domain/types/role.enum'; import { Role } from '../../../../domain/types/role.enum';
import { NamedRoute } from '../../../../domain/entities/ecosystem/named-route'; import { NamedRoute } from '../../../../domain/entities/ecosystem/named-route';
import { Route } from '../../../../domain/entities/ecosystem/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'; import { PointType } from '../../../../domain/types/geography.enum';
const person: Person = new Person( const person: Person = new Person(
@ -65,6 +65,10 @@ const mockGeorouter = {
}), }),
}; };
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
describe('Geography entity', () => { describe('Geography entity', () => {
it('should be defined', () => { it('should be defined', () => {
const geography = new Geography( const geography = new Geography(
@ -80,7 +84,10 @@ describe('Geography entity', () => {
}, },
], ],
}, },
'Europe/Paris', {
timezone: 'Europe/Paris',
finder: mockTimezoneFinder,
},
person, person,
); );
expect(geography).toBeDefined(); expect(geography).toBeDefined();
@ -103,7 +110,10 @@ describe('Geography entity', () => {
}, },
], ],
}, },
'Europe/Paris', {
timezone: 'Europe/Paris',
finder: mockTimezoneFinder,
},
person, person,
); );
geography.init(); geography.init();
@ -115,7 +125,10 @@ describe('Geography entity', () => {
{ {
waypoints: [], waypoints: [],
}, },
'Europe/Paris', {
timezone: 'Europe/Paris',
finder: mockTimezoneFinder,
},
person, person,
); );
expect(() => geography.init()).toThrow(); expect(() => geography.init()).toThrow();
@ -130,7 +143,10 @@ describe('Geography entity', () => {
}, },
], ],
}, },
'Europe/Paris', {
timezone: 'Europe/Paris',
finder: mockTimezoneFinder,
},
person, person,
); );
expect(() => geography.init()).toThrow(); expect(() => geography.init()).toThrow();
@ -149,7 +165,10 @@ describe('Geography entity', () => {
}, },
], ],
}, },
'Europe/Paris', {
timezone: 'Europe/Paris',
finder: mockTimezoneFinder,
},
person, person,
); );
expect(() => geography.init()).toThrow(); expect(() => geography.init()).toThrow();
@ -168,7 +187,10 @@ describe('Geography entity', () => {
}, },
], ],
}, },
'Europe/Paris', {
timezone: 'Europe/Paris',
finder: mockTimezoneFinder,
},
person, person,
); );
expect(() => geography.init()).toThrow(); expect(() => geography.init()).toThrow();
@ -190,7 +212,10 @@ describe('Geography entity', () => {
}, },
], ],
}, },
'Europe/Paris', {
timezone: 'Europe/Paris',
finder: mockTimezoneFinder,
},
person, person,
); );
geography.init(); geography.init();
@ -220,7 +245,10 @@ describe('Geography entity', () => {
}, },
], ],
}, },
'Europe/Paris', {
timezone: 'Europe/Paris',
finder: mockTimezoneFinder,
},
person, person,
); );
geography.init(); geography.init();
@ -246,7 +274,10 @@ describe('Geography entity', () => {
}, },
], ],
}, },
'Europe/Paris', {
timezone: 'Europe/Paris',
finder: mockTimezoneFinder,
},
person, person,
); );
geography.init(); geography.init();
@ -269,7 +300,10 @@ describe('Geography entity', () => {
}, },
], ],
}, },
'Europe/Paris', {
timezone: 'Europe/Paris',
finder: mockTimezoneFinder,
},
person, person,
); );
geography.init(); geography.init();

View File

@ -9,6 +9,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(), create: jest.fn().mockImplementation(),
}; };
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = { const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0, DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900, MARGIN_DURATION: 900,
@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest, matchRequest,
defaultParams, defaultParams,
mockGeorouterCreator, mockGeorouterCreator,
mockTimezoneFinder,
); );
describe('AlgorithmFactoryCreator', () => { describe('AlgorithmFactoryCreator', () => {

View File

@ -11,6 +11,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(), create: jest.fn().mockImplementation(),
}; };
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = { const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0, DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900, MARGIN_DURATION: 900,
@ -48,6 +52,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest, matchRequest,
defaultParams, defaultParams,
mockGeorouterCreator, mockGeorouterCreator,
mockTimezoneFinder,
); );
class FakeSelector extends Selector { class FakeSelector extends Selector {

View File

@ -9,6 +9,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(), create: jest.fn().mockImplementation(),
}; };
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = { const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0, DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900, MARGIN_DURATION: 900,
@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest, matchRequest,
defaultParams, defaultParams,
mockGeorouterCreator, mockGeorouterCreator,
mockTimezoneFinder,
); );
describe('ClassicAlgorithmFactory', () => { describe('ClassicAlgorithmFactory', () => {

View File

@ -9,6 +9,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(), create: jest.fn().mockImplementation(),
}; };
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = { const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0, DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900, MARGIN_DURATION: 900,
@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest, matchRequest,
defaultParams, defaultParams,
mockGeorouterCreator, mockGeorouterCreator,
mockTimezoneFinder,
); );
describe('ClassicGeoFilter', () => { describe('ClassicGeoFilter', () => {

View File

@ -9,6 +9,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(), create: jest.fn().mockImplementation(),
}; };
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = { const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0, DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900, MARGIN_DURATION: 900,
@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest, matchRequest,
defaultParams, defaultParams,
mockGeorouterCreator, mockGeorouterCreator,
mockTimezoneFinder,
); );
describe('ClassicTimeFilter', () => { describe('ClassicTimeFilter', () => {

View File

@ -9,6 +9,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(), create: jest.fn().mockImplementation(),
}; };
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = { const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0, DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900, MARGIN_DURATION: 900,
@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest, matchRequest,
defaultParams, defaultParams,
mockGeorouterCreator, mockGeorouterCreator,
mockTimezoneFinder,
); );
describe('ClassicWaypointCompleter', () => { describe('ClassicWaypointCompleter', () => {

View File

@ -9,6 +9,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(), create: jest.fn().mockImplementation(),
}; };
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = { const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0, DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900, MARGIN_DURATION: 900,
@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest, matchRequest,
defaultParams, defaultParams,
mockGeorouterCreator, mockGeorouterCreator,
mockTimezoneFinder,
); );
describe('ClassicSelector', () => { describe('ClassicSelector', () => {

View File

@ -9,6 +9,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(), create: jest.fn().mockImplementation(),
}; };
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = { const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0, DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900, MARGIN_DURATION: 900,
@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest, matchRequest,
defaultParams, defaultParams,
mockGeorouterCreator, mockGeorouterCreator,
mockTimezoneFinder,
); );
class FakeCompleter extends Completer { class FakeCompleter extends Completer {

View File

@ -9,6 +9,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(), create: jest.fn().mockImplementation(),
}; };
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = { const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0, DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900, MARGIN_DURATION: 900,
@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest, matchRequest,
defaultParams, defaultParams,
mockGeorouterCreator, mockGeorouterCreator,
mockTimezoneFinder,
); );
class FakeFilter extends Filter { class FakeFilter extends Filter {

View File

@ -9,6 +9,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(), create: jest.fn().mockImplementation(),
}; };
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = { const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0, DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900, MARGIN_DURATION: 900,
@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest, matchRequest,
defaultParams, defaultParams,
mockGeorouterCreator, mockGeorouterCreator,
mockTimezoneFinder,
); );
describe('JourneyCompleter', () => { describe('JourneyCompleter', () => {

View File

@ -21,6 +21,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(), create: jest.fn().mockImplementation(),
}; };
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = { const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0, DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900, MARGIN_DURATION: 900,
@ -58,6 +62,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest, matchRequest,
defaultParams, defaultParams,
mockGeorouterCreator, mockGeorouterCreator,
mockTimezoneFinder,
); );
describe('Matcher', () => { describe('Matcher', () => {

View File

@ -9,6 +9,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(), create: jest.fn().mockImplementation(),
}; };
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = { const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0, DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900, MARGIN_DURATION: 900,
@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest, matchRequest,
defaultParams, defaultParams,
mockGeorouterCreator, mockGeorouterCreator,
mockTimezoneFinder,
); );
class FakeProcessor extends Processor { class FakeProcessor extends Processor {

View File

@ -9,6 +9,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(), create: jest.fn().mockImplementation(),
}; };
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = { const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0, DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900, MARGIN_DURATION: 900,
@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest, matchRequest,
defaultParams, defaultParams,
mockGeorouterCreator, mockGeorouterCreator,
mockTimezoneFinder,
); );
describe('RouteCompleter', () => { describe('RouteCompleter', () => {

View File

@ -9,6 +9,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(), create: jest.fn().mockImplementation(),
}; };
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = { const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0, DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900, MARGIN_DURATION: 900,
@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest, matchRequest,
defaultParams, defaultParams,
mockGeorouterCreator, mockGeorouterCreator,
mockTimezoneFinder,
); );
class FakeSelector extends Selector { class FakeSelector extends Selector {

View File

@ -34,6 +34,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(), create: jest.fn().mockImplementation(),
}; };
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = { const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0, DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900, MARGIN_DURATION: 900,
@ -97,7 +101,12 @@ describe('MatchUseCase', () => {
describe('execute', () => { describe('execute', () => {
it('should return matches', async () => { it('should return matches', async () => {
const matches = await matchUseCase.execute( const matches = await matchUseCase.execute(
new MatchQuery(matchRequest, defaultParams, mockGeorouterCreator), new MatchQuery(
matchRequest,
defaultParams,
mockGeorouterCreator,
mockTimezoneFinder,
),
); );
expect(matches.total).toBe(3); expect(matches.total).toBe(3);
}); });
@ -105,7 +114,12 @@ describe('MatchUseCase', () => {
it('should throw an exception when error occurs', async () => { it('should throw an exception when error occurs', async () => {
await expect( await expect(
matchUseCase.execute( matchUseCase.execute(
new MatchQuery(matchRequest, defaultParams, mockGeorouterCreator), new MatchQuery(
matchRequest,
defaultParams,
mockGeorouterCreator,
mockTimezoneFinder,
),
), ),
).rejects.toBeInstanceOf(MatcherException); ).rejects.toBeInstanceOf(MatcherException);
}); });

View File

@ -30,6 +30,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(), create: jest.fn().mockImplementation(),
}; };
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
describe('Match query', () => { describe('Match query', () => {
it('should be defined', () => { it('should be defined', () => {
const matchRequest: MatchRequest = new MatchRequest(); const matchRequest: MatchRequest = new MatchRequest();
@ -48,6 +52,7 @@ describe('Match query', () => {
matchRequest, matchRequest,
defaultParams, defaultParams,
mockGeorouterCreator, mockGeorouterCreator,
mockTimezoneFinder,
); );
expect(matchQuery).toBeDefined(); expect(matchQuery).toBeDefined();
}); });
@ -71,6 +76,7 @@ describe('Match query', () => {
matchRequest, matchRequest,
defaultParams, defaultParams,
mockGeorouterCreator, mockGeorouterCreator,
mockTimezoneFinder,
); );
expect(matchQuery.exclusions.length).toBe(4); expect(matchQuery.exclusions.length).toBe(4);
}); });
@ -93,6 +99,7 @@ describe('Match query', () => {
matchRequest, matchRequest,
defaultParams, defaultParams,
mockGeorouterCreator, mockGeorouterCreator,
mockTimezoneFinder,
); );
expect(matchQuery.roles).toEqual([Role.DRIVER]); expect(matchQuery.roles).toEqual([Role.DRIVER]);
}); });
@ -115,6 +122,7 @@ describe('Match query', () => {
matchRequest, matchRequest,
defaultParams, defaultParams,
mockGeorouterCreator, mockGeorouterCreator,
mockTimezoneFinder,
); );
expect(matchQuery.roles).toEqual([Role.PASSENGER]); expect(matchQuery.roles).toEqual([Role.PASSENGER]);
}); });
@ -138,6 +146,7 @@ describe('Match query', () => {
matchRequest, matchRequest,
defaultParams, defaultParams,
mockGeorouterCreator, mockGeorouterCreator,
mockTimezoneFinder,
); );
expect(matchQuery.roles.length).toBe(2); expect(matchQuery.roles.length).toBe(2);
expect(matchQuery.roles).toContain(Role.PASSENGER); expect(matchQuery.roles).toContain(Role.PASSENGER);
@ -163,6 +172,7 @@ describe('Match query', () => {
matchRequest, matchRequest,
defaultParams, defaultParams,
mockGeorouterCreator, mockGeorouterCreator,
mockTimezoneFinder,
); );
expect(matchQuery.requirement.seatsDriver).toBe(1); expect(matchQuery.requirement.seatsDriver).toBe(1);
expect(matchQuery.requirement.seatsPassenger).toBe(2); expect(matchQuery.requirement.seatsPassenger).toBe(2);
@ -194,6 +204,7 @@ describe('Match query', () => {
matchRequest, matchRequest,
defaultParams, defaultParams,
mockGeorouterCreator, mockGeorouterCreator,
mockTimezoneFinder,
); );
expect(matchQuery.algorithmSettings.algorithmType).toBe( expect(matchQuery.algorithmSettings.algorithmType).toBe(
AlgorithmType.CLASSIC, AlgorithmType.CLASSIC,