mirror of
https://gitlab.com/mobicoop/v3/service/matcher.git
synced 2026-01-01 13:52:40 +00:00
wip
This commit is contained in:
@@ -10,11 +10,17 @@ import { CqrsModule } from '@nestjs/cqrs';
|
||||
import { Messager } from './adapters/secondaries/messager';
|
||||
import { TimezoneFinder } from './adapters/secondaries/timezone-finder';
|
||||
import { GeoTimezoneFinder } from '../geography/adapters/secondaries/geo-timezone-finder';
|
||||
import { DefaultParamsProvider } from './adapters/secondaries/default-params.provider';
|
||||
import { GeorouterCreator } from '../geography/adapters/secondaries/georouter-creator';
|
||||
import { GeographyModule } from '../geography/geography.module';
|
||||
import { HttpModule } from '@nestjs/axios';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
GeographyModule,
|
||||
DatabaseModule,
|
||||
CqrsModule,
|
||||
HttpModule,
|
||||
RabbitMQModule.forRootAsync(RabbitMQModule, {
|
||||
imports: [ConfigModule],
|
||||
useFactory: async (configService: ConfigService) => ({
|
||||
@@ -46,6 +52,8 @@ import { GeoTimezoneFinder } from '../geography/adapters/secondaries/geo-timezon
|
||||
TimezoneFinder,
|
||||
GeoTimezoneFinder,
|
||||
CreateAdUseCase,
|
||||
DefaultParamsProvider,
|
||||
GeorouterCreator,
|
||||
],
|
||||
exports: [],
|
||||
})
|
||||
|
||||
@@ -8,7 +8,10 @@ import { CreateAdCommand } from '../../commands/create-ad.command';
|
||||
import { CreateAdRequest } from '../../domain/dtos/create-ad.request';
|
||||
import { validateOrReject } from 'class-validator';
|
||||
import { Messager } from '../secondaries/messager';
|
||||
import { GeoTimezoneFinder } from 'src/modules/geography/adapters/secondaries/geo-timezone-finder';
|
||||
import { GeoTimezoneFinder } from '../../../geography/adapters/secondaries/geo-timezone-finder';
|
||||
import { plainToInstance } from 'class-transformer';
|
||||
import { DefaultParamsProvider } from '../secondaries/default-params.provider';
|
||||
import { GeorouterCreator } from '../../../geography/adapters/secondaries/georouter-creator';
|
||||
|
||||
@Controller()
|
||||
export class AdMessagerController {
|
||||
@@ -16,6 +19,8 @@ export class AdMessagerController {
|
||||
private readonly messager: Messager,
|
||||
private readonly commandBus: CommandBus,
|
||||
@InjectMapper() private readonly mapper: Mapper,
|
||||
private readonly defaultParamsProvider: DefaultParamsProvider,
|
||||
private readonly georouterCreator: GeorouterCreator,
|
||||
private readonly timezoneFinder: GeoTimezoneFinder,
|
||||
) {}
|
||||
|
||||
@@ -23,15 +28,10 @@ export class AdMessagerController {
|
||||
name: 'adCreated',
|
||||
})
|
||||
async adCreatedHandler(message: string): Promise<void> {
|
||||
let createAdRequest: CreateAdRequest;
|
||||
try {
|
||||
// parse message to conform to CreateAdRequest (not a real instance yet)
|
||||
const parsedMessage: CreateAdRequest = JSON.parse(message);
|
||||
// create a real instance of CreateAdRequest from parsed message
|
||||
createAdRequest = this.mapper.map(
|
||||
parsedMessage,
|
||||
CreateAdRequest,
|
||||
const createAdRequest: CreateAdRequest = plainToInstance(
|
||||
CreateAdRequest,
|
||||
JSON.parse(message),
|
||||
);
|
||||
// validate instance
|
||||
await validateOrReject(createAdRequest);
|
||||
@@ -48,14 +48,20 @@ export class AdMessagerController {
|
||||
createAdRequest.waypoints[0].lat,
|
||||
)[0];
|
||||
const ad: Ad = await this.commandBus.execute(
|
||||
new CreateAdCommand(createAdRequest),
|
||||
new CreateAdCommand(
|
||||
createAdRequest,
|
||||
this.defaultParamsProvider.getParams(),
|
||||
this.georouterCreator,
|
||||
this.timezoneFinder,
|
||||
),
|
||||
);
|
||||
console.log(ad);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
this.messager.publish(
|
||||
'logging.matcher.ad.crit',
|
||||
JSON.stringify({
|
||||
createAdRequest,
|
||||
message,
|
||||
error: e,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -2,10 +2,11 @@ import { Injectable } from '@nestjs/common';
|
||||
import { MatcherRepository } from '../../../database/domain/matcher-repository';
|
||||
import { Ad } from '../../domain/entities/ad';
|
||||
import { DatabaseException } from '../../../database/exceptions/database.exception';
|
||||
import { Frequency } from '../../domain/types/frequency.enum';
|
||||
|
||||
@Injectable()
|
||||
export class AdRepository extends MatcherRepository<Ad> {
|
||||
protected _model = 'ad';
|
||||
protected model = 'ad';
|
||||
|
||||
async createAd(ad: Partial<Ad>): Promise<Ad> {
|
||||
try {
|
||||
@@ -44,7 +45,7 @@ type AdFields = {
|
||||
uuid: string;
|
||||
driver: string;
|
||||
passenger: string;
|
||||
frequency: number;
|
||||
frequency: Frequency;
|
||||
fromDate: string;
|
||||
toDate: string;
|
||||
monTime: string;
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { IDefaultParams } from '../../domain/types/default-params.type';
|
||||
|
||||
@Injectable()
|
||||
export class DefaultParamsProvider {
|
||||
constructor(private readonly configService: ConfigService) {}
|
||||
|
||||
getParams = (): IDefaultParams => {
|
||||
return {
|
||||
DEFAULT_TIMEZONE: this.configService.get('DEFAULT_TIMEZONE'),
|
||||
GEOROUTER_TYPE: this.configService.get('GEOROUTER_TYPE'),
|
||||
GEOROUTER_URL: this.configService.get('GEOROUTER_URL'),
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -6,13 +6,13 @@ import { MessageBroker } from './message-broker';
|
||||
@Injectable()
|
||||
export class Messager extends MessageBroker {
|
||||
constructor(
|
||||
private readonly _amqpConnection: AmqpConnection,
|
||||
private readonly amqpConnection: AmqpConnection,
|
||||
configService: ConfigService,
|
||||
) {
|
||||
super(configService.get<string>('RMQ_EXCHANGE'));
|
||||
}
|
||||
|
||||
publish = (routingKey: string, message: string): void => {
|
||||
this._amqpConnection.publish(this.exchange, routingKey, message);
|
||||
this.amqpConnection.publish(this.exchange, routingKey, message);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,9 +1,54 @@
|
||||
import { IGeorouter } from '../../geography/domain/interfaces/georouter.interface';
|
||||
import { ICreateGeorouter } from '../../geography/domain/interfaces/georouter-creator.interface';
|
||||
import { CreateAdRequest } from '../domain/dtos/create-ad.request';
|
||||
import { Geography } from '../domain/entities/geography';
|
||||
import { IDefaultParams } from '../domain/types/default-params.type';
|
||||
import { Role } from '../domain/types/role.enum';
|
||||
import { IFindTimezone } from '../../geography/domain/interfaces/timezone-finder.interface';
|
||||
|
||||
export class CreateAdCommand {
|
||||
readonly createAdRequest: CreateAdRequest;
|
||||
private readonly defaultParams: IDefaultParams;
|
||||
private readonly georouter: IGeorouter;
|
||||
private readonly timezoneFinder: IFindTimezone;
|
||||
roles: Role[];
|
||||
geography: Geography;
|
||||
timezone: string;
|
||||
|
||||
constructor(request: CreateAdRequest) {
|
||||
constructor(
|
||||
request: CreateAdRequest,
|
||||
defaultParams: IDefaultParams,
|
||||
georouterCreator: ICreateGeorouter,
|
||||
timezoneFinder: IFindTimezone,
|
||||
) {
|
||||
this.createAdRequest = request;
|
||||
this.defaultParams = defaultParams;
|
||||
this.georouter = georouterCreator.create(
|
||||
defaultParams.GEOROUTER_TYPE,
|
||||
defaultParams.GEOROUTER_URL,
|
||||
);
|
||||
this.timezoneFinder = timezoneFinder;
|
||||
this.setRoles();
|
||||
this.setGeography();
|
||||
}
|
||||
|
||||
private setRoles = (): void => {
|
||||
this.roles = [];
|
||||
if (this.createAdRequest.driver) this.roles.push(Role.DRIVER);
|
||||
if (this.createAdRequest.passenger) this.roles.push(Role.PASSENGER);
|
||||
};
|
||||
|
||||
private setGeography = async (): Promise<void> => {
|
||||
this.geography = new Geography(this.createAdRequest.waypoints, {
|
||||
timezone: this.defaultParams.DEFAULT_TIMEZONE,
|
||||
finder: this.timezoneFinder,
|
||||
});
|
||||
if (this.geography.timezones.length > 0)
|
||||
this.createAdRequest.timezone = this.geography.timezones[0];
|
||||
try {
|
||||
await this.geography.createRoutes(this.roles, this.georouter);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,15 +3,17 @@ import {
|
||||
ArrayMinSize,
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsDate,
|
||||
IsEnum,
|
||||
IsMilitaryTime,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
} from 'class-validator';
|
||||
import { PointType } from '../../../geography/domain/types/point-type.enum';
|
||||
import { Frequency } from '../types/frequency.enum';
|
||||
import { Coordinates } from '../../../geography/domain/entities/coordinates';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
export class CreateAdRequest {
|
||||
@IsString()
|
||||
@@ -19,6 +21,11 @@ export class CreateAdRequest {
|
||||
@AutoMap()
|
||||
uuid: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@AutoMap()
|
||||
userUuid: string;
|
||||
|
||||
@IsBoolean()
|
||||
@AutoMap()
|
||||
driver: boolean;
|
||||
@@ -27,53 +34,54 @@ export class CreateAdRequest {
|
||||
@AutoMap()
|
||||
passenger: boolean;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsEnum(Frequency)
|
||||
@AutoMap()
|
||||
frequency: Frequency;
|
||||
|
||||
@IsString()
|
||||
@Type(() => Date)
|
||||
@IsDate()
|
||||
@AutoMap()
|
||||
fromDate: string;
|
||||
fromDate: Date;
|
||||
|
||||
@IsString()
|
||||
@Type(() => Date)
|
||||
@IsDate()
|
||||
@AutoMap()
|
||||
toDate: string;
|
||||
toDate: Date;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@IsMilitaryTime()
|
||||
@AutoMap()
|
||||
monTime?: string | null;
|
||||
monTime?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@IsMilitaryTime()
|
||||
@AutoMap()
|
||||
tueTime?: string | null;
|
||||
tueTime?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@IsMilitaryTime()
|
||||
@AutoMap()
|
||||
wedTime?: string | null;
|
||||
wedTime?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@IsMilitaryTime()
|
||||
@AutoMap()
|
||||
thuTime?: string | null;
|
||||
thuTime?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@IsMilitaryTime()
|
||||
@AutoMap()
|
||||
friTime?: string | null;
|
||||
friTime?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@IsMilitaryTime()
|
||||
@AutoMap()
|
||||
satTime?: string | null;
|
||||
satTime?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@IsMilitaryTime()
|
||||
@AutoMap()
|
||||
sunTime?: string | null;
|
||||
sunTime?: string;
|
||||
|
||||
@IsNumber()
|
||||
@AutoMap()
|
||||
@@ -103,19 +111,33 @@ export class CreateAdRequest {
|
||||
@AutoMap()
|
||||
sunMargin: number;
|
||||
|
||||
@IsEnum(PointType)
|
||||
@AutoMap()
|
||||
originType: PointType;
|
||||
|
||||
@IsEnum(PointType)
|
||||
@AutoMap()
|
||||
destinationType: PointType;
|
||||
|
||||
@Type(() => Coordinates)
|
||||
@IsArray()
|
||||
@ArrayMinSize(2)
|
||||
@AutoMap(() => [Coordinates])
|
||||
waypoints: Coordinates[];
|
||||
|
||||
@AutoMap()
|
||||
driverDuration?: number;
|
||||
|
||||
@AutoMap()
|
||||
driverDistance?: number;
|
||||
|
||||
@AutoMap()
|
||||
passengerDuration?: number;
|
||||
|
||||
@AutoMap()
|
||||
passengerDistance?: number;
|
||||
|
||||
@AutoMap()
|
||||
direction: string;
|
||||
|
||||
@AutoMap()
|
||||
fwdAzimuth: number;
|
||||
|
||||
@AutoMap()
|
||||
backAzimuth: number;
|
||||
|
||||
@IsNumber()
|
||||
@AutoMap()
|
||||
seatsDriver: number;
|
||||
@@ -129,13 +151,9 @@ export class CreateAdRequest {
|
||||
@AutoMap()
|
||||
seatsUsed?: number;
|
||||
|
||||
@IsString()
|
||||
@IsBoolean()
|
||||
@AutoMap()
|
||||
createdAt: string;
|
||||
|
||||
@IsString()
|
||||
@AutoMap()
|
||||
updatedAt: string;
|
||||
strict: boolean;
|
||||
|
||||
timezone?: string;
|
||||
}
|
||||
|
||||
7
src/modules/ad/domain/entities/ad.completer.ts
Normal file
7
src/modules/ad/domain/entities/ad.completer.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Ad } from './ad';
|
||||
|
||||
export class AdCompleter {
|
||||
complete = async (ad: Ad): Promise<Ad> => {
|
||||
return ad;
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AutoMap } from '@automapper/classes';
|
||||
import { PointType } from '../../../geography/domain/types/point-type.enum';
|
||||
import { Coordinates } from '../../../geography/domain/entities/coordinates';
|
||||
import { Frequency } from '../types/frequency.enum';
|
||||
|
||||
export class Ad {
|
||||
@AutoMap()
|
||||
@@ -13,7 +13,7 @@ export class Ad {
|
||||
passenger: boolean;
|
||||
|
||||
@AutoMap()
|
||||
frequency: number;
|
||||
frequency: Frequency;
|
||||
|
||||
@AutoMap()
|
||||
fromDate: Date;
|
||||
@@ -64,22 +64,16 @@ export class Ad {
|
||||
sunMargin: number;
|
||||
|
||||
@AutoMap()
|
||||
driverDuration: number;
|
||||
driverDuration?: number;
|
||||
|
||||
@AutoMap()
|
||||
driverDistance: number;
|
||||
driverDistance?: number;
|
||||
|
||||
@AutoMap()
|
||||
passengerDuration: number;
|
||||
passengerDuration?: number;
|
||||
|
||||
@AutoMap()
|
||||
passengerDistance: number;
|
||||
|
||||
@AutoMap()
|
||||
originType: PointType;
|
||||
|
||||
@AutoMap()
|
||||
destinationType: PointType;
|
||||
passengerDistance?: number;
|
||||
|
||||
@AutoMap(() => [Coordinates])
|
||||
waypoints: Coordinates[];
|
||||
@@ -102,6 +96,9 @@ export class Ad {
|
||||
@AutoMap()
|
||||
seatsUsed: number;
|
||||
|
||||
@AutoMap()
|
||||
strict: boolean;
|
||||
|
||||
@AutoMap()
|
||||
createdAt: Date;
|
||||
|
||||
|
||||
98
src/modules/ad/domain/entities/geography.ts
Normal file
98
src/modules/ad/domain/entities/geography.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { Coordinates } from '../../../geography/domain/entities/coordinates';
|
||||
import { Route } from '../../../geography/domain/entities/route';
|
||||
import { IFindTimezone } from '../../../geography/domain/interfaces/timezone-finder.interface';
|
||||
import { Role } from '../types/role.enum';
|
||||
import { IGeorouter } from '../../../geography/domain/interfaces/georouter.interface';
|
||||
import { Path } from '../../../geography/domain/types/path.type';
|
||||
import { Timezoner } from '../../../geography/domain/types/timezoner';
|
||||
|
||||
export class Geography {
|
||||
private points: Coordinates[];
|
||||
timezones: string[];
|
||||
driverRoute: Route;
|
||||
passengerRoute: Route;
|
||||
timezoneFinder: IFindTimezone;
|
||||
|
||||
constructor(points: Coordinates[], timezoner: Timezoner) {
|
||||
this.points = points;
|
||||
this.timezones = [timezoner.timezone];
|
||||
this.timezoneFinder = timezoner.finder;
|
||||
this.setTimezones();
|
||||
}
|
||||
|
||||
createRoutes = async (
|
||||
roles: Role[],
|
||||
georouter: IGeorouter,
|
||||
): Promise<void> => {
|
||||
const paths: Path[] = [];
|
||||
if (roles.includes(Role.DRIVER) && roles.includes(Role.PASSENGER)) {
|
||||
if (this.points.length == 2) {
|
||||
// 2 points => same route for driver and passenger
|
||||
const commonPath: Path = {
|
||||
key: RouteKey.COMMON,
|
||||
points: this.points,
|
||||
};
|
||||
paths.push(commonPath);
|
||||
} else {
|
||||
const driverPath: Path = {
|
||||
key: RouteKey.DRIVER,
|
||||
points: this.points,
|
||||
};
|
||||
const passengerPath: Path = {
|
||||
key: RouteKey.PASSENGER,
|
||||
points: [this.points[0], this.points[this.points.length - 1]],
|
||||
};
|
||||
paths.push(driverPath, passengerPath);
|
||||
}
|
||||
} else if (roles.includes(Role.DRIVER)) {
|
||||
const driverPath: Path = {
|
||||
key: RouteKey.DRIVER,
|
||||
points: this.points,
|
||||
};
|
||||
paths.push(driverPath);
|
||||
} else if (roles.includes(Role.PASSENGER)) {
|
||||
const passengerPath: Path = {
|
||||
key: RouteKey.PASSENGER,
|
||||
points: [this.points[0], this.points[this.points.length - 1]],
|
||||
};
|
||||
paths.push(passengerPath);
|
||||
}
|
||||
const routes = await georouter.route(paths, {
|
||||
withDistance: false,
|
||||
withPoints: false,
|
||||
withTime: false,
|
||||
});
|
||||
if (routes.some((route) => route.key == RouteKey.COMMON)) {
|
||||
this.driverRoute = routes.find(
|
||||
(route) => route.key == RouteKey.COMMON,
|
||||
).route;
|
||||
this.passengerRoute = routes.find(
|
||||
(route) => route.key == RouteKey.COMMON,
|
||||
).route;
|
||||
} else {
|
||||
if (routes.some((route) => route.key == RouteKey.DRIVER)) {
|
||||
this.driverRoute = routes.find(
|
||||
(route) => route.key == RouteKey.DRIVER,
|
||||
).route;
|
||||
}
|
||||
if (routes.some((route) => route.key == RouteKey.PASSENGER)) {
|
||||
this.passengerRoute = routes.find(
|
||||
(route) => route.key == RouteKey.PASSENGER,
|
||||
).route;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private setTimezones = (): void => {
|
||||
this.timezones = this.timezoneFinder.timezones(
|
||||
this.points[0].lat,
|
||||
this.points[0].lon,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export enum RouteKey {
|
||||
COMMON = 'common',
|
||||
DRIVER = 'driver',
|
||||
PASSENGER = 'passenger',
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
import { DateTime, TimeZone } from 'timezonecomplete';
|
||||
|
||||
export class TimeConverter {
|
||||
static toUtcDatetime = (
|
||||
date: string,
|
||||
time: string,
|
||||
timezone: string,
|
||||
): Date => {
|
||||
static toUtcDatetime = (date: Date, time: string, timezone: string): Date => {
|
||||
try {
|
||||
if (!date || !time || !timezone) throw new Error();
|
||||
return new Date(
|
||||
new DateTime(`${date}T${time}:00`, TimeZone.zone(timezone, false))
|
||||
new DateTime(
|
||||
`${date.toISOString().split('T')[0]}T${time}:00`,
|
||||
TimeZone.zone(timezone, false),
|
||||
)
|
||||
.convert(TimeZone.zone('UTC'))
|
||||
.toIsoString(),
|
||||
);
|
||||
|
||||
5
src/modules/ad/domain/types/default-params.type.ts
Normal file
5
src/modules/ad/domain/types/default-params.type.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export type IDefaultParams = {
|
||||
DEFAULT_TIMEZONE: string;
|
||||
GEOROUTER_TYPE: string;
|
||||
GEOROUTER_URL: string;
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
export enum Frequency {
|
||||
PUNCTUAL = 1,
|
||||
RECURRENT = 2,
|
||||
PUNCTUAL = 'PUNCTUAL',
|
||||
RECURRENT = 'RECURRENT',
|
||||
}
|
||||
|
||||
4
src/modules/ad/domain/types/role.enum.ts
Normal file
4
src/modules/ad/domain/types/role.enum.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum Role {
|
||||
DRIVER = 'DRIVER',
|
||||
PASSENGER = 'PASSENGER',
|
||||
}
|
||||
@@ -20,6 +20,16 @@ export class CreateAdUseCase {
|
||||
CreateAdRequest,
|
||||
Ad,
|
||||
);
|
||||
adToCreate.driverDistance = command.geography.driverRoute?.distance;
|
||||
adToCreate.driverDuration = command.geography.driverRoute?.duration;
|
||||
adToCreate.passengerDistance = command.geography.passengerRoute?.distance;
|
||||
adToCreate.passengerDuration = command.geography.passengerRoute?.duration;
|
||||
adToCreate.fwdAzimuth = command.geography.driverRoute
|
||||
? command.geography.driverRoute.fwdAzimuth
|
||||
: command.geography.passengerRoute.fwdAzimuth;
|
||||
adToCreate.backAzimuth = command.geography.driverRoute
|
||||
? command.geography.driverRoute.backAzimuth
|
||||
: command.geography.passengerRoute.backAzimuth;
|
||||
return adToCreate;
|
||||
// return await this.adRepository.createAd(adToCreate);
|
||||
} catch (error) {
|
||||
|
||||
@@ -4,7 +4,6 @@ import { Injectable } from '@nestjs/common';
|
||||
import { Ad } from '../domain/entities/ad';
|
||||
import { AdPresenter } from '../adapters/primaries/ad.presenter';
|
||||
import { CreateAdRequest } from '../domain/dtos/create-ad.request';
|
||||
import { Coordinates } from '../../geography/domain/entities/coordinates';
|
||||
import { TimeConverter } from '../domain/entities/time-converter';
|
||||
|
||||
@Injectable()
|
||||
@@ -16,43 +15,10 @@ export class AdProfile extends AutomapperProfile {
|
||||
override get profile() {
|
||||
return (mapper: any) => {
|
||||
createMap(mapper, Ad, AdPresenter);
|
||||
createMap(
|
||||
mapper,
|
||||
CreateAdRequest,
|
||||
CreateAdRequest,
|
||||
forMember(
|
||||
(dest) => dest.waypoints,
|
||||
mapFrom((source) =>
|
||||
source.waypoints.map(
|
||||
(waypoint) =>
|
||||
new Coordinates(
|
||||
waypoint.lon ?? undefined,
|
||||
waypoint.lat ?? undefined,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
createMap(
|
||||
mapper,
|
||||
CreateAdRequest,
|
||||
Ad,
|
||||
forMember(
|
||||
(dest) => dest.fromDate,
|
||||
mapFrom((source) => new Date(source.fromDate)),
|
||||
),
|
||||
forMember(
|
||||
(dest) => dest.toDate,
|
||||
mapFrom((source) => new Date(source.toDate)),
|
||||
),
|
||||
forMember(
|
||||
(dest) => dest.createdAt,
|
||||
mapFrom((source) => new Date(source.createdAt)),
|
||||
),
|
||||
forMember(
|
||||
(dest) => dest.updatedAt,
|
||||
mapFrom((source) => new Date(source.updatedAt)),
|
||||
),
|
||||
forMember(
|
||||
(dest) => dest.monTime,
|
||||
mapFrom(({ monTime: time, fromDate: date, timezone }) =>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { DefaultParamsProvider } from '../../../../adapters/secondaries/default-params.provider';
|
||||
import { IDefaultParams } from '../../../../domain/types/default-params.type';
|
||||
|
||||
const mockConfigService = {
|
||||
get: jest.fn().mockImplementation(() => 'some_default_value'),
|
||||
};
|
||||
|
||||
describe('DefaultParamsProvider', () => {
|
||||
let defaultParamsProvider: DefaultParamsProvider;
|
||||
|
||||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [],
|
||||
providers: [
|
||||
DefaultParamsProvider,
|
||||
{
|
||||
provide: ConfigService,
|
||||
useValue: mockConfigService,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
defaultParamsProvider = module.get<DefaultParamsProvider>(
|
||||
DefaultParamsProvider,
|
||||
);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(defaultParamsProvider).toBeDefined();
|
||||
});
|
||||
|
||||
it('should provide default params', async () => {
|
||||
const params: IDefaultParams = defaultParamsProvider.getParams();
|
||||
expect(params.GEOROUTER_URL).toBe('some_default_value');
|
||||
});
|
||||
});
|
||||
42
src/modules/ad/tests/unit/domain/ad.completer.spec.ts
Normal file
42
src/modules/ad/tests/unit/domain/ad.completer.spec.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Frequency } from '../../../domain/types/frequency.enum';
|
||||
import { Ad } from '../../../domain/entities/ad';
|
||||
import { AdCompleter } from '../../../domain/entities/ad.completer';
|
||||
import { Coordinates } from '../../../../geography/domain/entities/coordinates';
|
||||
|
||||
const ad: Ad = new Ad();
|
||||
ad.driver = true;
|
||||
ad.passenger = false;
|
||||
ad.frequency = Frequency.RECURRENT;
|
||||
ad.fromDate = new Date('2023-05-01');
|
||||
ad.toDate = new Date('2024-05-01');
|
||||
ad.monTime = new Date('2023-05-01T06:00:00.000Z');
|
||||
ad.tueTime = new Date('2023-05-01T06:00:00.000Z');
|
||||
ad.wedTime = new Date('2023-05-01T06:00:00.000Z');
|
||||
ad.thuTime = new Date('2023-05-01T06:00:00.000Z');
|
||||
ad.friTime = new Date('2023-05-01T06:00:00.000Z');
|
||||
ad.monMargin = 900;
|
||||
ad.tueMargin = 900;
|
||||
ad.wedMargin = 900;
|
||||
ad.thuMargin = 900;
|
||||
ad.friMargin = 900;
|
||||
ad.satMargin = 900;
|
||||
ad.sunMargin = 900;
|
||||
ad.waypoints = [new Coordinates(6.18, 48.69), new Coordinates(6.44, 48.85)];
|
||||
ad.seatsDriver = 3;
|
||||
ad.seatsPassenger = 1;
|
||||
ad.strict = false;
|
||||
|
||||
describe('AdCompleter', () => {
|
||||
it('should be defined', () => {
|
||||
expect(new AdCompleter()).toBeDefined();
|
||||
});
|
||||
|
||||
describe('complete', () => {
|
||||
it('should complete an ad', async () => {
|
||||
const ad: Ad = new Ad();
|
||||
const adCompleter: AdCompleter = new AdCompleter();
|
||||
const completedAd: Ad = await adCompleter.complete(ad);
|
||||
expect(completedAd.fwdAzimuth).toBe(45);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,4 @@
|
||||
import { CreateAdRequest } from '../../../domain/dtos/create-ad.request';
|
||||
import { PointType } from '../../../../geography/domain/types/point-type.enum';
|
||||
import { CreateAdUseCase } from '../../../domain/usecases/create-ad.usecase';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AutomapperModule } from '@automapper/nestjs';
|
||||
@@ -8,16 +7,28 @@ import { AdRepository } from '../../../adapters/secondaries/ad.repository';
|
||||
import { CreateAdCommand } from '../../../commands/create-ad.command';
|
||||
import { Ad } from '../../../domain/entities/ad';
|
||||
import { AdProfile } from '../../../mappers/ad.profile';
|
||||
import { Frequency } from '../../../domain/types/frequency.enum';
|
||||
import { IDefaultParams } from '../../../domain/types/default-params.type';
|
||||
|
||||
const mockAdRepository = {};
|
||||
|
||||
const mockGeorouterCreator = {
|
||||
create: jest.fn().mockImplementation(),
|
||||
};
|
||||
|
||||
const defaultParams: IDefaultParams = {
|
||||
GEOROUTER_TYPE: 'graphhopper',
|
||||
GEOROUTER_URL: 'http://localhost',
|
||||
};
|
||||
|
||||
const createAdRequest: CreateAdRequest = {
|
||||
uuid: '77c55dfc-c28b-4026-942e-f94e95401fb1',
|
||||
userUuid: 'dfd993f6-7889-4876-9570-5e1d7b6e3f42',
|
||||
driver: true,
|
||||
passenger: false,
|
||||
frequency: 2,
|
||||
fromDate: '2023-04-26',
|
||||
toDate: '2024-04-25',
|
||||
frequency: Frequency.RECURRENT,
|
||||
fromDate: new Date('2023-04-26'),
|
||||
toDate: new Date('2024-04-25'),
|
||||
monTime: '07:00',
|
||||
tueTime: '07:00',
|
||||
wedTime: '07:00',
|
||||
@@ -32,12 +43,9 @@ const createAdRequest: CreateAdRequest = {
|
||||
friMargin: 900,
|
||||
satMargin: 900,
|
||||
sunMargin: 900,
|
||||
originType: PointType.OTHER,
|
||||
destinationType: PointType.OTHER,
|
||||
seatsDriver: 3,
|
||||
seatsPassenger: 1,
|
||||
createdAt: '2023-04-01 12:45',
|
||||
updatedAt: '2023-04-01 12:45',
|
||||
strict: false,
|
||||
waypoints: [
|
||||
{ lon: 6, lat: 45 },
|
||||
{ lon: 6.5, lat: 45.5 },
|
||||
@@ -70,7 +78,11 @@ describe('CreateAdUseCase', () => {
|
||||
describe('execute', () => {
|
||||
it('should create an ad', async () => {
|
||||
const ad = await createAdUseCase.execute(
|
||||
new CreateAdCommand(createAdRequest),
|
||||
new CreateAdCommand(
|
||||
createAdRequest,
|
||||
defaultParams,
|
||||
mockGeorouterCreator,
|
||||
),
|
||||
);
|
||||
expect(ad).toBeInstanceOf(Ad);
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ describe('TimeConverter', () => {
|
||||
it('should convert a Europe/Paris datetime to utc datetime', () => {
|
||||
expect(
|
||||
TimeConverter.toUtcDatetime(
|
||||
'2023-05-01',
|
||||
new Date('2023-05-01'),
|
||||
'07:00',
|
||||
'Europe/Paris',
|
||||
).getUTCHours(),
|
||||
@@ -20,16 +20,28 @@ describe('TimeConverter', () => {
|
||||
TimeConverter.toUtcDatetime(undefined, '07:00', 'Europe/Paris'),
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
TimeConverter.toUtcDatetime('2023-13-01', '07:00', 'Europe/Paris'),
|
||||
TimeConverter.toUtcDatetime(
|
||||
new Date('2023-13-01'),
|
||||
'07:00',
|
||||
'Europe/Paris',
|
||||
),
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
TimeConverter.toUtcDatetime('2023-05-01', undefined, 'Europe/Paris'),
|
||||
TimeConverter.toUtcDatetime(
|
||||
new Date('2023-05-01'),
|
||||
undefined,
|
||||
'Europe/Paris',
|
||||
),
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
TimeConverter.toUtcDatetime('2023-05-01', 'a', 'Europe/Paris'),
|
||||
TimeConverter.toUtcDatetime(new Date('2023-05-01'), 'a', 'Europe/Paris'),
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
TimeConverter.toUtcDatetime('2023-13-01', '07:00', 'OlympusMons/Mars'),
|
||||
TimeConverter.toUtcDatetime(
|
||||
new Date('2023-13-01'),
|
||||
'07:00',
|
||||
'OlympusMons/Mars',
|
||||
),
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user