This commit is contained in:
sbriat 2023-04-28 15:53:57 +02:00
parent 95310651d8
commit 1f9a9896e9
15 changed files with 129 additions and 65 deletions

16
package-lock.json generated
View File

@ -36,7 +36,8 @@
"got": "^11.8.6",
"ioredis": "^5.3.1",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.2.0"
"rxjs": "^7.2.0",
"timezonecomplete": "^5.12.4"
},
"devDependencies": {
"@nestjs/cli": "^9.0.0",
@ -8576,6 +8577,14 @@
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
"dev": true
},
"node_modules/timezonecomplete": {
"version": "5.12.4",
"resolved": "https://registry.npmjs.org/timezonecomplete/-/timezonecomplete-5.12.4.tgz",
"integrity": "sha512-K+ocagBAl5wu9Ifh5oHKhRRLb0wP7j0VjAzjboZsT6bnVmtJNRe3Wnk2IPp0C4Uc8HpLly3gbfUrTlJ3M7vCPA==",
"dependencies": {
"tzdata": "^1.0.25"
}
},
"node_modules/tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
@ -8867,6 +8876,11 @@
"node": ">=4.2.0"
}
},
"node_modules/tzdata": {
"version": "1.0.38",
"resolved": "https://registry.npmjs.org/tzdata/-/tzdata-1.0.38.tgz",
"integrity": "sha512-KIgVvZTLt+DWzr3MOENNLCLdsNB+usedRYYHCVfVbA7TDewj8mfjlWmj3Mv6FfdrvfeE6Oprt+qE47YiL90duQ=="
},
"node_modules/uid": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz",

View File

@ -59,7 +59,8 @@
"got": "^11.8.6",
"ioredis": "^5.3.1",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.2.0"
"rxjs": "^7.2.0",
"timezonecomplete": "^5.12.4"
},
"devDependencies": {
"@nestjs/cli": "^9.0.0",

View File

@ -7,6 +7,9 @@ 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';
import { Messager } from './adapters/secondaries/messager';
import { TimezoneFinder } from './adapters/secondaries/timezone-finder';
import { GeoTimezoneFinder } from '../geography/adapters/secondaries/geo-timezone-finder';
@Module({
imports: [
@ -36,7 +39,14 @@ import { CqrsModule } from '@nestjs/cqrs';
}),
],
controllers: [AdMessagerController],
providers: [AdProfile, AdRepository, CreateAdUseCase],
providers: [
AdProfile,
Messager,
AdRepository,
TimezoneFinder,
GeoTimezoneFinder,
CreateAdUseCase,
],
exports: [],
})
export class AdModule {}

View File

@ -6,51 +6,59 @@ 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 { ValidationError, validateOrReject } from 'class-validator';
import { validateOrReject } from 'class-validator';
import { Messager } from '../secondaries/messager';
import { GeoTimezoneFinder } from 'src/modules/geography/adapters/secondaries/geo-timezone-finder';
@Controller()
export class AdMessagerController {
constructor(
private readonly messager: Messager,
private readonly commandBus: CommandBus,
@InjectMapper() private readonly mapper: Mapper,
private readonly timezoneFinder: GeoTimezoneFinder,
) {}
@RabbitSubscribe({
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);
console.log(parsedMessage);
// create a real instance of CreateAdRequest from parsed message
// const createAdRequest: CreateAdRequest = this.mapper.map(
// parsedMessage,
// CreateAdRequest,
// CreateAdRequest,
// );
const createAdRequest = new CreateAdRequest();
createAdRequest.originType = parsedMessage.originType;
createAdRequest.destinationType = parsedMessage.destinationType;
createAdRequest.waypoints = parsedMessage.waypoints.map((waypoint) => ({
lon: waypoint.lon,
lat: waypoint.lat,
}));
console.log(createAdRequest);
createAdRequest = this.mapper.map(
parsedMessage,
CreateAdRequest,
CreateAdRequest,
);
// validate instance
await validateOrReject(createAdRequest.waypoints[0]);
await validateOrReject(createAdRequest);
// validate nested objects (fixes direct nested validation bug)
for (const waypoint of createAdRequest.waypoints) {
try {
await validateOrReject(waypoint);
} catch (e) {
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),
);
console.log(ad);
} catch (e) {
if (Array.isArray(e)) {
e.forEach((error) =>
error instanceof ValidationError
? console.log(error.constraints)
: console.log(error),
);
}
this.messager.publish(
'logging.matcher.ad.crit',
JSON.stringify({
createAdRequest,
error: e,
}),
);
}
}
}

View File

@ -0,0 +1,12 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export abstract class MessageBroker {
exchange: string;
constructor(exchange: string) {
this.exchange = exchange;
}
abstract publish(routingKey: string, message: string): void;
}

View File

@ -0,0 +1,18 @@
import { AmqpConnection } from '@golevelup/nestjs-rabbitmq';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { MessageBroker } from './message-broker';
@Injectable()
export class Messager extends MessageBroker {
constructor(
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);
};
}

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

View File

@ -8,12 +8,10 @@ import {
IsNumber,
IsOptional,
IsString,
ValidateNested,
} from 'class-validator';
import { PointType } from '../../../geography/domain/types/point-type.enum';
import { Frequency } from '../types/frequency.enum';
import { Coordinates } from '../../../geography/domain/types/coordinates.type';
import { Type } from 'class-transformer';
import { Coordinates } from '../../../geography/domain/entities/coordinates';
export class CreateAdRequest {
@IsString()
@ -114,9 +112,7 @@ export class CreateAdRequest {
destinationType: PointType;
@IsArray()
@ValidateNested({ each: true })
@ArrayMinSize(2)
@Type(() => Coordinates)
@AutoMap(() => [Coordinates])
waypoints: Coordinates[];
@ -140,4 +136,6 @@ export class CreateAdRequest {
@IsString()
@AutoMap()
updatedAt: string;
timezone: string;
}

View File

@ -1,6 +1,6 @@
import { AutoMap } from '@automapper/classes';
import { PointType } from '../../../geography/domain/types/point-type.enum';
import { Coordinates } from '../../../geography/domain/types/coordinates.type';
import { Coordinates } from '../../../geography/domain/entities/coordinates';
export class Ad {
@AutoMap()
@ -22,7 +22,7 @@ export class Ad {
toDate: Date;
@AutoMap()
monTime: string;
monTime: Date;
@AutoMap()
tueTime: string;

View File

@ -4,7 +4,8 @@ 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 'src/modules/geography/domain/types/coordinates.type';
import { Coordinates } from '../../geography/domain/entities/coordinates';
import moment from 'moment-timezone';
@Injectable()
export class AdProfile extends AutomapperProfile {
@ -21,16 +22,14 @@ export class AdProfile extends AutomapperProfile {
CreateAdRequest,
forMember(
(dest) => dest.waypoints,
mapFrom(
(source) =>
source.waypoints.map(
(waypoint) =>
new Coordinates(
waypoint.lon ?? undefined,
waypoint.lat ?? undefined,
),
),
// .filter((waypoint) => waypoint),
mapFrom((source) =>
source.waypoints.map(
(waypoint) =>
new Coordinates(
waypoint.lon ?? undefined,
waypoint.lat ?? undefined,
),
),
),
),
);
@ -54,6 +53,18 @@ export class AdProfile extends AutomapperProfile {
(dest) => dest.updatedAt,
mapFrom((source) => new Date(source.updatedAt)),
),
// forMember(
// (dest) => dest.monTime,
// mapFrom((source) =>
// source.monTime
// ? new Date(
// moment
// .tz(`${source.fromDate} ${source.monTime}`, source.timezone)
// .format(),
// )
// : undefined,
// ),
// ),
);
};
}

View File

@ -1,17 +0,0 @@
import { createMap, Mapper } from '@automapper/core';
import { AutomapperProfile, InjectMapper } from '@automapper/nestjs';
import { Injectable } from '@nestjs/common';
import { Coordinates } from '../../geography/domain/types/coordinates.type';
@Injectable()
export class CoordinatesProfile extends AutomapperProfile {
constructor(@InjectMapper() mapper: Mapper) {
super(mapper);
}
override get profile() {
return (mapper: any) => {
createMap(mapper, Coordinates, Coordinates);
};
}
}

View File

@ -1,5 +1,5 @@
import { PointType } from './point-type.enum';
import { Coordinates } from './coordinates.type';
import { Coordinates } from '../entities/coordinates';
export type Point = Coordinates & {
type?: PointType;

View File

@ -1,4 +1,4 @@
import { Coordinates } from 'src/modules/geography/domain/types/coordinates.type';
import { Coordinates } from '../../../../geography/domain/entities/coordinates';
export class SpacetimePoint {
coordinates: Coordinates;

View File

@ -11,7 +11,6 @@ import { Day } from '../../types/day.type';
export class Time {
private timeRequest: IRequestTime;
private defaultMarginDuration: number;
private defaultValidityDuration: number;
frequency: Frequency;
fromDate: Date;
@ -25,7 +24,6 @@ export class Time {
defaultValidityDuration: number,
) {
this.timeRequest = timeRequest;
this.defaultMarginDuration = defaultMarginDuration;
this.defaultValidityDuration = defaultValidityDuration;
this.schedule = {};
this.marginDurations = {