Merge branch 'update-ad' into 'release-1.8'
Draft: Propagate ad updates See merge request mobicoop/v3/service/matcher!47
This commit is contained in:
commit
b3b55041ca
|
@ -12,7 +12,7 @@
|
|||
"@grpc/grpc-js": "^1.9.14",
|
||||
"@grpc/proto-loader": "^0.7.10",
|
||||
"@mobicoop/configuration-module": "^8.0.0",
|
||||
"@mobicoop/ddd-library": "^2.4.3",
|
||||
"@mobicoop/ddd-library": "^2.5.0",
|
||||
"@mobicoop/health-module": "^2.3.2",
|
||||
"@mobicoop/message-broker-module": "^2.1.2",
|
||||
"@nestjs/axios": "^3.0.1",
|
||||
|
@ -1881,9 +1881,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@mobicoop/ddd-library": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@mobicoop/ddd-library/-/ddd-library-2.4.3.tgz",
|
||||
"integrity": "sha512-HxNtAfov8ne7XsFTSIDI811r3L1VDV9YUikgX7HPjrB8u2gQh6FQFnIz3Fjb/zWOGxrDEIy8HEM0AYmXkf8ULA==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@mobicoop/ddd-library/-/ddd-library-2.5.0.tgz",
|
||||
"integrity": "sha512-dTx7KTILs53HCqNx0BDVTzIZxfPW3pi0fZ4UMw/vDNm3oTqGA+jg7YBfNxn8yadM+j2dDIN5Kum43CmKGH8yYA==",
|
||||
"dependencies": {
|
||||
"@nestjs/event-emitter": "^2.0.3",
|
||||
"@nestjs/microservices": "^10.3.0",
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
"test:unit:ci": "jest --testPathPattern 'tests/unit/' --coverage",
|
||||
"test:integration": "npm run migrate:test && dotenv -e .env.test -- jest --testPathPattern 'tests/integration/' --verbose --runInBand",
|
||||
"test:integration:ci": "npm run migrate:test:ci && dotenv -e ci/.env.ci -- jest --testPathPattern 'tests/integration/' --runInBand",
|
||||
"test:cov": "jest --testPathPattern 'tests/unit/' --coverage",
|
||||
"test:watch": "jest --testPathPattern 'tests/unit/' --watch",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||
"migrate": "docker exec v3-matcher-api sh -c 'npx prisma migrate deploy'",
|
||||
"migrate:dev": "docker exec v3-matcher-api sh -c 'npx prisma migrate dev'",
|
||||
|
@ -35,7 +35,7 @@
|
|||
"@grpc/proto-loader": "^0.7.10",
|
||||
"@songkeys/nestjs-redis": "^10.0.0",
|
||||
"@mobicoop/configuration-module": "^8.0.0",
|
||||
"@mobicoop/ddd-library": "^2.4.3",
|
||||
"@mobicoop/ddd-library": "^2.5.0",
|
||||
"@mobicoop/health-module": "^2.3.2",
|
||||
"@mobicoop/message-broker-module": "^2.1.2",
|
||||
"@nestjs/axios": "^3.0.1",
|
||||
|
|
|
@ -10,11 +10,16 @@ export const GRPC_GEOROUTER_SERVICE_NAME = 'GeorouterService';
|
|||
export const MATCHER_AD_CREATED_ROUTING_KEY = 'matcher-ad.created';
|
||||
export const MATCHER_AD_CREATION_FAILED_ROUTING_KEY =
|
||||
'matcher-ad.creation-failed';
|
||||
export const MATCHER_AD_UPDATED_ROUTING_KEY = 'matcher-ad.updated';
|
||||
export const MATCHER_AD_UPDATE_FAILED_ROUTING_KEY = 'matcher-ad.update-failed';
|
||||
|
||||
// messaging input
|
||||
export const AD_CREATED_MESSAGE_HANDLER = 'adCreated';
|
||||
export const AD_CREATED_ROUTING_KEY = 'ad.created';
|
||||
export const AD_CREATED_QUEUE = 'matcher.ad.created';
|
||||
export const AD_UPDATED_MESSAGE_HANDLER = 'adUpdated';
|
||||
export const AD_UPDATED_ROUTING_KEY = 'ad.updated';
|
||||
export const AD_UPDATED_QUEUE = 'matcher.ad.updated';
|
||||
export const AD_DELETED_MESSAGE_HANDLER = 'adDeleted';
|
||||
export const AD_DELETED_ROUTING_KEY = 'ad.deleted';
|
||||
export const AD_DELETED_QUEUE = 'matcher.ad.deleted';
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
AdWriteExtraModel,
|
||||
AdWriteModel,
|
||||
ScheduleItemModel,
|
||||
ScheduleWriteModel,
|
||||
} from './infrastructure/ad.repository';
|
||||
|
||||
/**
|
||||
|
@ -38,9 +39,8 @@ export class AdMapper
|
|||
private readonly directionEncoder: DirectionEncoderPort,
|
||||
) {}
|
||||
|
||||
toPersistence = (entity: AdEntity): AdWriteModel => {
|
||||
toPersistence = (entity: AdEntity, update?: boolean): AdWriteModel => {
|
||||
const copy = entity.getProps();
|
||||
const now = new Date();
|
||||
const record: AdWriteModel = {
|
||||
uuid: copy.id,
|
||||
driver: copy.driver,
|
||||
|
@ -48,22 +48,7 @@ export class AdMapper
|
|||
frequency: copy.frequency,
|
||||
fromDate: new Date(copy.fromDate),
|
||||
toDate: new Date(copy.toDate),
|
||||
schedule: {
|
||||
create: copy.schedule.map((scheduleItem: ScheduleItemProps) => ({
|
||||
uuid: v4(),
|
||||
day: scheduleItem.day,
|
||||
time: new Date(
|
||||
1970,
|
||||
0,
|
||||
1,
|
||||
parseInt(scheduleItem.time.split(':')[0]),
|
||||
parseInt(scheduleItem.time.split(':')[1]),
|
||||
),
|
||||
margin: scheduleItem.margin,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
})),
|
||||
},
|
||||
schedule: this.toScheduleItemWriteModel(copy.schedule, update),
|
||||
seatsProposed: copy.seatsProposed,
|
||||
seatsRequested: copy.seatsRequested,
|
||||
strict: copy.strict,
|
||||
|
@ -73,12 +58,39 @@ export class AdMapper
|
|||
passengerDistance: copy.passengerDistance,
|
||||
fwdAzimuth: copy.fwdAzimuth,
|
||||
backAzimuth: copy.backAzimuth,
|
||||
createdAt: copy.createdAt,
|
||||
updatedAt: copy.updatedAt,
|
||||
};
|
||||
return record;
|
||||
};
|
||||
|
||||
toScheduleItemWriteModel = (
|
||||
schedule: ScheduleItemProps[],
|
||||
update?: boolean,
|
||||
): ScheduleWriteModel => {
|
||||
const now = new Date();
|
||||
const record: ScheduleWriteModel = {
|
||||
create: schedule.map((scheduleItem: ScheduleItemProps) => ({
|
||||
uuid: v4(),
|
||||
day: scheduleItem.day,
|
||||
time: new Date(
|
||||
1970,
|
||||
0,
|
||||
1,
|
||||
parseInt(scheduleItem.time.split(':')[0]),
|
||||
parseInt(scheduleItem.time.split(':')[1]),
|
||||
),
|
||||
margin: scheduleItem.margin,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
})),
|
||||
};
|
||||
if (update) {
|
||||
record.deleteMany = {
|
||||
createdAt: { lt: now },
|
||||
};
|
||||
}
|
||||
return record;
|
||||
};
|
||||
|
||||
toDomain = (record: AdReadModel): AdEntity =>
|
||||
new AdEntity({
|
||||
id: record.uuid,
|
||||
|
|
|
@ -31,6 +31,7 @@ import {
|
|||
import { AdMapper } from './ad.mapper';
|
||||
import { CreateAdService } from './core/application/commands/create-ad/create-ad.service';
|
||||
import { DeleteAdService } from './core/application/commands/delete-ad/delete-ad.service';
|
||||
import { UpdateAdService } from './core/application/commands/update-ad/update-ad.service';
|
||||
import { PublishMessageWhenMatcherAdIsCreatedDomainEventHandler } from './core/application/event-handlers/publish-message-when-matcher-ad-is-created.domain-event-handler';
|
||||
import { MatchQueryHandler } from './core/application/queries/match/match.query-handler';
|
||||
import { AdRepository } from './infrastructure/ad.repository';
|
||||
|
@ -44,6 +45,7 @@ import { TimezoneFinder } from './infrastructure/timezone-finder';
|
|||
import { MatchGrpcController } from './interface/grpc-controllers/match.grpc-controller';
|
||||
import { AdCreatedMessageHandler } from './interface/message-handlers/ad-created.message-handler';
|
||||
import { AdDeletedMessageHandler } from './interface/message-handlers/ad-deleted.message-handler';
|
||||
import { AdUpdatedMessageHandler } from './interface/message-handlers/ad-updated.message-handler';
|
||||
import { MatchMapper } from './match.mapper';
|
||||
import { MatchingMapper } from './matching.mapper';
|
||||
|
||||
|
@ -98,13 +100,21 @@ const imports = [
|
|||
|
||||
const grpcControllers = [MatchGrpcController];
|
||||
|
||||
const messageHandlers = [AdCreatedMessageHandler, AdDeletedMessageHandler];
|
||||
const messageHandlers = [
|
||||
AdCreatedMessageHandler,
|
||||
AdUpdatedMessageHandler,
|
||||
AdDeletedMessageHandler,
|
||||
];
|
||||
|
||||
const eventHandlers: Provider[] = [
|
||||
PublishMessageWhenMatcherAdIsCreatedDomainEventHandler,
|
||||
];
|
||||
|
||||
const commandHandlers: Provider[] = [CreateAdService, DeleteAdService];
|
||||
const commandHandlers: Provider[] = [
|
||||
CreateAdService,
|
||||
UpdateAdService,
|
||||
DeleteAdService,
|
||||
];
|
||||
|
||||
const queryHandlers: Provider[] = [MatchQueryHandler];
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
import { Command, CommandProps } from '@mobicoop/ddd-library';
|
||||
import { ScheduleItem } from '../../types/schedule-item.type';
|
||||
import { Frequency, UserAd } from '@modules/ad/core/domain/ad.types';
|
||||
import { Address } from '../../types/address.type';
|
||||
import { ScheduleItem } from '../../types/schedule-item.type';
|
||||
|
||||
export class CreateAdCommand extends Command {
|
||||
export class CreateAdCommand extends Command implements UserAd {
|
||||
readonly id: string;
|
||||
readonly driver: boolean;
|
||||
readonly passenger: boolean;
|
||||
|
|
|
@ -1,32 +1,22 @@
|
|||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
||||
import { CreateAdCommand } from './create-ad.command';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import {
|
||||
AD_MESSAGE_PUBLISHER,
|
||||
AD_REPOSITORY,
|
||||
AD_ROUTE_PROVIDER,
|
||||
} from '@modules/ad/ad.di-tokens';
|
||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||
import { AdRepositoryPort } from '../../ports/ad.repository.port';
|
||||
import {
|
||||
AggregateID,
|
||||
ConflictException,
|
||||
MessagePublisherPort,
|
||||
} from '@mobicoop/ddd-library';
|
||||
import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors';
|
||||
import { Role } from '@modules/ad/core/domain/ad.types';
|
||||
import {
|
||||
Path,
|
||||
PathCreator,
|
||||
PathType,
|
||||
TypedRoute,
|
||||
} from '@modules/ad/core/domain/path-creator.service';
|
||||
import { Waypoint } from '../../types/waypoint.type';
|
||||
import { Point as PointValueObject } from '@modules/ad/core/domain/value-objects/point.value-object';
|
||||
import { Point } from '@modules/geography/core/domain/route.types';
|
||||
import { MatcherAdCreationFailedIntegrationEvent } from '../../events/matcher-ad-creation-failed.integration-event';
|
||||
AD_MESSAGE_PUBLISHER,
|
||||
AD_REPOSITORY,
|
||||
AD_ROUTE_PROVIDER,
|
||||
} from '@modules/ad/ad.di-tokens';
|
||||
import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors';
|
||||
import { AdFactory } from '@modules/ad/core/domain/ad.factory';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
||||
import { MATCHER_AD_CREATION_FAILED_ROUTING_KEY } from '@src/app.constants';
|
||||
import { GeorouterPort } from '../../ports/georouter.port';
|
||||
import { GeorouterService } from '../../../domain/georouter.service';
|
||||
import { MatcherAdCreationFailedIntegrationEvent } from '../../events/matcher-ad-failure.integration-event';
|
||||
import { AdRepositoryPort } from '../../ports/ad.repository.port';
|
||||
import { CreateAdCommand } from './create-ad.command';
|
||||
|
||||
@CommandHandler(CreateAdCommand)
|
||||
export class CreateAdService implements ICommandHandler {
|
||||
|
@ -36,106 +26,16 @@ export class CreateAdService implements ICommandHandler {
|
|||
@Inject(AD_REPOSITORY)
|
||||
private readonly repository: AdRepositoryPort,
|
||||
@Inject(AD_ROUTE_PROVIDER)
|
||||
private readonly routeProvider: GeorouterPort,
|
||||
private readonly routeProvider: GeorouterService,
|
||||
) {}
|
||||
|
||||
async execute(command: CreateAdCommand): Promise<AggregateID> {
|
||||
const roles: Role[] = [];
|
||||
if (command.driver) roles.push(Role.DRIVER);
|
||||
if (command.passenger) roles.push(Role.PASSENGER);
|
||||
|
||||
const pathCreator: PathCreator = new PathCreator(
|
||||
roles,
|
||||
command.waypoints.map(
|
||||
(waypoint: Waypoint) =>
|
||||
new PointValueObject({
|
||||
lon: waypoint.lon,
|
||||
lat: waypoint.lat,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
let typedRoutes: TypedRoute[];
|
||||
let driverDistance: number | undefined;
|
||||
let driverDuration: number | undefined;
|
||||
let passengerDistance: number | undefined;
|
||||
let passengerDuration: number | undefined;
|
||||
let points: PointValueObject[] | undefined;
|
||||
let fwdAzimuth: number | undefined;
|
||||
let backAzimuth: number | undefined;
|
||||
|
||||
try {
|
||||
try {
|
||||
typedRoutes = await Promise.all(
|
||||
pathCreator.getBasePaths().map(async (path: Path) => ({
|
||||
type: path.type,
|
||||
route: await this.routeProvider.getRoute({
|
||||
waypoints: path.waypoints,
|
||||
}),
|
||||
})),
|
||||
);
|
||||
} catch (e: any) {
|
||||
throw new Error('Unable to find a route for given waypoints');
|
||||
}
|
||||
|
||||
try {
|
||||
typedRoutes.forEach((typedRoute: TypedRoute) => {
|
||||
if ([PathType.DRIVER, PathType.GENERIC].includes(typedRoute.type)) {
|
||||
driverDistance = typedRoute.route.distance;
|
||||
driverDuration = typedRoute.route.duration;
|
||||
points = typedRoute.route.points.map(
|
||||
(point: Point) =>
|
||||
new PointValueObject({
|
||||
lon: point.lon,
|
||||
lat: point.lat,
|
||||
}),
|
||||
);
|
||||
fwdAzimuth = typedRoute.route.fwdAzimuth;
|
||||
backAzimuth = typedRoute.route.backAzimuth;
|
||||
}
|
||||
if (
|
||||
[PathType.PASSENGER, PathType.GENERIC].includes(typedRoute.type)
|
||||
) {
|
||||
passengerDistance = typedRoute.route.distance;
|
||||
passengerDuration = typedRoute.route.duration;
|
||||
if (!points)
|
||||
points = typedRoute.route.points.map(
|
||||
(point: Point) =>
|
||||
new PointValueObject({
|
||||
lon: point.lon,
|
||||
lat: point.lat,
|
||||
}),
|
||||
);
|
||||
if (!fwdAzimuth) fwdAzimuth = typedRoute.route.fwdAzimuth;
|
||||
if (!backAzimuth) backAzimuth = typedRoute.route.backAzimuth;
|
||||
}
|
||||
});
|
||||
} catch (error: any) {
|
||||
throw new Error('Invalid route');
|
||||
}
|
||||
|
||||
const ad = AdEntity.create({
|
||||
id: command.id,
|
||||
driver: command.driver,
|
||||
passenger: command.passenger,
|
||||
frequency: command.frequency,
|
||||
fromDate: command.fromDate,
|
||||
toDate: command.toDate,
|
||||
schedule: command.schedule,
|
||||
seatsProposed: command.seatsProposed,
|
||||
seatsRequested: command.seatsRequested,
|
||||
strict: command.strict,
|
||||
waypoints: command.waypoints,
|
||||
points: points as PointValueObject[],
|
||||
driverDistance,
|
||||
driverDuration,
|
||||
passengerDistance,
|
||||
passengerDuration,
|
||||
fwdAzimuth: fwdAzimuth as number,
|
||||
backAzimuth: backAzimuth as number,
|
||||
});
|
||||
const adFactory = new AdFactory(this.routeProvider);
|
||||
const ad = await adFactory.create(command);
|
||||
|
||||
try {
|
||||
//TODO it should not be this service's concern that Prisma does not support postgis types
|
||||
await this.repository.insertExtra(ad, 'ad');
|
||||
return ad.id;
|
||||
} catch (error: any) {
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
import { CreateAdCommand } from '../create-ad/create-ad.command';
|
||||
|
||||
export class UpdateAdCommand extends CreateAdCommand {}
|
|
@ -0,0 +1,48 @@
|
|||
import { MessagePublisherPort } from '@mobicoop/ddd-library';
|
||||
import {
|
||||
AD_MESSAGE_PUBLISHER,
|
||||
AD_REPOSITORY,
|
||||
AD_ROUTE_PROVIDER,
|
||||
} from '@modules/ad/ad.di-tokens';
|
||||
import { AdFactory } from '@modules/ad/core/domain/ad.factory';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
||||
import { MATCHER_AD_UPDATE_FAILED_ROUTING_KEY } from '@src/app.constants';
|
||||
import { GeorouterService } from '../../../domain/georouter.service';
|
||||
import { MatcherAdUpdateFailedIntegrationEvent } from '../../events/matcher-ad-failure.integration-event';
|
||||
import { AdRepositoryPort } from '../../ports/ad.repository.port';
|
||||
import { UpdateAdCommand } from './update-ad.command';
|
||||
|
||||
@CommandHandler(UpdateAdCommand)
|
||||
export class UpdateAdService implements ICommandHandler {
|
||||
constructor(
|
||||
@Inject(AD_MESSAGE_PUBLISHER)
|
||||
private readonly messagePublisher: MessagePublisherPort,
|
||||
@Inject(AD_REPOSITORY)
|
||||
private readonly repository: AdRepositoryPort,
|
||||
@Inject(AD_ROUTE_PROVIDER)
|
||||
private readonly routeProvider: GeorouterService,
|
||||
) {}
|
||||
|
||||
async execute(command: UpdateAdCommand): Promise<void> {
|
||||
try {
|
||||
const adFactory = new AdFactory(this.routeProvider);
|
||||
const ad = await adFactory.create(command);
|
||||
return this.repository.update(ad.id, ad);
|
||||
} catch (error: any) {
|
||||
const integrationEvent = new MatcherAdUpdateFailedIntegrationEvent({
|
||||
id: command.id,
|
||||
metadata: {
|
||||
correlationId: command.id,
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
cause: error.message,
|
||||
});
|
||||
this.messagePublisher.publish(
|
||||
MATCHER_AD_UPDATE_FAILED_ROUTING_KEY,
|
||||
JSON.stringify(integrationEvent),
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
import { IntegrationEvent, IntegrationEventProps } from '@mobicoop/ddd-library';
|
||||
|
||||
export class MatcherAdCreationFailedIntegrationEvent extends IntegrationEvent {
|
||||
readonly cause?: string;
|
||||
|
||||
constructor(
|
||||
props: IntegrationEventProps<MatcherAdCreationFailedIntegrationEvent>,
|
||||
) {
|
||||
super(props);
|
||||
this.cause = props.cause;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import { IntegrationEvent, IntegrationEventProps } from '@mobicoop/ddd-library';
|
||||
|
||||
export class MatcherAdFailureIntegrationEvent extends IntegrationEvent {
|
||||
readonly cause?: string;
|
||||
|
||||
constructor(
|
||||
props: IntegrationEventProps<MatcherAdCreationFailedIntegrationEvent>,
|
||||
) {
|
||||
super(props);
|
||||
this.cause = props.cause;
|
||||
}
|
||||
}
|
||||
|
||||
export class MatcherAdCreationFailedIntegrationEvent extends MatcherAdFailureIntegrationEvent {}
|
||||
export class MatcherAdUpdateFailedIntegrationEvent extends MatcherAdFailureIntegrationEvent {}
|
|
@ -1,9 +1,9 @@
|
|||
import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
|
||||
import { Completer } from './completer.abstract';
|
||||
import { MatchQuery } from '../match.query';
|
||||
import { Step } from '../../../types/step.type';
|
||||
import { CarpoolPathItem } from '@modules/ad/core/domain/value-objects/carpool-path-item.value-object';
|
||||
import { RouteResponse } from '../../../ports/georouter.port';
|
||||
import { RouteResponse } from '../../../../domain/georouter.service';
|
||||
import { Step } from '../../../types/step.type';
|
||||
import { MatchQuery } from '../match.query';
|
||||
import { Completer } from './completer.abstract';
|
||||
|
||||
export class RouteCompleter extends Completer {
|
||||
protected readonly type: RouteCompleterType;
|
||||
|
|
|
@ -8,8 +8,8 @@ import {
|
|||
} from '@modules/ad/core/domain/path-creator.service';
|
||||
import { Point } from '@modules/ad/core/domain/value-objects/point.value-object';
|
||||
import { MatchRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/match.request.dto';
|
||||
import { GeorouterService } from '../../../domain/georouter.service';
|
||||
import { DateTimeTransformerPort } from '../../ports/datetime-transformer.port';
|
||||
import { GeorouterPort } from '../../ports/georouter.port';
|
||||
import { AlgorithmType } from '../../types/algorithm.types';
|
||||
import { Route } from '../../types/route.type';
|
||||
import { Waypoint } from '../../types/waypoint.type';
|
||||
|
@ -41,10 +41,10 @@ export class MatchQuery extends QueryBase {
|
|||
passengerRoute?: Route;
|
||||
backAzimuth?: number;
|
||||
private readonly originWaypoint: Waypoint;
|
||||
routeProvider: GeorouterPort;
|
||||
routeProvider: GeorouterService;
|
||||
|
||||
// TODO: remove MatchRequestDto depency (here core domain depends on interface /!\)
|
||||
constructor(props: MatchRequestDto, routeProvider: GeorouterPort) {
|
||||
constructor(props: MatchRequestDto, routeProvider: GeorouterService) {
|
||||
super();
|
||||
this.id = props.id;
|
||||
this.driver = props.driver;
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
import { AdEntity } from './ad.entity';
|
||||
import { Role, UserAd } from './ad.types';
|
||||
import { GeorouterService } from './georouter.service';
|
||||
import {
|
||||
Path,
|
||||
PathCreator,
|
||||
PathType,
|
||||
TypedRoute,
|
||||
} from './path-creator.service';
|
||||
import { Point } from './value-objects/point.value-object';
|
||||
|
||||
export class AdFactory {
|
||||
constructor(private readonly routeProvider: GeorouterService) {}
|
||||
/**
|
||||
* Create an AdEntity (a "matcher ad", that is: the data needed to match an ad with a match query)
|
||||
* from a "user ad" (the data provided by the user).
|
||||
*/
|
||||
public async create(ad: UserAd): Promise<AdEntity> {
|
||||
const roles: Role[] = [];
|
||||
if (ad.driver) roles.push(Role.DRIVER);
|
||||
if (ad.passenger) roles.push(Role.PASSENGER);
|
||||
|
||||
const pathCreator = new PathCreator(
|
||||
roles,
|
||||
ad.waypoints.map((wp) => new Point({ lon: wp.lon, lat: wp.lat })),
|
||||
);
|
||||
|
||||
let typedRoutes: TypedRoute[];
|
||||
try {
|
||||
typedRoutes = await Promise.all(
|
||||
pathCreator.getBasePaths().map(async (path: Path) => ({
|
||||
type: path.type,
|
||||
route: await this.routeProvider.getRoute({
|
||||
waypoints: path.waypoints,
|
||||
}),
|
||||
})),
|
||||
);
|
||||
} catch (e: any) {
|
||||
throw new Error('Unable to find a route for given waypoints');
|
||||
}
|
||||
|
||||
let driverDistance: number | undefined;
|
||||
let driverDuration: number | undefined;
|
||||
let passengerDistance: number | undefined;
|
||||
let passengerDuration: number | undefined;
|
||||
let points: Point[];
|
||||
let fwdAzimuth: number;
|
||||
let backAzimuth: number;
|
||||
try {
|
||||
typedRoutes.forEach((typedRoute: TypedRoute) => {
|
||||
if ([PathType.DRIVER, PathType.GENERIC].includes(typedRoute.type)) {
|
||||
driverDistance = typedRoute.route.distance;
|
||||
driverDuration = typedRoute.route.duration;
|
||||
points = typedRoute.route.points.map((point) => new Point(point));
|
||||
fwdAzimuth = typedRoute.route.fwdAzimuth;
|
||||
backAzimuth = typedRoute.route.backAzimuth;
|
||||
}
|
||||
|
||||
if ([PathType.PASSENGER, PathType.GENERIC].includes(typedRoute.type)) {
|
||||
passengerDistance = typedRoute.route.distance;
|
||||
passengerDuration = typedRoute.route.duration;
|
||||
if (!points) {
|
||||
points = typedRoute.route.points.map((point) => new Point(point));
|
||||
}
|
||||
if (!fwdAzimuth) fwdAzimuth = typedRoute.route.fwdAzimuth;
|
||||
if (!backAzimuth) backAzimuth = typedRoute.route.backAzimuth;
|
||||
}
|
||||
});
|
||||
} catch (error: any) {
|
||||
throw new Error('Invalid route');
|
||||
}
|
||||
|
||||
return AdEntity.create({
|
||||
id: ad.id,
|
||||
driver: ad.driver,
|
||||
passenger: ad.passenger,
|
||||
frequency: ad.frequency,
|
||||
fromDate: ad.fromDate,
|
||||
toDate: ad.toDate,
|
||||
schedule: ad.schedule,
|
||||
seatsProposed: ad.seatsProposed,
|
||||
seatsRequested: ad.seatsRequested,
|
||||
strict: ad.strict,
|
||||
waypoints: ad.waypoints,
|
||||
points: points!,
|
||||
driverDistance,
|
||||
driverDuration,
|
||||
passengerDistance,
|
||||
passengerDuration,
|
||||
fwdAzimuth: fwdAzimuth!,
|
||||
backAzimuth: backAzimuth!,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,6 +1,23 @@
|
|||
import { PointProps } from './value-objects/point.value-object';
|
||||
import { ScheduleItemProps } from './value-objects/schedule-item.value-object';
|
||||
|
||||
/**
|
||||
* The data provided by the end-user to publish an ad
|
||||
*/
|
||||
export interface UserAd {
|
||||
id: string;
|
||||
driver: boolean;
|
||||
passenger: boolean;
|
||||
frequency: Frequency;
|
||||
fromDate: string;
|
||||
toDate: string;
|
||||
schedule: ScheduleItemProps[];
|
||||
seatsProposed: number;
|
||||
seatsRequested: number;
|
||||
strict: boolean;
|
||||
waypoints: PointProps[];
|
||||
}
|
||||
|
||||
// All properties that an Ad has
|
||||
export interface AdProps {
|
||||
driver: boolean;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
export type Point = {
|
||||
lon: number;
|
||||
lat: number;
|
||||
|
@ -25,7 +23,6 @@ export type RouteResponse = {
|
|||
steps?: Step[];
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export abstract class GeorouterPort {
|
||||
abstract getRoute(request: RouteRequest): Promise<RouteResponse>;
|
||||
export interface GeorouterService {
|
||||
getRoute(request: RouteRequest): Promise<RouteResponse>;
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
import { LoggerBase, MessagePublisherPort } from '@mobicoop/ddd-library';
|
||||
import { ExtendedPrismaRepositoryBase } from '@mobicoop/ddd-library/dist/db/prisma-repository.base';
|
||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { AdRepositoryPort } from '../core/application/ports/ad.repository.port';
|
||||
import { LoggerBase, MessagePublisherPort } from '@mobicoop/ddd-library';
|
||||
import { PrismaService } from './prisma.service';
|
||||
import { AD_MESSAGE_PUBLISHER } from '../ad.di-tokens';
|
||||
import { AdEntity } from '../core/domain/ad.entity';
|
||||
import { AdMapper } from '../ad.mapper';
|
||||
import { ExtendedPrismaRepositoryBase } from '@mobicoop/ddd-library/dist/db/prisma-repository.base';
|
||||
import { Frequency } from '../core/domain/ad.types';
|
||||
import { SERVICE_NAME } from '@src/app.constants';
|
||||
import { AD_MESSAGE_PUBLISHER } from '../ad.di-tokens';
|
||||
import { AdMapper } from '../ad.mapper';
|
||||
import { AdRepositoryPort } from '../core/application/ports/ad.repository.port';
|
||||
import { AdEntity } from '../core/domain/ad.entity';
|
||||
import { Frequency } from '../core/domain/ad.types';
|
||||
import { PrismaService } from './prisma.service';
|
||||
|
||||
export type AdModel = {
|
||||
uuid: string;
|
||||
|
@ -26,8 +26,6 @@ export type AdModel = {
|
|||
passengerDistance?: number;
|
||||
fwdAzimuth: number;
|
||||
backAzimuth: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -36,15 +34,26 @@ export type AdModel = {
|
|||
export type AdReadModel = AdModel & {
|
||||
waypoints: string;
|
||||
schedule: ScheduleItemModel[];
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
/**
|
||||
* The record ready to be sent to the persistence system
|
||||
*/
|
||||
export type AdWriteModel = AdModel & {
|
||||
schedule: {
|
||||
create: ScheduleItemModel[];
|
||||
};
|
||||
schedule: ScheduleWriteModel;
|
||||
};
|
||||
|
||||
export type ScheduleWriteModel = {
|
||||
deleteMany?: PastCreatedFilter;
|
||||
create: ScheduleItemModel[];
|
||||
};
|
||||
|
||||
// used to delete records created in the past,
|
||||
// because the order of `create` and `deleteMany` is not guaranteed
|
||||
export type PastCreatedFilter = {
|
||||
createdAt: { lt: Date };
|
||||
};
|
||||
|
||||
export type AdWriteExtraModel = {
|
||||
|
@ -70,11 +79,15 @@ export type UngroupedAdModel = AdModel &
|
|||
scheduleItemCreatedAt: Date;
|
||||
scheduleItemUpdatedAt: Date;
|
||||
waypoints: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
export type GroupedAdModel = AdModel & {
|
||||
schedule: ScheduleItemModel[];
|
||||
waypoints: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -169,4 +182,12 @@ export class AdRepository
|
|||
});
|
||||
return adReadModels;
|
||||
};
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
entity: AdEntity,
|
||||
identifier?: string,
|
||||
): Promise<void> {
|
||||
this.updateExtra(id, entity, 'ad', identifier);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
import { Observable, lastValueFrom } from 'rxjs';
|
||||
import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
|
||||
import { ClientGrpc } from '@nestjs/microservices';
|
||||
import { GRPC_GEOROUTER_SERVICE_NAME } from '@src/app.constants';
|
||||
import { Observable, lastValueFrom } from 'rxjs';
|
||||
import { GEOGRAPHY_PACKAGE } from '../ad.di-tokens';
|
||||
import {
|
||||
GeorouterPort,
|
||||
GeorouterService,
|
||||
RouteRequest,
|
||||
RouteResponse,
|
||||
} from '../core/application/ports/georouter.port';
|
||||
import { GEOGRAPHY_PACKAGE } from '../ad.di-tokens';
|
||||
} from '../core/domain/georouter.service';
|
||||
|
||||
interface GeorouterService {
|
||||
interface GeorouterPort {
|
||||
getRoute(request: RouteRequest): Observable<RouteResponse>;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class Georouter implements GeorouterPort, OnModuleInit {
|
||||
private georouterService: GeorouterService;
|
||||
export class Georouter implements GeorouterService, OnModuleInit {
|
||||
private georouterService: GeorouterPort;
|
||||
|
||||
constructor(@Inject(GEOGRAPHY_PACKAGE) private readonly client: ClientGrpc) {}
|
||||
|
||||
onModuleInit() {
|
||||
this.georouterService = this.client.getService<GeorouterService>(
|
||||
this.georouterService = this.client.getService<GeorouterPort>(
|
||||
GRPC_GEOROUTER_SERVICE_NAME,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { RpcValidationPipe } from '@mobicoop/ddd-library';
|
||||
import { AD_ROUTE_PROVIDER } from '@modules/ad/ad.di-tokens';
|
||||
import { GeorouterPort } from '@modules/ad/core/application/ports/georouter.port';
|
||||
import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query';
|
||||
import { MatchingResult } from '@modules/ad/core/application/queries/match/match.query-handler';
|
||||
import { GeorouterService } from '@modules/ad/core/domain/georouter.service';
|
||||
import { MatchEntity } from '@modules/ad/core/domain/match.entity';
|
||||
import { MatchMapper } from '@modules/ad/match.mapper';
|
||||
import { Controller, Inject, UseFilters, UsePipes } from '@nestjs/common';
|
||||
|
@ -24,7 +24,7 @@ export class MatchGrpcController {
|
|||
constructor(
|
||||
private readonly queryBus: QueryBus,
|
||||
@Inject(AD_ROUTE_PROVIDER)
|
||||
private readonly routeProvider: GeorouterPort,
|
||||
private readonly routeProvider: GeorouterService,
|
||||
private readonly matchMapper: MatchMapper,
|
||||
) {}
|
||||
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import { RabbitSubscribe } from '@mobicoop/message-broker-module';
|
||||
import { UpdateAdCommand } from '@modules/ad/core/application/commands/update-ad/update-ad.command';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CommandBus } from '@nestjs/cqrs';
|
||||
import {
|
||||
AD_UPDATED_MESSAGE_HANDLER,
|
||||
AD_UPDATED_ROUTING_KEY,
|
||||
} from '@src/app.constants';
|
||||
import { Ad } from './ad.types';
|
||||
|
||||
@Injectable()
|
||||
export class AdUpdatedMessageHandler {
|
||||
constructor(private readonly commandBus: CommandBus) {}
|
||||
|
||||
@RabbitSubscribe({
|
||||
name: AD_UPDATED_MESSAGE_HANDLER,
|
||||
routingKey: AD_UPDATED_ROUTING_KEY,
|
||||
})
|
||||
public async adUpdated(message: string) {
|
||||
try {
|
||||
const updatedAd: { data: Ad } = JSON.parse(message);
|
||||
await this.commandBus.execute(new UpdateAdCommand(updatedAd.data));
|
||||
} catch (error: any) {
|
||||
// do not throw error to acknowledge incoming message
|
||||
// error handling should be done in the command handler, if relevant
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +1,17 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AggregateID, ConflictException } from '@mobicoop/ddd-library';
|
||||
import {
|
||||
AD_MESSAGE_PUBLISHER,
|
||||
AD_REPOSITORY,
|
||||
AD_ROUTE_PROVIDER,
|
||||
} from '@modules/ad/ad.di-tokens';
|
||||
import { AggregateID } from '@mobicoop/ddd-library';
|
||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||
import { ConflictException } from '@mobicoop/ddd-library';
|
||||
import { CreateAdProps, Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
import { CreateAdService } from '@modules/ad/core/application/commands/create-ad/create-ad.service';
|
||||
import { CreateAdCommand } from '@modules/ad/core/application/commands/create-ad/create-ad.command';
|
||||
import { CreateAdService } from '@modules/ad/core/application/commands/create-ad/create-ad.service';
|
||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||
import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors';
|
||||
import { CreateAdProps, Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
import { GeorouterService } from '@modules/ad/core/domain/georouter.service';
|
||||
import { PointProps } from '@modules/ad/core/domain/value-objects/point.value-object';
|
||||
import { GeorouterPort } from '@modules/ad/core/application/ports/georouter.port';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
const originWaypoint: PointProps = {
|
||||
lat: 48.689445,
|
||||
|
@ -62,7 +61,7 @@ const mockAdRepository = {
|
|||
}),
|
||||
};
|
||||
|
||||
const mockRouteProvider: GeorouterPort = {
|
||||
const mockRouteProvider: GeorouterService = {
|
||||
getRoute: jest
|
||||
.fn()
|
||||
.mockImplementationOnce(() => {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { DateTimeTransformerPort } from '@modules/ad/core/application/ports/datetime-transformer.port';
|
||||
import { GeorouterPort } from '@modules/ad/core/application/ports/georouter.port';
|
||||
import {
|
||||
MatchQuery,
|
||||
ScheduleItem,
|
||||
|
@ -7,6 +6,7 @@ import {
|
|||
import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types';
|
||||
import { Waypoint } from '@modules/ad/core/application/types/waypoint.type';
|
||||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
import { GeorouterService } from '@modules/ad/core/domain/georouter.service';
|
||||
import { simpleMockGeorouter } from '../georouter.mock';
|
||||
|
||||
const originWaypoint: Waypoint = {
|
||||
|
@ -61,7 +61,7 @@ const mockInputDateTimeTransformer: DateTimeTransformerPort = {
|
|||
time: jest.fn().mockImplementation(() => '23:05'),
|
||||
};
|
||||
|
||||
const mockRouteProvider: GeorouterPort = {
|
||||
const mockRouteProvider: GeorouterService = {
|
||||
getRoute: jest
|
||||
.fn()
|
||||
.mockImplementationOnce(simpleMockGeorouter.getRoute)
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
import {
|
||||
RouteRequest,
|
||||
RouteResponse,
|
||||
} from '@modules/ad/core/application/ports/georouter.port';
|
||||
import {
|
||||
RouteCompleter,
|
||||
RouteCompleterType,
|
||||
|
@ -12,6 +8,10 @@ import { Waypoint } from '@modules/ad/core/application/types/waypoint.type';
|
|||
import { Frequency, Role } from '@modules/ad/core/domain/ad.types';
|
||||
import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
|
||||
import { Target } from '@modules/ad/core/domain/candidate.types';
|
||||
import {
|
||||
RouteRequest,
|
||||
RouteResponse,
|
||||
} from '@modules/ad/core/domain/georouter.service';
|
||||
import { Actor } from '@modules/ad/core/domain/value-objects/actor.value-object';
|
||||
import { Step } from '@modules/geography/core/domain/route.types';
|
||||
import { simpleMockGeorouter } from '../georouter.mock';
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
import {
|
||||
AD_MESSAGE_PUBLISHER,
|
||||
AD_REPOSITORY,
|
||||
AD_ROUTE_PROVIDER,
|
||||
} from '@modules/ad/ad.di-tokens';
|
||||
import { UpdateAdCommand } from '@modules/ad/core/application/commands/update-ad/update-ad.command';
|
||||
import { UpdateAdService } from '@modules/ad/core/application/commands/update-ad/update-ad.service';
|
||||
import { GeorouterService } from '@modules/ad/core/domain/georouter.service';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { createAdProps } from './ad.fixtures';
|
||||
|
||||
const mockAdRepository = {
|
||||
update: jest.fn().mockImplementation((id) => {
|
||||
if (id === '42') {
|
||||
throw 'Bad id!';
|
||||
}
|
||||
}),
|
||||
};
|
||||
|
||||
const mockRouteProvider: GeorouterService = {
|
||||
getRoute: jest.fn().mockImplementation(() => ({
|
||||
distance: 350101,
|
||||
duration: 14422,
|
||||
fwdAzimuth: 273,
|
||||
backAzimuth: 93,
|
||||
distanceAzimuth: 336544,
|
||||
points: [
|
||||
{
|
||||
lon: 6.1765102,
|
||||
lat: 48.689445,
|
||||
},
|
||||
{
|
||||
lon: 4.984578,
|
||||
lat: 48.725687,
|
||||
},
|
||||
{
|
||||
lon: 2.3522,
|
||||
lat: 48.8566,
|
||||
},
|
||||
],
|
||||
})),
|
||||
};
|
||||
|
||||
const mockMessagePublisher = {
|
||||
publish: jest.fn().mockImplementation(),
|
||||
};
|
||||
|
||||
describe('update-ad.service', () => {
|
||||
let updateAdService: UpdateAdService;
|
||||
|
||||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
{
|
||||
provide: AD_REPOSITORY,
|
||||
useValue: mockAdRepository,
|
||||
},
|
||||
{
|
||||
provide: AD_ROUTE_PROVIDER,
|
||||
useValue: mockRouteProvider,
|
||||
},
|
||||
{
|
||||
provide: AD_MESSAGE_PUBLISHER,
|
||||
useValue: mockMessagePublisher,
|
||||
},
|
||||
UpdateAdService,
|
||||
],
|
||||
}).compile();
|
||||
|
||||
updateAdService = module.get<UpdateAdService>(UpdateAdService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(updateAdService).toBeDefined();
|
||||
});
|
||||
|
||||
describe('execute', () => {
|
||||
it('should call the repository update method', async () => {
|
||||
const updateAdCommand = new UpdateAdCommand(createAdProps());
|
||||
await updateAdService.execute(updateAdCommand);
|
||||
expect(mockAdRepository.update).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should emit an event when an error occurs', async () => {
|
||||
const commandProps = createAdProps();
|
||||
commandProps.id = '42';
|
||||
const updateAdCommand = new UpdateAdCommand(commandProps);
|
||||
await expect(updateAdService.execute(updateAdCommand)).rejects.toBe(
|
||||
'Bad id!',
|
||||
);
|
||||
expect(mockMessagePublisher.publish).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,10 +1,10 @@
|
|||
import { GeorouterPort } from '@modules/ad/core/application/ports/georouter.port';
|
||||
import { GeorouterService } from '@modules/ad/core/domain/georouter.service';
|
||||
|
||||
export const bareMockGeorouter: GeorouterPort = {
|
||||
export const bareMockGeorouter: GeorouterService = {
|
||||
getRoute: jest.fn(),
|
||||
};
|
||||
|
||||
export const simpleMockGeorouter: GeorouterPort = {
|
||||
export const simpleMockGeorouter: GeorouterService = {
|
||||
getRoute: jest.fn().mockImplementation(() => ({
|
||||
distance: 350101,
|
||||
duration: 14422,
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
import { AdUpdatedMessageHandler } from '@modules/ad/interface/message-handlers/ad-updated.message-handler';
|
||||
import { CommandBus } from '@nestjs/cqrs';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
const adUpdatedMessage =
|
||||
'{"data": {"id":"4eb6a6af-ecfd-41c3-9118-473a507014d4","driver":"true","passenger":"true","frequency":"PUNCTUAL","fromDate":"2023-08-18","toDate":"2023-08-18","schedule":[{"day":"5","time":"10:00","margin":"900"}],"seatsProposed":"3","seatsRequested":"1","strict":"false","waypoints":[{"position":"0","houseNumber":"5","street":"rue de la monnaie","locality":"Nancy","postalCode":"54000","country":"France","lon":"48.689445","lat":"6.17651"},{"position":"1","locality":"Paris","postalCode":"75000","country":"France","lon":"48.8566","lat":"2.3522"}]}}';
|
||||
|
||||
const mockCommandBus = {
|
||||
execute: jest.fn(),
|
||||
};
|
||||
|
||||
describe('Ad Updated Message Handler', () => {
|
||||
let adUpdatedMessageHandler: AdUpdatedMessageHandler;
|
||||
|
||||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
{
|
||||
provide: CommandBus,
|
||||
useValue: mockCommandBus,
|
||||
},
|
||||
AdUpdatedMessageHandler,
|
||||
],
|
||||
}).compile();
|
||||
|
||||
adUpdatedMessageHandler = module.get<AdUpdatedMessageHandler>(
|
||||
AdUpdatedMessageHandler,
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(adUpdatedMessageHandler).toBeDefined();
|
||||
});
|
||||
|
||||
it('should update an ad', async () => {
|
||||
await adUpdatedMessageHandler.adUpdated(adUpdatedMessage);
|
||||
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
|
@ -12,6 +12,9 @@ import {
|
|||
AD_DELETED_MESSAGE_HANDLER,
|
||||
AD_DELETED_QUEUE,
|
||||
AD_DELETED_ROUTING_KEY,
|
||||
AD_UPDATED_MESSAGE_HANDLER,
|
||||
AD_UPDATED_QUEUE,
|
||||
AD_UPDATED_ROUTING_KEY,
|
||||
SERVICE_NAME,
|
||||
} from '@src/app.constants';
|
||||
import { MESSAGE_PUBLISHER } from './messager.di-tokens';
|
||||
|
@ -36,6 +39,10 @@ const imports = [
|
|||
routingKey: AD_CREATED_ROUTING_KEY,
|
||||
queue: AD_CREATED_QUEUE,
|
||||
},
|
||||
[AD_UPDATED_MESSAGE_HANDLER]: {
|
||||
routingKey: AD_UPDATED_ROUTING_KEY,
|
||||
queue: AD_UPDATED_QUEUE,
|
||||
},
|
||||
[AD_DELETED_MESSAGE_HANDLER]: {
|
||||
routingKey: AD_DELETED_ROUTING_KEY,
|
||||
queue: AD_DELETED_QUEUE,
|
||||
|
|
Loading…
Reference in New Issue