mirror of
https://gitlab.com/mobicoop/v3/service/matcher.git
synced 2025-12-31 08:22:41 +00:00
refactor to ddh, first commit
This commit is contained in:
140
old/modules/ad/domain/dtos/create-ad.request.ts
Normal file
140
old/modules/ad/domain/dtos/create-ad.request.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import { AutoMap } from '@automapper/classes';
|
||||
import {
|
||||
ArrayMinSize,
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsDate,
|
||||
IsEnum,
|
||||
IsMilitaryTime,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
} from 'class-validator';
|
||||
import { Frequency } from '../types/frequency.enum';
|
||||
import { Coordinate } from '../../../geography/domain/entities/coordinate';
|
||||
import { Type } from 'class-transformer';
|
||||
import { HasTruthyWith } from './has-truthy-with.validator';
|
||||
|
||||
export class CreateAdRequest {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@AutoMap()
|
||||
uuid: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@AutoMap()
|
||||
userUuid: string;
|
||||
|
||||
@HasTruthyWith('passenger', {
|
||||
message: 'A role (driver or passenger) must be set to true',
|
||||
})
|
||||
@IsBoolean()
|
||||
@AutoMap()
|
||||
driver: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@AutoMap()
|
||||
passenger: boolean;
|
||||
|
||||
@IsEnum(Frequency)
|
||||
@AutoMap()
|
||||
frequency: Frequency;
|
||||
|
||||
@Type(() => Date)
|
||||
@IsDate()
|
||||
@AutoMap()
|
||||
fromDate: Date;
|
||||
|
||||
@Type(() => Date)
|
||||
@IsDate()
|
||||
@AutoMap()
|
||||
toDate: Date;
|
||||
|
||||
@IsOptional()
|
||||
@IsMilitaryTime()
|
||||
@AutoMap()
|
||||
monTime?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsMilitaryTime()
|
||||
@AutoMap()
|
||||
tueTime?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsMilitaryTime()
|
||||
@AutoMap()
|
||||
wedTime?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsMilitaryTime()
|
||||
@AutoMap()
|
||||
thuTime?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsMilitaryTime()
|
||||
@AutoMap()
|
||||
friTime?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsMilitaryTime()
|
||||
@AutoMap()
|
||||
satTime?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsMilitaryTime()
|
||||
@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;
|
||||
|
||||
@Type(() => Coordinate)
|
||||
@IsArray()
|
||||
@ArrayMinSize(2)
|
||||
@AutoMap(() => [Coordinate])
|
||||
addresses: Coordinate[];
|
||||
|
||||
@IsNumber()
|
||||
@AutoMap()
|
||||
seatsDriver: number;
|
||||
|
||||
@IsNumber()
|
||||
@AutoMap()
|
||||
seatsPassenger: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@AutoMap()
|
||||
seatsUsed?: number;
|
||||
|
||||
@IsBoolean()
|
||||
@AutoMap()
|
||||
strict: boolean;
|
||||
}
|
||||
32
old/modules/ad/domain/dtos/has-truthy-with.validator.ts
Normal file
32
old/modules/ad/domain/dtos/has-truthy-with.validator.ts
Normal 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
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
109
old/modules/ad/domain/entities/ad.ts
Normal file
109
old/modules/ad/domain/entities/ad.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { AutoMap } from '@automapper/classes';
|
||||
import { Frequency } from '../types/frequency.enum';
|
||||
|
||||
export class Ad {
|
||||
@AutoMap()
|
||||
uuid: string;
|
||||
|
||||
@AutoMap()
|
||||
userUuid: string;
|
||||
|
||||
@AutoMap()
|
||||
driver: boolean;
|
||||
|
||||
@AutoMap()
|
||||
passenger: boolean;
|
||||
|
||||
@AutoMap()
|
||||
frequency: Frequency;
|
||||
|
||||
@AutoMap()
|
||||
fromDate: Date;
|
||||
|
||||
@AutoMap()
|
||||
toDate: Date;
|
||||
|
||||
@AutoMap()
|
||||
monTime: Date;
|
||||
|
||||
@AutoMap()
|
||||
tueTime: Date;
|
||||
|
||||
@AutoMap()
|
||||
wedTime: Date;
|
||||
|
||||
@AutoMap()
|
||||
thuTime: Date;
|
||||
|
||||
@AutoMap()
|
||||
friTime: Date;
|
||||
|
||||
@AutoMap()
|
||||
satTime: Date;
|
||||
|
||||
@AutoMap()
|
||||
sunTime: Date;
|
||||
|
||||
@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()
|
||||
waypoints: string;
|
||||
|
||||
@AutoMap()
|
||||
direction: string;
|
||||
|
||||
@AutoMap()
|
||||
fwdAzimuth: number;
|
||||
|
||||
@AutoMap()
|
||||
backAzimuth: number;
|
||||
|
||||
@AutoMap()
|
||||
seatsDriver: number;
|
||||
|
||||
@AutoMap()
|
||||
seatsPassenger: number;
|
||||
|
||||
@AutoMap()
|
||||
seatsUsed: number;
|
||||
|
||||
@AutoMap()
|
||||
strict: boolean;
|
||||
|
||||
@AutoMap()
|
||||
createdAt: Date;
|
||||
|
||||
@AutoMap()
|
||||
updatedAt: Date;
|
||||
}
|
||||
92
old/modules/ad/domain/entities/geography.ts
Normal file
92
old/modules/ad/domain/entities/geography.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { Coordinate } from '../../../geography/domain/entities/coordinate';
|
||||
import { Route } from '../../../geography/domain/entities/route';
|
||||
import { Role } from '../types/role.enum';
|
||||
import { IGeorouter } from '../../../geography/domain/interfaces/georouter.interface';
|
||||
import { Path } from '../../../geography/domain/types/path.type';
|
||||
import { GeorouterSettings } from '../../../geography/domain/types/georouter-settings.type';
|
||||
|
||||
export class Geography {
|
||||
private coordinates: Coordinate[];
|
||||
driverRoute: Route;
|
||||
passengerRoute: Route;
|
||||
|
||||
constructor(coordinates: Coordinate[]) {
|
||||
this.coordinates = coordinates;
|
||||
}
|
||||
|
||||
createRoutes = async (
|
||||
roles: Role[],
|
||||
georouter: IGeorouter,
|
||||
settings: GeorouterSettings,
|
||||
): Promise<void> => {
|
||||
const paths: Path[] = this.getPaths(roles);
|
||||
const routes = await georouter.route(paths, settings);
|
||||
if (routes.some((route) => route.key == RouteType.COMMON)) {
|
||||
this.driverRoute = routes.find(
|
||||
(route) => route.key == RouteType.COMMON,
|
||||
).route;
|
||||
this.passengerRoute = routes.find(
|
||||
(route) => route.key == RouteType.COMMON,
|
||||
).route;
|
||||
} else {
|
||||
if (routes.some((route) => route.key == RouteType.DRIVER)) {
|
||||
this.driverRoute = routes.find(
|
||||
(route) => route.key == RouteType.DRIVER,
|
||||
).route;
|
||||
}
|
||||
if (routes.some((route) => route.key == RouteType.PASSENGER)) {
|
||||
this.passengerRoute = routes.find(
|
||||
(route) => route.key == RouteType.PASSENGER,
|
||||
).route;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private getPaths = (roles: Role[]): Path[] => {
|
||||
const paths: Path[] = [];
|
||||
if (roles.includes(Role.DRIVER) && roles.includes(Role.PASSENGER)) {
|
||||
if (this.coordinates.length == 2) {
|
||||
// 2 points => same route for driver and passenger
|
||||
const commonPath: Path = {
|
||||
key: RouteType.COMMON,
|
||||
points: this.coordinates,
|
||||
};
|
||||
paths.push(commonPath);
|
||||
} else {
|
||||
const driverPath: Path = this.createDriverPath();
|
||||
const passengerPath: Path = this.createPassengerPath();
|
||||
paths.push(driverPath, passengerPath);
|
||||
}
|
||||
} else if (roles.includes(Role.DRIVER)) {
|
||||
const driverPath: Path = this.createDriverPath();
|
||||
paths.push(driverPath);
|
||||
} else if (roles.includes(Role.PASSENGER)) {
|
||||
const passengerPath: Path = this.createPassengerPath();
|
||||
paths.push(passengerPath);
|
||||
}
|
||||
return paths;
|
||||
};
|
||||
|
||||
private createDriverPath = (): Path => {
|
||||
return {
|
||||
key: RouteType.DRIVER,
|
||||
points: this.coordinates,
|
||||
};
|
||||
};
|
||||
|
||||
private createPassengerPath = (): Path => {
|
||||
return {
|
||||
key: RouteType.PASSENGER,
|
||||
points: [
|
||||
this.coordinates[0],
|
||||
this.coordinates[this.coordinates.length - 1],
|
||||
],
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export enum RouteType {
|
||||
COMMON = 'common',
|
||||
DRIVER = 'driver',
|
||||
PASSENGER = 'passenger',
|
||||
}
|
||||
19
old/modules/ad/domain/entities/time-converter.ts
Normal file
19
old/modules/ad/domain/entities/time-converter.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { DateTime, TimeZone } from 'timezonecomplete';
|
||||
|
||||
export class TimeConverter {
|
||||
static toUtcDatetime = (date: Date, time: string, timezone: string): Date => {
|
||||
try {
|
||||
if (!date || !time || !timezone) throw new Error();
|
||||
return new Date(
|
||||
new DateTime(
|
||||
`${date.toISOString().split('T')[0]}T${time}:00`,
|
||||
TimeZone.zone(timezone, false),
|
||||
)
|
||||
.convert(TimeZone.zone('UTC'))
|
||||
.toIsoString(),
|
||||
);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { DefaultParams } from '../types/default-params.type';
|
||||
|
||||
export interface IProvideParams {
|
||||
getParams(): DefaultParams;
|
||||
}
|
||||
5
old/modules/ad/domain/types/default-params.type.ts
Normal file
5
old/modules/ad/domain/types/default-params.type.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export type DefaultParams = {
|
||||
DEFAULT_TIMEZONE: string;
|
||||
GEOROUTER_TYPE: string;
|
||||
GEOROUTER_URL: string;
|
||||
};
|
||||
4
old/modules/ad/domain/types/frequency.enum.ts
Normal file
4
old/modules/ad/domain/types/frequency.enum.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum Frequency {
|
||||
PUNCTUAL = 'PUNCTUAL',
|
||||
RECURRENT = 'RECURRENT',
|
||||
}
|
||||
4
old/modules/ad/domain/types/role.enum.ts
Normal file
4
old/modules/ad/domain/types/role.enum.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum Role {
|
||||
DRIVER = 'DRIVER',
|
||||
PASSENGER = 'PASSENGER',
|
||||
}
|
||||
150
old/modules/ad/domain/usecases/create-ad.usecase.ts
Normal file
150
old/modules/ad/domain/usecases/create-ad.usecase.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import { CommandHandler } from '@nestjs/cqrs';
|
||||
import { CreateAdCommand } from '../../commands/create-ad.command';
|
||||
import { Ad } from '../entities/ad';
|
||||
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';
|
||||
import { Coordinate } from '../../../geography/domain/entities/coordinate';
|
||||
import {
|
||||
DIRECTION_ENCODER,
|
||||
GEOROUTER_CREATOR,
|
||||
PARAMS_PROVIDER,
|
||||
TIMEZONE_FINDER,
|
||||
} from '../../ad.constants';
|
||||
|
||||
@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(PARAMS_PROVIDER)
|
||||
private readonly defaultParamsProvider: IProvideParams,
|
||||
@Inject(GEOROUTER_CREATOR)
|
||||
private readonly georouterCreator: ICreateGeorouter,
|
||||
@Inject(TIMEZONE_FINDER)
|
||||
private readonly timezoneFinder: IFindTimezone,
|
||||
@Inject(DIRECTION_ENCODER)
|
||||
private readonly directionEncoder: IEncodeDirection,
|
||||
) {
|
||||
this.defaultParams = defaultParamsProvider.getParams();
|
||||
this.georouter = georouterCreator.create(
|
||||
this.defaultParams.GEOROUTER_TYPE,
|
||||
this.defaultParams.GEOROUTER_URL,
|
||||
);
|
||||
}
|
||||
|
||||
async execute(command: CreateAdCommand): Promise<Ad> {
|
||||
try {
|
||||
this.ad = this.mapper.map(command.createAdRequest, CreateAdRequest, Ad);
|
||||
this.setTimezone(command.createAdRequest.addresses);
|
||||
this.setGeography(command.createAdRequest.addresses);
|
||||
this.setRoles(command.createAdRequest);
|
||||
await this.geography.createRoutes(this.roles, this.georouter, {
|
||||
withDistance: false,
|
||||
withPoints: true,
|
||||
withTime: false,
|
||||
});
|
||||
this.setAdGeography(command);
|
||||
this.setAdSchedule(command);
|
||||
return await this.adRepository.createAd(this.ad);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private setTimezone = (coordinates: Coordinate[]): void => {
|
||||
this.timezone = this.defaultParams.DEFAULT_TIMEZONE;
|
||||
try {
|
||||
const timezones = this.timezoneFinder.timezones(
|
||||
coordinates[0].lon,
|
||||
coordinates[0].lat,
|
||||
);
|
||||
if (timezones.length > 0) this.timezone = timezones[0];
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
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 = (coordinates: Coordinate[]): void => {
|
||||
this.geography = new Geography(coordinates);
|
||||
};
|
||||
|
||||
private setAdGeography = (command: CreateAdCommand): void => {
|
||||
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.addresses,
|
||||
);
|
||||
this.ad.direction = this.geography.driverRoute
|
||||
? this.directionEncoder.encode(this.geography.driverRoute.points)
|
||||
: undefined;
|
||||
};
|
||||
|
||||
private setAdSchedule = (command: CreateAdCommand): void => {
|
||||
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,
|
||||
);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user