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", "got": "^11.8.6",
"ioredis": "^5.3.1", "ioredis": "^5.3.1",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rxjs": "^7.2.0" "rxjs": "^7.2.0",
"timezonecomplete": "^5.12.4"
}, },
"devDependencies": { "devDependencies": {
"@nestjs/cli": "^9.0.0", "@nestjs/cli": "^9.0.0",
@ -8576,6 +8577,14 @@
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
"dev": true "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": { "node_modules/tmp": {
"version": "0.0.33", "version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
@ -8867,6 +8876,11 @@
"node": ">=4.2.0" "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": { "node_modules/uid": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz",

View File

@ -59,7 +59,8 @@
"got": "^11.8.6", "got": "^11.8.6",
"ioredis": "^5.3.1", "ioredis": "^5.3.1",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rxjs": "^7.2.0" "rxjs": "^7.2.0",
"timezonecomplete": "^5.12.4"
}, },
"devDependencies": { "devDependencies": {
"@nestjs/cli": "^9.0.0", "@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 { AdRepository } from './adapters/secondaries/ad.repository';
import { DatabaseModule } from '../database/database.module'; import { DatabaseModule } from '../database/database.module';
import { CqrsModule } from '@nestjs/cqrs'; 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({ @Module({
imports: [ imports: [
@ -36,7 +39,14 @@ import { CqrsModule } from '@nestjs/cqrs';
}), }),
], ],
controllers: [AdMessagerController], controllers: [AdMessagerController],
providers: [AdProfile, AdRepository, CreateAdUseCase], providers: [
AdProfile,
Messager,
AdRepository,
TimezoneFinder,
GeoTimezoneFinder,
CreateAdUseCase,
],
exports: [], exports: [],
}) })
export class AdModule {} export class AdModule {}

View File

@ -6,51 +6,59 @@ import { Mapper } from '@automapper/core';
import { CommandBus } from '@nestjs/cqrs'; import { CommandBus } from '@nestjs/cqrs';
import { CreateAdCommand } from '../../commands/create-ad.command'; import { CreateAdCommand } from '../../commands/create-ad.command';
import { CreateAdRequest } from '../../domain/dtos/create-ad.request'; 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() @Controller()
export class AdMessagerController { export class AdMessagerController {
constructor( constructor(
private readonly messager: Messager,
private readonly commandBus: CommandBus, private readonly commandBus: CommandBus,
@InjectMapper() private readonly mapper: Mapper, @InjectMapper() private readonly mapper: Mapper,
private readonly timezoneFinder: GeoTimezoneFinder,
) {} ) {}
@RabbitSubscribe({ @RabbitSubscribe({
name: 'adCreated', name: 'adCreated',
}) })
async adCreatedHandler(message: string): Promise<void> { async adCreatedHandler(message: string): Promise<void> {
let createAdRequest: CreateAdRequest;
try { try {
// parse message to conform to CreateAdRequest (not a real instance yet) // parse message to conform to CreateAdRequest (not a real instance yet)
const parsedMessage: CreateAdRequest = JSON.parse(message); const parsedMessage: CreateAdRequest = JSON.parse(message);
console.log(parsedMessage);
// create a real instance of CreateAdRequest from parsed message // create a real instance of CreateAdRequest from parsed message
// const createAdRequest: CreateAdRequest = this.mapper.map( createAdRequest = this.mapper.map(
// parsedMessage, parsedMessage,
// CreateAdRequest, CreateAdRequest,
// 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);
// validate instance // 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( const ad: Ad = await this.commandBus.execute(
new CreateAdCommand(createAdRequest), new CreateAdCommand(createAdRequest),
); );
console.log(ad); console.log(ad);
} catch (e) { } catch (e) {
if (Array.isArray(e)) { this.messager.publish(
e.forEach((error) => 'logging.matcher.ad.crit',
error instanceof ValidationError JSON.stringify({
? console.log(error.constraints) createAdRequest,
: console.log(error), 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, IsNumber,
IsOptional, IsOptional,
IsString, IsString,
ValidateNested,
} from 'class-validator'; } from 'class-validator';
import { PointType } from '../../../geography/domain/types/point-type.enum'; import { PointType } from '../../../geography/domain/types/point-type.enum';
import { Frequency } from '../types/frequency.enum'; import { Frequency } from '../types/frequency.enum';
import { Coordinates } from '../../../geography/domain/types/coordinates.type'; import { Coordinates } from '../../../geography/domain/entities/coordinates';
import { Type } from 'class-transformer';
export class CreateAdRequest { export class CreateAdRequest {
@IsString() @IsString()
@ -114,9 +112,7 @@ export class CreateAdRequest {
destinationType: PointType; destinationType: PointType;
@IsArray() @IsArray()
@ValidateNested({ each: true })
@ArrayMinSize(2) @ArrayMinSize(2)
@Type(() => Coordinates)
@AutoMap(() => [Coordinates]) @AutoMap(() => [Coordinates])
waypoints: Coordinates[]; waypoints: Coordinates[];
@ -140,4 +136,6 @@ export class CreateAdRequest {
@IsString() @IsString()
@AutoMap() @AutoMap()
updatedAt: string; updatedAt: string;
timezone: string;
} }

View File

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

View File

@ -4,7 +4,8 @@ import { Injectable } from '@nestjs/common';
import { Ad } from '../domain/entities/ad'; import { Ad } from '../domain/entities/ad';
import { AdPresenter } from '../adapters/primaries/ad.presenter'; import { AdPresenter } from '../adapters/primaries/ad.presenter';
import { CreateAdRequest } from '../domain/dtos/create-ad.request'; 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() @Injectable()
export class AdProfile extends AutomapperProfile { export class AdProfile extends AutomapperProfile {
@ -21,16 +22,14 @@ export class AdProfile extends AutomapperProfile {
CreateAdRequest, CreateAdRequest,
forMember( forMember(
(dest) => dest.waypoints, (dest) => dest.waypoints,
mapFrom( mapFrom((source) =>
(source) => source.waypoints.map(
source.waypoints.map( (waypoint) =>
(waypoint) => new Coordinates(
new Coordinates( waypoint.lon ?? undefined,
waypoint.lon ?? undefined, waypoint.lat ?? undefined,
waypoint.lat ?? undefined, ),
), ),
),
// .filter((waypoint) => waypoint),
), ),
), ),
); );
@ -54,6 +53,18 @@ export class AdProfile extends AutomapperProfile {
(dest) => dest.updatedAt, (dest) => dest.updatedAt,
mapFrom((source) => new Date(source.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 { PointType } from './point-type.enum';
import { Coordinates } from './coordinates.type'; import { Coordinates } from '../entities/coordinates';
export type Point = Coordinates & { export type Point = Coordinates & {
type?: PointType; 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 { export class SpacetimePoint {
coordinates: Coordinates; coordinates: Coordinates;

View File

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