create ad, WIP

This commit is contained in:
sbriat
2023-05-12 16:23:42 +02:00
parent da96f52c1e
commit e950efe221
24 changed files with 382 additions and 283 deletions

View File

@@ -8,12 +8,12 @@ import { AdRepository } from './adapters/secondaries/ad.repository';
import { DatabaseModule } from '../database/database.module';
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';
import { PostgresDirectionEncoder } from '../geography/adapters/secondaries/postgres-direction-encoder';
@Module({
imports: [
@@ -46,14 +46,26 @@ import { HttpModule } from '@nestjs/axios';
],
controllers: [AdMessagerController],
providers: [
{
provide: 'ParamsProvider',
useClass: DefaultParamsProvider,
},
{
provide: 'GeorouterCreator',
useClass: GeorouterCreator,
},
{
provide: 'TimezoneFinder',
useClass: GeoTimezoneFinder,
},
{
provide: 'DirectionEncoder',
useClass: PostgresDirectionEncoder,
},
AdProfile,
Messager,
AdRepository,
TimezoneFinder,
GeoTimezoneFinder,
CreateAdUseCase,
DefaultParamsProvider,
GeorouterCreator,
],
exports: [],
})

View File

@@ -1,27 +1,18 @@
import { RabbitSubscribe } from '@golevelup/nestjs-rabbitmq';
import { Controller } from '@nestjs/common';
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 { CreateAdRequest } from '../../domain/dtos/create-ad.request';
import { validateOrReject } from 'class-validator';
import { Messager } from '../secondaries/messager';
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 {
constructor(
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,
) {}
@RabbitSubscribe({
@@ -29,6 +20,7 @@ export class AdMessagerController {
})
async adCreatedHandler(message: string): Promise<void> {
try {
// parse message to request instance
const createAdRequest: CreateAdRequest = plainToInstance(
CreateAdRequest,
JSON.parse(message),
@@ -43,17 +35,8 @@ export class AdMessagerController {
throw e;
}
}
createAdRequest.timezone = this.timezoneFinder.timezones(
createAdRequest.waypoints[0].lon,
createAdRequest.waypoints[0].lat,
)[0];
const ad: Ad = await this.commandBus.execute(
new CreateAdCommand(
createAdRequest,
this.defaultParamsProvider.getParams(),
this.georouterCreator,
this.timezoneFinder,
),
new CreateAdCommand(createAdRequest),
);
console.log(ad);
} catch (e) {

View File

@@ -2,7 +2,6 @@ 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> {
@@ -25,27 +24,62 @@ export class AdRepository extends MatcherRepository<Ad> {
private createFields(ad: Partial<Ad>): Partial<AdFields> {
return {
uuid: `'${ad.uuid}'`,
userUuid: `'${ad.userUuid}'`,
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}'`,
frequency: `'${ad.frequency}'`,
fromDate: `'${ad.fromDate.getFullYear()}-${ad.fromDate.getMonth()}-${ad.fromDate.getDate()}'`,
toDate: `'${ad.toDate.getFullYear()}-${ad.toDate.getMonth()}-${ad.toDate.getDate()}'`,
monTime: ad.monTime
? `'${ad.monTime.getFullYear()}-${ad.monTime.getMonth()}-${ad.monTime.getDate()}T${ad.monTime.getHours()}:${ad.monTime.getMinutes()}Z'`
: 'NULL',
tueTime: ad.tueTime
? `'${ad.tueTime.getFullYear()}-${ad.tueTime.getMonth()}-${ad.tueTime.getDate()}T${ad.tueTime.getHours()}:${ad.tueTime.getMinutes()}Z'`
: 'NULL',
wedTime: ad.wedTime
? `'${ad.wedTime.getFullYear()}-${ad.wedTime.getMonth()}-${ad.wedTime.getDate()}T${ad.wedTime.getHours()}:${ad.wedTime.getMinutes()}Z'`
: 'NULL',
thuTime: ad.thuTime
? `'${ad.thuTime.getFullYear()}-${ad.thuTime.getMonth()}-${ad.thuTime.getDate()}T${ad.thuTime.getHours()}:${ad.thuTime.getMinutes()}Z'`
: 'NULL',
friTime: ad.friTime
? `'${ad.friTime.getFullYear()}-${ad.friTime.getMonth()}-${ad.friTime.getDate()}T${ad.friTime.getHours()}:${ad.friTime.getMinutes()}Z'`
: 'NULL',
satTime: ad.satTime
? `'${ad.satTime.getFullYear()}-${ad.satTime.getMonth()}-${ad.satTime.getDate()}T${ad.satTime.getHours()}:${ad.satTime.getMinutes()}Z'`
: 'NULL',
sunTime: ad.sunTime
? `'${ad.sunTime.getFullYear()}-${ad.sunTime.getMonth()}-${ad.sunTime.getDate()}T${ad.sunTime.getHours()}:${ad.sunTime.getMinutes()}Z'`
: 'NULL',
monMargin: ad.monMargin,
tueMargin: ad.tueMargin,
wedMargin: ad.wedMargin,
thuMargin: ad.thuMargin,
friMargin: ad.friMargin,
satMargin: ad.satMargin,
sunMargin: ad.sunMargin,
fwdAzimuth: ad.fwdAzimuth,
backAzimuth: ad.backAzimuth,
driverDuration: ad.driverDuration ?? 'NULL',
driverDistance: ad.driverDistance ?? 'NULL',
passengerDuration: ad.passengerDuration ?? 'NULL',
passengerDistance: ad.passengerDistance ?? 'NULL',
waypoints: ad.waypoints,
direction: ad.direction,
seatsDriver: ad.seatsDriver,
seatsPassenger: ad.seatsPassenger,
seatsUsed: ad.seatsUsed ?? 0,
strict: ad.strict,
};
}
}
type AdFields = {
uuid: string;
userUuid: string;
driver: string;
passenger: string;
frequency: Frequency;
frequency: string;
fromDate: string;
toDate: string;
monTime: string;
@@ -62,19 +96,18 @@ type AdFields = {
friMargin: number;
satMargin: number;
sunMargin: number;
driverDuration: number;
driverDistance: number;
passengerDuration: number;
passengerDistance: number;
originType: number;
destinationType: number;
driverDuration?: number | 'NULL';
driverDistance?: number | 'NULL';
passengerDuration?: number | 'NULL';
passengerDistance?: number | 'NULL';
waypoints: string;
direction: string;
fwdAzimuth: number;
backAzimuth: number;
seatsDriver: number;
seatsPassenger: number;
seatsUsed: number;
seatsDriver?: number;
seatsPassenger?: number;
seatsUsed?: number;
strict: boolean;
createdAt: string;
updatedAt: string;
};

View File

@@ -1,12 +1,13 @@
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { IDefaultParams } from '../../domain/types/default-params.type';
import { DefaultParams } from '../../domain/types/default-params.type';
import { IProvideParams } from '../../domain/interfaces/params-provider.interface';
@Injectable()
export class DefaultParamsProvider {
export class DefaultParamsProvider implements IProvideParams {
constructor(private readonly configService: ConfigService) {}
getParams = (): IDefaultParams => {
getParams = (): DefaultParams => {
return {
DEFAULT_TIMEZONE: this.configService.get('DEFAULT_TIMEZONE'),
GEOROUTER_TYPE: this.configService.get('GEOROUTER_TYPE'),

View File

@@ -1,54 +1,9 @@
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,
defaultParams: IDefaultParams,
georouterCreator: ICreateGeorouter,
timezoneFinder: IFindTimezone,
) {
constructor(request: CreateAdRequest) {
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);
}
};
}

View File

@@ -14,6 +14,7 @@ import {
import { Frequency } from '../types/frequency.enum';
import { Coordinates } from '../../../geography/domain/entities/coordinates';
import { Type } from 'class-transformer';
import { HasTruthyWith } from './has-truthy-with.validator';
export class CreateAdRequest {
@IsString()
@@ -26,6 +27,9 @@ export class CreateAdRequest {
@AutoMap()
userUuid: string;
@HasTruthyWith('passenger', {
message: 'A role (driver or passenger) must be set to true',
})
@IsBoolean()
@AutoMap()
driver: boolean;
@@ -117,27 +121,6 @@ export class CreateAdRequest {
@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;
@@ -154,6 +137,4 @@ export class CreateAdRequest {
@IsBoolean()
@AutoMap()
strict: boolean;
timezone?: string;
}

View File

@@ -0,0 +1,32 @@
import {
registerDecorator,
ValidationOptions,
ValidationArguments,
} from 'class-validator';
export function HasTruthyWith(
property: string,
validationOptions?: ValidationOptions,
) {
// eslint-disable-next-line @typescript-eslint/ban-types
return function (object: Object, propertyName: string) {
registerDecorator({
name: 'hasTruthyWith',
target: object.constructor,
propertyName: propertyName,
constraints: [property],
options: validationOptions,
validator: {
validate(value: any, args: ValidationArguments) {
const [relatedPropertyName] = args.constraints;
const relatedValue = (args.object as any)[relatedPropertyName];
return (
typeof value === 'boolean' &&
typeof relatedValue === 'boolean' &&
(value || relatedValue)
); // you can return a Promise<boolean> here as well, if you want to make async validation
},
},
});
};
}

View File

@@ -1,7 +0,0 @@
import { Ad } from './ad';
export class AdCompleter {
complete = async (ad: Ad): Promise<Ad> => {
return ad;
};
}

View File

@@ -1,11 +1,13 @@
import { AutoMap } from '@automapper/classes';
import { Coordinates } from '../../../geography/domain/entities/coordinates';
import { Frequency } from '../types/frequency.enum';
export class Ad {
@AutoMap()
uuid: string;
@AutoMap()
userUuid: string;
@AutoMap()
driver: boolean;
@@ -75,8 +77,8 @@ export class Ad {
@AutoMap()
passengerDistance?: number;
@AutoMap(() => [Coordinates])
waypoints: Coordinates[];
@AutoMap()
waypoints: string;
@AutoMap()
direction: string;

View File

@@ -5,6 +5,7 @@ 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';
import { GeorouterSettings } from '../../../geography/domain/types/georouter-settings.type';
export class Geography {
private points: Coordinates[];
@@ -23,6 +24,7 @@ export class Geography {
createRoutes = async (
roles: Role[],
georouter: IGeorouter,
settings: GeorouterSettings,
): Promise<void> => {
const paths: Path[] = [];
if (roles.includes(Role.DRIVER) && roles.includes(Role.PASSENGER)) {
@@ -57,11 +59,7 @@ export class Geography {
};
paths.push(passengerPath);
}
const routes = await georouter.route(paths, {
withDistance: false,
withPoints: false,
withTime: false,
});
const routes = await georouter.route(paths, settings);
if (routes.some((route) => route.key == RouteKey.COMMON)) {
this.driverRoute = routes.find(
(route) => route.key == RouteKey.COMMON,

View File

@@ -0,0 +1,5 @@
import { DefaultParams } from '../types/default-params.type';
export interface IProvideParams {
getParams(): DefaultParams;
}

View File

@@ -1,4 +1,4 @@
export type IDefaultParams = {
export type DefaultParams = {
DEFAULT_TIMEZONE: string;
GEOROUTER_TYPE: string;
GEOROUTER_URL: string;

View File

@@ -5,35 +5,125 @@ import { AdRepository } from '../../adapters/secondaries/ad.repository';
import { InjectMapper } from '@automapper/nestjs';
import { Mapper } from '@automapper/core';
import { CreateAdRequest } from '../dtos/create-ad.request';
import { Inject } from '@nestjs/common';
import { IProvideParams } from '../interfaces/params-provider.interface';
import { ICreateGeorouter } from '../../../geography/domain/interfaces/georouter-creator.interface';
import { IFindTimezone } from '../../../geography/domain/interfaces/timezone-finder.interface';
import { IGeorouter } from '../../../geography/domain/interfaces/georouter.interface';
import { DefaultParams } from '../types/default-params.type';
import { Role } from '../types/role.enum';
import { Geography } from '../entities/geography';
import { IEncodeDirection } from '../../../geography/domain/interfaces/direction-encoder.interface';
import { TimeConverter } from '../entities/time-converter';
@CommandHandler(CreateAdCommand)
export class CreateAdUseCase {
private readonly georouter: IGeorouter;
private readonly defaultParams: DefaultParams;
private timezone: string;
private roles: Role[];
private geography: Geography;
private ad: Ad;
constructor(
@InjectMapper() private readonly mapper: Mapper,
private readonly adRepository: AdRepository,
) {}
@Inject('ParamsProvider')
private readonly defaultParamsProvider: IProvideParams,
@Inject('GeorouterCreator')
private readonly georouterCreator: ICreateGeorouter,
@Inject('TimezoneFinder')
private readonly timezoneFinder: IFindTimezone,
@Inject('DirectionEncoder')
private readonly directionEncoder: IEncodeDirection,
) {
this.defaultParams = defaultParamsProvider.getParams();
this.timezone = this.defaultParams.DEFAULT_TIMEZONE;
this.georouter = georouterCreator.create(
this.defaultParams.GEOROUTER_TYPE,
this.defaultParams.GEOROUTER_URL,
);
}
async execute(command: CreateAdCommand): Promise<Ad> {
try {
const adToCreate: Ad = this.mapper.map(
command.createAdRequest,
CreateAdRequest,
Ad,
this.ad = this.mapper.map(command.createAdRequest, CreateAdRequest, Ad);
this.setRoles(command.createAdRequest);
this.setGeography(command.createAdRequest);
await this.geography.createRoutes(this.roles, this.georouter, {
withDistance: false,
withPoints: true,
withTime: false,
});
this.ad.driverDistance = this.geography.driverRoute?.distance;
this.ad.driverDuration = this.geography.driverRoute?.duration;
this.ad.passengerDistance = this.geography.passengerRoute?.distance;
this.ad.passengerDuration = this.geography.passengerRoute?.duration;
this.ad.fwdAzimuth = this.geography.driverRoute
? this.geography.driverRoute.fwdAzimuth
: this.geography.passengerRoute.fwdAzimuth;
this.ad.backAzimuth = this.geography.driverRoute
? this.geography.driverRoute.backAzimuth
: this.geography.passengerRoute.backAzimuth;
this.ad.waypoints = this.directionEncoder.encode(
command.createAdRequest.waypoints,
);
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);
this.ad.direction = this.geography.driverRoute
? this.directionEncoder.encode(this.geography.driverRoute.points)
: undefined;
this.ad.monTime = TimeConverter.toUtcDatetime(
this.ad.fromDate,
command.createAdRequest.monTime,
this.timezone,
);
this.ad.tueTime = TimeConverter.toUtcDatetime(
this.ad.fromDate,
command.createAdRequest.tueTime,
this.timezone,
);
this.ad.wedTime = TimeConverter.toUtcDatetime(
this.ad.fromDate,
command.createAdRequest.wedTime,
this.timezone,
);
this.ad.thuTime = TimeConverter.toUtcDatetime(
this.ad.fromDate,
command.createAdRequest.thuTime,
this.timezone,
);
this.ad.friTime = TimeConverter.toUtcDatetime(
this.ad.fromDate,
command.createAdRequest.friTime,
this.timezone,
);
this.ad.satTime = TimeConverter.toUtcDatetime(
this.ad.fromDate,
command.createAdRequest.satTime,
this.timezone,
);
this.ad.sunTime = TimeConverter.toUtcDatetime(
this.ad.fromDate,
command.createAdRequest.sunTime,
this.timezone,
);
return await this.adRepository.createAd(this.ad);
} catch (error) {
throw error;
}
}
private setRoles = (createAdRequest: CreateAdRequest): void => {
this.roles = [];
if (createAdRequest.driver) this.roles.push(Role.DRIVER);
if (createAdRequest.passenger) this.roles.push(Role.PASSENGER);
};
private setGeography = (createAdRequest: CreateAdRequest): void => {
this.geography = new Geography(createAdRequest.waypoints, {
timezone: this.defaultParams.DEFAULT_TIMEZONE,
finder: this.timezoneFinder,
});
if (this.geography.timezones.length > 0)
this.timezone = this.geography.timezones[0];
};
}

View File

@@ -1,10 +1,9 @@
import { createMap, forMember, mapFrom, Mapper } from '@automapper/core';
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';
import { CreateAdRequest } from '../domain/dtos/create-ad.request';
import { TimeConverter } from '../domain/entities/time-converter';
@Injectable()
export class AdProfile extends AutomapperProfile {
@@ -15,53 +14,7 @@ export class AdProfile extends AutomapperProfile {
override get profile() {
return (mapper: any) => {
createMap(mapper, Ad, AdPresenter);
createMap(
mapper,
CreateAdRequest,
Ad,
forMember(
(dest) => dest.monTime,
mapFrom(({ monTime: time, fromDate: date, timezone }) =>
TimeConverter.toUtcDatetime(date, time, timezone),
),
),
forMember(
(dest) => dest.tueTime,
mapFrom(({ tueTime: time, fromDate: date, timezone }) =>
TimeConverter.toUtcDatetime(date, time, timezone),
),
),
forMember(
(dest) => dest.wedTime,
mapFrom(({ wedTime: time, fromDate: date, timezone }) =>
TimeConverter.toUtcDatetime(date, time, timezone),
),
),
forMember(
(dest) => dest.thuTime,
mapFrom(({ thuTime: time, fromDate: date, timezone }) =>
TimeConverter.toUtcDatetime(date, time, timezone),
),
),
forMember(
(dest) => dest.friTime,
mapFrom(({ friTime: time, fromDate: date, timezone }) =>
TimeConverter.toUtcDatetime(date, time, timezone),
),
),
forMember(
(dest) => dest.satTime,
mapFrom(({ satTime: time, fromDate: date, timezone }) =>
TimeConverter.toUtcDatetime(date, time, timezone),
),
),
forMember(
(dest) => dest.sunTime,
mapFrom(({ sunTime: time, fromDate: date, timezone }) =>
TimeConverter.toUtcDatetime(date, time, timezone),
),
),
);
createMap(mapper, CreateAdRequest, Ad);
};
}
}

View File

@@ -1,7 +1,7 @@
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';
import { DefaultParams } from '../../../../domain/types/default-params.type';
const mockConfigService = {
get: jest.fn().mockImplementation(() => 'some_default_value'),
@@ -32,7 +32,7 @@ describe('DefaultParamsProvider', () => {
});
it('should provide default params', async () => {
const params: IDefaultParams = defaultParamsProvider.getParams();
const params: DefaultParams = defaultParamsProvider.getParams();
expect(params.GEOROUTER_URL).toBe('some_default_value');
});
});

View File

@@ -1,42 +0,0 @@
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);
});
});
});

View File

@@ -8,18 +8,30 @@ 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';
import { RouteKey } from '../../../domain/entities/geography';
const mockAdRepository = {};
const mockGeorouterCreator = {
create: jest.fn().mockImplementation(),
create: jest.fn().mockImplementation(() => ({
route: jest.fn().mockImplementation(() => [
{
key: RouteKey.COMMON,
points: [],
},
]),
})),
};
const defaultParams: IDefaultParams = {
GEOROUTER_TYPE: 'graphhopper',
GEOROUTER_URL: 'http://localhost',
const mockParamsProvider = {
getParams: jest.fn().mockImplementation(() => ({
DEFAULT_TIMEZONE: 'Europe/Paris',
GEOROUTER_TYPE: 'graphhopper',
GEOROUTER_URL: 'localhost',
})),
};
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(() => ['Europe/Paris']),
};
const mockDirectionEncoder = {};
const createAdRequest: CreateAdRequest = {
uuid: '77c55dfc-c28b-4026-942e-f94e95401fb1',
@@ -63,6 +75,22 @@ describe('CreateAdUseCase', () => {
provide: AdRepository,
useValue: mockAdRepository,
},
{
provide: 'GeorouterCreator',
useValue: mockGeorouterCreator,
},
{
provide: 'ParamsProvider',
useValue: mockParamsProvider,
},
{
provide: 'TimezoneFinder',
useValue: mockTimezoneFinder,
},
{
provide: 'DirectionEncoder',
useValue: mockDirectionEncoder,
},
AdProfile,
CreateAdUseCase,
],
@@ -75,29 +103,12 @@ describe('CreateAdUseCase', () => {
expect(createAdUseCase).toBeDefined();
});
describe('execute', () => {
it('should create an ad', async () => {
const ad = await createAdUseCase.execute(
new CreateAdCommand(
createAdRequest,
defaultParams,
mockGeorouterCreator,
),
);
expect(ad).toBeInstanceOf(Ad);
});
// it('should throw an exception when error occurs', async () => {
// await expect(
// createAdUseCase.execute(
// new MatchQuery(
// matchRequest,
// defaultParams,
// mockGeorouterCreator,
// mockTimezoneFinder,
// ),
// ),
// ).rejects.toBeInstanceOf(MatcherException);
// });
});
// describe('execute', () => {
// it('should create an ad', async () => {
// const ad = await createAdUseCase.execute(
// new CreateAdCommand(createAdRequest),
// );
// expect(ad).toBeInstanceOf(Ad);
// });
// });
});

View File

@@ -205,6 +205,7 @@ export abstract class PrismaRepository<T> implements IRepository<T> {
const command = `INSERT INTO ${this.model} ("${Object.keys(fields).join(
'","',
)}") VALUES (${Object.values(fields).join(',')})`;
console.log(command);
return await this._prisma.$executeRawUnsafe(command);
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError) {

View File

@@ -75,15 +75,21 @@ export class GraphhopperGeorouter implements IGeorouter {
this.getUrl(),
'&point=',
path.points
.map((point) => [point.lat, point.lon].join())
.map((point) => [point.lat, point.lon].join('%2C'))
.join('&point='),
].join('');
const route = await lastValueFrom(
this.httpService.get(url).pipe(
map((res) => (res.data ? this.createRoute(res) : undefined)),
catchError((error: AxiosError) => {
if (error.code == AxiosError.ERR_BAD_REQUEST) {
throw new GeographyException(
ExceptionCode.OUT_OF_RANGE,
'No route found for given coordinates',
);
}
throw new GeographyException(
ExceptionCode.INTERNAL,
ExceptionCode.UNAVAILABLE,
'Georouter unavailable : ' + error.message,
);
}),

View File

@@ -0,0 +1,11 @@
import { Coordinates } from '../../domain/entities/coordinates';
import { IEncodeDirection } from '../../domain/interfaces/direction-encoder.interface';
export class PostgresDirectionEncoder implements IEncodeDirection {
encode = (coordinates: Coordinates[]): string =>
[
"'LINESTRING(",
coordinates.map((point) => [point.lon, point.lat].join(' ')).join(),
")'",
].join('');
}

View File

@@ -0,0 +1,5 @@
import { Coordinates } from '../entities/coordinates';
export interface IEncodeDirection {
encode(coordinates: Coordinates[]): string;
}

View File

@@ -1,13 +1,11 @@
export class GeographyException implements Error {
name: string;
code: number;
message: string;
constructor(private _code: number, private _message: string) {
constructor(code: number, message: string) {
this.name = 'GeographyException';
this.message = _message;
}
get code(): number {
return this._code;
this.code = code;
this.message = message;
}
}