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

@@ -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<string>('RMQ_EXCHANGE'),
routingKey: 'ad.created',
queue: `${configService.get<string>(
'RMQ_EXCHANGE',
)}-matcher-ad-created`,
queue: 'matcher-ad-created',
},
},
uri: configService.get<string>('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 {}

View File

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

View File

@@ -1,6 +1,6 @@
import { AutoMap } from '@automapper/classes';
export class Ad {
export class AdPresenter {
@AutoMap()
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()
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;
}

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

View File

@@ -202,9 +202,9 @@ export abstract class PrismaRepository<T> implements IRepository<T> {
async createWithFields(fields: object): Promise<number> {
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<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)`;
const values = Object.keys(entity).map((key) => `${key} = ${entity[key]}`);
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,
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 {

View File

@@ -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: [

View File

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

View File

@@ -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<ICollection<Match>> {
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({

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

View File

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

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 { 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<string>;
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,
);

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 { SpacetimePoint } from './spacetime-point';
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 { 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: [],
})

View File

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

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

View File

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

View File

@@ -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', () => {

View File

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

View File

@@ -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', () => {

View File

@@ -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', () => {

View File

@@ -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', () => {

View File

@@ -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', () => {

View File

@@ -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', () => {

View File

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

View File

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

View File

@@ -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', () => {

View File

@@ -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', () => {

View File

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

View File

@@ -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', () => {

View File

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

View File

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

View File

@@ -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,