send messages when matcher ad is created, or when matcher ad creation has failed
This commit is contained in:
		
							parent
							
								
									73f660bf6d
								
							
						
					
					
						commit
						80fac59c43
					
				| 
						 | 
					@ -14,13 +14,9 @@ export const AD_UPDATED_QUEUE = 'matcher-ad-updated';
 | 
				
			||||||
export const AD_DELETED_MESSAGE_HANDLER = 'adDeleted';
 | 
					export const AD_DELETED_MESSAGE_HANDLER = 'adDeleted';
 | 
				
			||||||
export const AD_DELETED_ROUTING_KEY = 'ad.deleted';
 | 
					export const AD_DELETED_ROUTING_KEY = 'ad.deleted';
 | 
				
			||||||
export const AD_DELETED_QUEUE = 'matcher-ad-deleted';
 | 
					export const AD_DELETED_QUEUE = 'matcher-ad-deleted';
 | 
				
			||||||
 | 
					export const MATCHER_AD_CREATED_ROUTING_KEY = 'matcher.ad.created';
 | 
				
			||||||
// configuration
 | 
					export const MATCHER_AD_CREATION_FAILED_ROUTING_KEY =
 | 
				
			||||||
export const SERVICE_CONFIGURATION_SET_QUEUE = 'matcher-configuration-set';
 | 
					  'matcher.ad.creation.failed';
 | 
				
			||||||
export const SERVICE_CONFIGURATION_DELETE_QUEUE =
 | 
					 | 
				
			||||||
  'matcher-configuration-delete';
 | 
					 | 
				
			||||||
export const SERVICE_CONFIGURATION_PROPAGATE_QUEUE =
 | 
					 | 
				
			||||||
  'matcher-configuration-propagate';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// health
 | 
					// health
 | 
				
			||||||
export const GRPC_HEALTH_PACKAGE_NAME = 'health';
 | 
					export const GRPC_HEALTH_PACKAGE_NAME = 'health';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,6 +43,7 @@ import {
 | 
				
			||||||
  RedisModuleOptions,
 | 
					  RedisModuleOptions,
 | 
				
			||||||
} from '@songkeys/nestjs-redis';
 | 
					} from '@songkeys/nestjs-redis';
 | 
				
			||||||
import { ConfigurationRepository } from '@mobicoop/configuration-module';
 | 
					import { ConfigurationRepository } from '@mobicoop/configuration-module';
 | 
				
			||||||
 | 
					import { PublishMessageWhenMatcherAdIsCreatedDomainEventHandler } from './core/application/event-handlers/publish-message-when-matcher-ad-is-created.domain-event-handler';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const imports = [
 | 
					const imports = [
 | 
				
			||||||
  CqrsModule,
 | 
					  CqrsModule,
 | 
				
			||||||
| 
						 | 
					@ -80,6 +81,10 @@ const grpcControllers = [MatchGrpcController];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const messageHandlers = [AdCreatedMessageHandler];
 | 
					const messageHandlers = [AdCreatedMessageHandler];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const eventHandlers: Provider[] = [
 | 
				
			||||||
 | 
					  PublishMessageWhenMatcherAdIsCreatedDomainEventHandler,
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const commandHandlers: Provider[] = [CreateAdService];
 | 
					const commandHandlers: Provider[] = [CreateAdService];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const queryHandlers: Provider[] = [MatchQueryHandler];
 | 
					const queryHandlers: Provider[] = [MatchQueryHandler];
 | 
				
			||||||
| 
						 | 
					@ -150,6 +155,7 @@ const adapters: Provider[] = [
 | 
				
			||||||
  controllers: [...grpcControllers],
 | 
					  controllers: [...grpcControllers],
 | 
				
			||||||
  providers: [
 | 
					  providers: [
 | 
				
			||||||
    ...messageHandlers,
 | 
					    ...messageHandlers,
 | 
				
			||||||
 | 
					    ...eventHandlers,
 | 
				
			||||||
    ...commandHandlers,
 | 
					    ...commandHandlers,
 | 
				
			||||||
    ...queryHandlers,
 | 
					    ...queryHandlers,
 | 
				
			||||||
    ...mappers,
 | 
					    ...mappers,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,18 @@
 | 
				
			||||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
 | 
					import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
 | 
				
			||||||
import { CreateAdCommand } from './create-ad.command';
 | 
					import { CreateAdCommand } from './create-ad.command';
 | 
				
			||||||
import { Inject } from '@nestjs/common';
 | 
					import { Inject } from '@nestjs/common';
 | 
				
			||||||
import { AD_REPOSITORY, AD_ROUTE_PROVIDER } from '@modules/ad/ad.di-tokens';
 | 
					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 { AdEntity } from '@modules/ad/core/domain/ad.entity';
 | 
				
			||||||
import { AdRepositoryPort } from '../../ports/ad.repository.port';
 | 
					import { AdRepositoryPort } from '../../ports/ad.repository.port';
 | 
				
			||||||
import { AggregateID, ConflictException } from '@mobicoop/ddd-library';
 | 
					import {
 | 
				
			||||||
 | 
					  AggregateID,
 | 
				
			||||||
 | 
					  ConflictException,
 | 
				
			||||||
 | 
					  MessagePublisherPort,
 | 
				
			||||||
 | 
					} from '@mobicoop/ddd-library';
 | 
				
			||||||
import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors';
 | 
					import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors';
 | 
				
			||||||
import { RouteProviderPort } from '../../ports/route-provider.port';
 | 
					import { RouteProviderPort } from '../../ports/route-provider.port';
 | 
				
			||||||
import { Role } from '@modules/ad/core/domain/ad.types';
 | 
					import { Role } from '@modules/ad/core/domain/ad.types';
 | 
				
			||||||
| 
						 | 
					@ -17,10 +25,14 @@ import {
 | 
				
			||||||
import { Waypoint } from '../../types/waypoint.type';
 | 
					import { Waypoint } from '../../types/waypoint.type';
 | 
				
			||||||
import { Point as PointValueObject } from '@modules/ad/core/domain/value-objects/point.value-object';
 | 
					import { Point as PointValueObject } from '@modules/ad/core/domain/value-objects/point.value-object';
 | 
				
			||||||
import { Point } from '@modules/geography/core/domain/route.types';
 | 
					import { Point } from '@modules/geography/core/domain/route.types';
 | 
				
			||||||
 | 
					import { MatcherAdCreationFailedIntegrationEvent } from '../../events/matcher-ad-creation-failed.integration-event';
 | 
				
			||||||
 | 
					import { MATCHER_AD_CREATION_FAILED_ROUTING_KEY } from '@src/app.constants';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@CommandHandler(CreateAdCommand)
 | 
					@CommandHandler(CreateAdCommand)
 | 
				
			||||||
export class CreateAdService implements ICommandHandler {
 | 
					export class CreateAdService implements ICommandHandler {
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
 | 
					    @Inject(AD_MESSAGE_PUBLISHER)
 | 
				
			||||||
 | 
					    private readonly messagePublisher: MessagePublisherPort,
 | 
				
			||||||
    @Inject(AD_REPOSITORY)
 | 
					    @Inject(AD_REPOSITORY)
 | 
				
			||||||
    private readonly repository: AdRepositoryPort,
 | 
					    private readonly repository: AdRepositoryPort,
 | 
				
			||||||
    @Inject(AD_ROUTE_PROVIDER)
 | 
					    @Inject(AD_ROUTE_PROVIDER)
 | 
				
			||||||
| 
						 | 
					@ -44,6 +56,15 @@ export class CreateAdService implements ICommandHandler {
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let typedRoutes: TypedRoute[];
 | 
					    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 {
 | 
					      try {
 | 
				
			||||||
        typedRoutes = await Promise.all(
 | 
					        typedRoutes = await Promise.all(
 | 
				
			||||||
          pathCreator.getBasePaths().map(async (path: Path) => ({
 | 
					          pathCreator.getBasePaths().map(async (path: Path) => ({
 | 
				
			||||||
| 
						 | 
					@ -55,13 +76,6 @@ export class CreateAdService implements ICommandHandler {
 | 
				
			||||||
        throw new Error('Unable to find a route for given waypoints');
 | 
					        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: PointValueObject[] | undefined;
 | 
					 | 
				
			||||||
    let fwdAzimuth: number | undefined;
 | 
					 | 
				
			||||||
    let backAzimuth: number | undefined;
 | 
					 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        typedRoutes.forEach((typedRoute: TypedRoute) => {
 | 
					        typedRoutes.forEach((typedRoute: TypedRoute) => {
 | 
				
			||||||
          if ([PathType.DRIVER, PathType.GENERIC].includes(typedRoute.type)) {
 | 
					          if ([PathType.DRIVER, PathType.GENERIC].includes(typedRoute.type)) {
 | 
				
			||||||
| 
						 | 
					@ -77,7 +91,9 @@ export class CreateAdService implements ICommandHandler {
 | 
				
			||||||
            fwdAzimuth = typedRoute.route.fwdAzimuth;
 | 
					            fwdAzimuth = typedRoute.route.fwdAzimuth;
 | 
				
			||||||
            backAzimuth = typedRoute.route.backAzimuth;
 | 
					            backAzimuth = typedRoute.route.backAzimuth;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        if ([PathType.PASSENGER, PathType.GENERIC].includes(typedRoute.type)) {
 | 
					          if (
 | 
				
			||||||
 | 
					            [PathType.PASSENGER, PathType.GENERIC].includes(typedRoute.type)
 | 
				
			||||||
 | 
					          ) {
 | 
				
			||||||
            passengerDistance = typedRoute.route.distance;
 | 
					            passengerDistance = typedRoute.route.distance;
 | 
				
			||||||
            passengerDuration = typedRoute.route.duration;
 | 
					            passengerDuration = typedRoute.route.duration;
 | 
				
			||||||
            if (!points)
 | 
					            if (!points)
 | 
				
			||||||
| 
						 | 
					@ -95,6 +111,7 @@ export class CreateAdService implements ICommandHandler {
 | 
				
			||||||
      } catch (error: any) {
 | 
					      } catch (error: any) {
 | 
				
			||||||
        throw new Error('Invalid route');
 | 
					        throw new Error('Invalid route');
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const ad = AdEntity.create({
 | 
					      const ad = AdEntity.create({
 | 
				
			||||||
        id: command.id,
 | 
					        id: command.id,
 | 
				
			||||||
        driver: command.driver,
 | 
					        driver: command.driver,
 | 
				
			||||||
| 
						 | 
					@ -115,6 +132,7 @@ export class CreateAdService implements ICommandHandler {
 | 
				
			||||||
        fwdAzimuth: fwdAzimuth as number,
 | 
					        fwdAzimuth: fwdAzimuth as number,
 | 
				
			||||||
        backAzimuth: backAzimuth as number,
 | 
					        backAzimuth: backAzimuth as number,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        await this.repository.insertExtra(ad, 'ad');
 | 
					        await this.repository.insertExtra(ad, 'ad');
 | 
				
			||||||
        return ad.id;
 | 
					        return ad.id;
 | 
				
			||||||
| 
						 | 
					@ -124,5 +142,21 @@ export class CreateAdService implements ICommandHandler {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        throw error;
 | 
					        throw error;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (error: any) {
 | 
				
			||||||
 | 
					      const matcherAdCreationFailedIntegrationEvent =
 | 
				
			||||||
 | 
					        new MatcherAdCreationFailedIntegrationEvent({
 | 
				
			||||||
 | 
					          id: command.id,
 | 
				
			||||||
 | 
					          metadata: {
 | 
				
			||||||
 | 
					            correlationId: command.id,
 | 
				
			||||||
 | 
					            timestamp: Date.now(),
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          cause: error.message,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      this.messagePublisher.publish(
 | 
				
			||||||
 | 
					        MATCHER_AD_CREATION_FAILED_ROUTING_KEY,
 | 
				
			||||||
 | 
					        JSON.stringify(matcherAdCreationFailedIntegrationEvent),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      throw error;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,34 @@
 | 
				
			||||||
 | 
					import { Inject, Injectable } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { OnEvent } from '@nestjs/event-emitter';
 | 
				
			||||||
 | 
					import { MatcherAdCreatedDomainEvent } from '../../domain/events/matcher-ad-created.domain-event';
 | 
				
			||||||
 | 
					import { MessagePublisherPort } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					import { AD_MESSAGE_PUBLISHER } from '@modules/ad/ad.di-tokens';
 | 
				
			||||||
 | 
					import { MATCHER_AD_CREATED_ROUTING_KEY } from '@src/app.constants';
 | 
				
			||||||
 | 
					import { MatcherAdCreatedIntegrationEvent } from '../events/matcher-ad-created.integration-event';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class PublishMessageWhenMatcherAdIsCreatedDomainEventHandler {
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    @Inject(AD_MESSAGE_PUBLISHER)
 | 
				
			||||||
 | 
					    private readonly messagePublisher: MessagePublisherPort,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @OnEvent(MatcherAdCreatedDomainEvent.name, { async: true, promisify: true })
 | 
				
			||||||
 | 
					  async handle(event: MatcherAdCreatedDomainEvent): Promise<any> {
 | 
				
			||||||
 | 
					    const matcherAdCreatedIntegrationEvent =
 | 
				
			||||||
 | 
					      new MatcherAdCreatedIntegrationEvent({
 | 
				
			||||||
 | 
					        id: event.aggregateId,
 | 
				
			||||||
 | 
					        driverDuration: event.driverDuration,
 | 
				
			||||||
 | 
					        driverDistance: event.driverDistance,
 | 
				
			||||||
 | 
					        passengerDuration: event.passengerDuration,
 | 
				
			||||||
 | 
					        passengerDistance: event.passengerDistance,
 | 
				
			||||||
 | 
					        fwdAzimuth: event.fwdAzimuth,
 | 
				
			||||||
 | 
					        backAzimuth: event.backAzimuth,
 | 
				
			||||||
 | 
					        metadata: event.metadata,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    this.messagePublisher.publish(
 | 
				
			||||||
 | 
					      MATCHER_AD_CREATED_ROUTING_KEY,
 | 
				
			||||||
 | 
					      JSON.stringify(matcherAdCreatedIntegrationEvent),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,20 @@
 | 
				
			||||||
 | 
					import { IntegrationEvent, IntegrationEventProps } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class MatcherAdCreatedIntegrationEvent extends IntegrationEvent {
 | 
				
			||||||
 | 
					  readonly driverDuration?: number;
 | 
				
			||||||
 | 
					  readonly driverDistance?: number;
 | 
				
			||||||
 | 
					  readonly passengerDuration?: number;
 | 
				
			||||||
 | 
					  readonly passengerDistance?: number;
 | 
				
			||||||
 | 
					  readonly fwdAzimuth: number;
 | 
				
			||||||
 | 
					  readonly backAzimuth: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(props: IntegrationEventProps<MatcherAdCreatedIntegrationEvent>) {
 | 
				
			||||||
 | 
					    super(props);
 | 
				
			||||||
 | 
					    this.driverDuration = props.driverDuration;
 | 
				
			||||||
 | 
					    this.driverDistance = props.driverDistance;
 | 
				
			||||||
 | 
					    this.passengerDuration = props.passengerDuration;
 | 
				
			||||||
 | 
					    this.passengerDistance = props.passengerDistance;
 | 
				
			||||||
 | 
					    this.fwdAzimuth = props.fwdAzimuth;
 | 
				
			||||||
 | 
					    this.backAzimuth = props.backAzimuth;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,12 @@
 | 
				
			||||||
 | 
					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;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -21,7 +21,6 @@ export abstract class Algorithm {
 | 
				
			||||||
    for (const processor of this.processors) {
 | 
					    for (const processor of this.processors) {
 | 
				
			||||||
      this.candidates = await processor.execute(this.candidates);
 | 
					      this.candidates = await processor.execute(this.candidates);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // console.log(JSON.stringify(this.candidates, null, 2));
 | 
					 | 
				
			||||||
    return this.candidates.map((candidate: CandidateEntity) =>
 | 
					    return this.candidates.map((candidate: CandidateEntity) =>
 | 
				
			||||||
      MatchEntity.create({
 | 
					      MatchEntity.create({
 | 
				
			||||||
        adId: candidate.id,
 | 
					        adId: candidate.id,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
import { Frequency, Role } from '@modules/ad/core/domain/ad.types';
 | 
					import { AdStatus, Frequency, Role } from '@modules/ad/core/domain/ad.types';
 | 
				
			||||||
import { Selector } from '../algorithm.abstract';
 | 
					import { Selector } from '../algorithm.abstract';
 | 
				
			||||||
import { Waypoint } from '../../../types/waypoint.type';
 | 
					import { Waypoint } from '../../../types/waypoint.type';
 | 
				
			||||||
import { Point } from '../../../types/point.type';
 | 
					import { Point } from '../../../types/point.type';
 | 
				
			||||||
| 
						 | 
					@ -133,6 +133,7 @@ export class PassengerOrientedSelector extends Selector {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _createWhere = (role: Role): string =>
 | 
					  private _createWhere = (role: Role): string =>
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
 | 
					      this._whereStatus(),
 | 
				
			||||||
      this._whereRole(role),
 | 
					      this._whereRole(role),
 | 
				
			||||||
      this._whereStrict(),
 | 
					      this._whereStrict(),
 | 
				
			||||||
      this._whereDate(),
 | 
					      this._whereDate(),
 | 
				
			||||||
| 
						 | 
					@ -144,6 +145,8 @@ export class PassengerOrientedSelector extends Selector {
 | 
				
			||||||
      .filter((where: string) => where != '')
 | 
					      .filter((where: string) => where != '')
 | 
				
			||||||
      .join(' AND ');
 | 
					      .join(' AND ');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private _whereStatus = (): string => `status='${AdStatus.VALID}'`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _whereRole = (role: Role): string =>
 | 
					  private _whereRole = (role: Role): string =>
 | 
				
			||||||
    role == Role.PASSENGER ? 'driver=True' : 'passenger=True';
 | 
					    role == Role.PASSENGER ? 'driver=True' : 'passenger=True';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,29 @@
 | 
				
			||||||
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
 | 
					import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
 | 
				
			||||||
import { AdProps, CreateAdProps } from './ad.types';
 | 
					import { AdProps, CreateAdProps } from './ad.types';
 | 
				
			||||||
 | 
					import { MatcherAdCreatedDomainEvent } from './events/matcher-ad-created.domain-event';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class AdEntity extends AggregateRoot<AdProps> {
 | 
					export class AdEntity extends AggregateRoot<AdProps> {
 | 
				
			||||||
  protected readonly _id: AggregateID;
 | 
					  protected readonly _id: AggregateID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static create = (create: CreateAdProps): AdEntity => {
 | 
					  static create = (create: CreateAdProps): AdEntity => {
 | 
				
			||||||
    const props: AdProps = { ...create };
 | 
					    const props: AdProps = { ...create };
 | 
				
			||||||
    return new AdEntity({ id: create.id, props });
 | 
					    const ad = new AdEntity({ id: create.id, props });
 | 
				
			||||||
 | 
					    ad.addEvent(
 | 
				
			||||||
 | 
					      new MatcherAdCreatedDomainEvent({
 | 
				
			||||||
 | 
					        metadata: {
 | 
				
			||||||
 | 
					          correlationId: create.id,
 | 
				
			||||||
 | 
					          timestamp: Date.now(),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        aggregateId: create.id,
 | 
				
			||||||
 | 
					        driverDistance: create.driverDistance,
 | 
				
			||||||
 | 
					        driverDuration: create.driverDuration,
 | 
				
			||||||
 | 
					        passengerDistance: create.passengerDistance,
 | 
				
			||||||
 | 
					        passengerDuration: create.passengerDuration,
 | 
				
			||||||
 | 
					        fwdAzimuth: create.fwdAzimuth,
 | 
				
			||||||
 | 
					        backAzimuth: create.backAzimuth,
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    return ad;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  validate(): void {
 | 
					  validate(): void {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -53,3 +53,10 @@ export enum Role {
 | 
				
			||||||
  DRIVER = 'DRIVER',
 | 
					  DRIVER = 'DRIVER',
 | 
				
			||||||
  PASSENGER = 'PASSENGER',
 | 
					  PASSENGER = 'PASSENGER',
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export enum AdStatus {
 | 
				
			||||||
 | 
					  PENDING = 'PENDING',
 | 
				
			||||||
 | 
					  VALID = 'VALID',
 | 
				
			||||||
 | 
					  INVALID = 'INVALID',
 | 
				
			||||||
 | 
					  SUSPENDED = 'SUSPENDED',
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,20 @@
 | 
				
			||||||
 | 
					import { DomainEvent, DomainEventProps } from '@mobicoop/ddd-library';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class MatcherAdCreatedDomainEvent extends DomainEvent {
 | 
				
			||||||
 | 
					  readonly driverDuration?: number;
 | 
				
			||||||
 | 
					  readonly driverDistance?: number;
 | 
				
			||||||
 | 
					  readonly passengerDuration?: number;
 | 
				
			||||||
 | 
					  readonly passengerDistance?: number;
 | 
				
			||||||
 | 
					  readonly fwdAzimuth: number;
 | 
				
			||||||
 | 
					  readonly backAzimuth: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(props: DomainEventProps<MatcherAdCreatedDomainEvent>) {
 | 
				
			||||||
 | 
					    super(props);
 | 
				
			||||||
 | 
					    this.driverDuration = props.driverDuration;
 | 
				
			||||||
 | 
					    this.driverDistance = props.driverDistance;
 | 
				
			||||||
 | 
					    this.passengerDuration = props.passengerDuration;
 | 
				
			||||||
 | 
					    this.passengerDistance = props.passengerDistance;
 | 
				
			||||||
 | 
					    this.fwdAzimuth = props.fwdAzimuth;
 | 
				
			||||||
 | 
					    this.backAzimuth = props.backAzimuth;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,6 @@ export class AdCreatedMessageHandler {
 | 
				
			||||||
    name: AD_CREATED_MESSAGE_HANDLER,
 | 
					    name: AD_CREATED_MESSAGE_HANDLER,
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
  public async adCreated(message: string) {
 | 
					  public async adCreated(message: string) {
 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
    const createdAd: Ad = JSON.parse(message);
 | 
					    const createdAd: Ad = JSON.parse(message);
 | 
				
			||||||
    await this.commandBus.execute(
 | 
					    await this.commandBus.execute(
 | 
				
			||||||
      new CreateAdCommand({
 | 
					      new CreateAdCommand({
 | 
				
			||||||
| 
						 | 
					@ -30,8 +29,5 @@ export class AdCreatedMessageHandler {
 | 
				
			||||||
        waypoints: createdAd.waypoints,
 | 
					        waypoints: createdAd.waypoints,
 | 
				
			||||||
      }),
 | 
					      }),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    } catch (e: any) {
 | 
					 | 
				
			||||||
      console.log(e);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,9 @@
 | 
				
			||||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
import { AD_REPOSITORY, AD_ROUTE_PROVIDER } from '@modules/ad/ad.di-tokens';
 | 
					import {
 | 
				
			||||||
 | 
					  AD_MESSAGE_PUBLISHER,
 | 
				
			||||||
 | 
					  AD_REPOSITORY,
 | 
				
			||||||
 | 
					  AD_ROUTE_PROVIDER,
 | 
				
			||||||
 | 
					} from '@modules/ad/ad.di-tokens';
 | 
				
			||||||
import { AggregateID } from '@mobicoop/ddd-library';
 | 
					import { AggregateID } from '@mobicoop/ddd-library';
 | 
				
			||||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
 | 
					import { AdEntity } from '@modules/ad/core/domain/ad.entity';
 | 
				
			||||||
import { ConflictException } from '@mobicoop/ddd-library';
 | 
					import { ConflictException } from '@mobicoop/ddd-library';
 | 
				
			||||||
| 
						 | 
					@ -96,6 +100,10 @@ const mockRouteProvider: RouteProviderPort = {
 | 
				
			||||||
  getDetailed: jest.fn(),
 | 
					  getDetailed: jest.fn(),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockMessagePublisher = {
 | 
				
			||||||
 | 
					  publish: jest.fn().mockImplementation(),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('create-ad.service', () => {
 | 
					describe('create-ad.service', () => {
 | 
				
			||||||
  let createAdService: CreateAdService;
 | 
					  let createAdService: CreateAdService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -110,6 +118,10 @@ describe('create-ad.service', () => {
 | 
				
			||||||
          provide: AD_ROUTE_PROVIDER,
 | 
					          provide: AD_ROUTE_PROVIDER,
 | 
				
			||||||
          useValue: mockRouteProvider,
 | 
					          useValue: mockRouteProvider,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          provide: AD_MESSAGE_PUBLISHER,
 | 
				
			||||||
 | 
					          useValue: mockMessagePublisher,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        CreateAdService,
 | 
					        CreateAdService,
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
    }).compile();
 | 
					    }).compile();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,57 @@
 | 
				
			||||||
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
 | 
					import { AD_MESSAGE_PUBLISHER } from '@modules/ad/ad.di-tokens';
 | 
				
			||||||
 | 
					import { MATCHER_AD_CREATED_ROUTING_KEY } from '@src/app.constants';
 | 
				
			||||||
 | 
					import { PublishMessageWhenMatcherAdIsCreatedDomainEventHandler } from '@modules/ad/core/application/event-handlers/publish-message-when-matcher-ad-is-created.domain-event-handler';
 | 
				
			||||||
 | 
					import { MatcherAdCreatedDomainEvent } from '@modules/ad/core/domain/events/matcher-ad-created.domain-event';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockMessagePublisher = {
 | 
				
			||||||
 | 
					  publish: jest.fn().mockImplementation(),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Publish message when matcher ad is created domain event handler', () => {
 | 
				
			||||||
 | 
					  let publishMessageWhenMatcherAdIsCreatedDomainEventHandler: PublishMessageWhenMatcherAdIsCreatedDomainEventHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(async () => {
 | 
				
			||||||
 | 
					    const module: TestingModule = await Test.createTestingModule({
 | 
				
			||||||
 | 
					      providers: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          provide: AD_MESSAGE_PUBLISHER,
 | 
				
			||||||
 | 
					          useValue: mockMessagePublisher,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        PublishMessageWhenMatcherAdIsCreatedDomainEventHandler,
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    }).compile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    publishMessageWhenMatcherAdIsCreatedDomainEventHandler =
 | 
				
			||||||
 | 
					      module.get<PublishMessageWhenMatcherAdIsCreatedDomainEventHandler>(
 | 
				
			||||||
 | 
					        PublishMessageWhenMatcherAdIsCreatedDomainEventHandler,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should publish a message', () => {
 | 
				
			||||||
 | 
					    jest.spyOn(mockMessagePublisher, 'publish');
 | 
				
			||||||
 | 
					    const matcherAdCreatedDomainEvent: MatcherAdCreatedDomainEvent = {
 | 
				
			||||||
 | 
					      id: 'some-domain-event-id',
 | 
				
			||||||
 | 
					      aggregateId: 'some-aggregate-id',
 | 
				
			||||||
 | 
					      metadata: {
 | 
				
			||||||
 | 
					        timestamp: new Date('2023-06-28T05:00:00Z').getTime(),
 | 
				
			||||||
 | 
					        correlationId: 'some-correlation-id',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      driverDistance: 65845,
 | 
				
			||||||
 | 
					      driverDuration: 3254,
 | 
				
			||||||
 | 
					      fwdAzimuth: 90,
 | 
				
			||||||
 | 
					      backAzimuth: 270,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    publishMessageWhenMatcherAdIsCreatedDomainEventHandler.handle(
 | 
				
			||||||
 | 
					      matcherAdCreatedDomainEvent,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    expect(
 | 
				
			||||||
 | 
					      publishMessageWhenMatcherAdIsCreatedDomainEventHandler,
 | 
				
			||||||
 | 
					    ).toBeDefined();
 | 
				
			||||||
 | 
					    expect(mockMessagePublisher.publish).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					    expect(mockMessagePublisher.publish).toHaveBeenCalledWith(
 | 
				
			||||||
 | 
					      MATCHER_AD_CREATED_ROUTING_KEY,
 | 
				
			||||||
 | 
					      '{"id":"some-aggregate-id","metadata":{"correlationId":"some-correlation-id","timestamp":1687928400000},"driverDuration":3254,"driverDistance":65845,"fwdAzimuth":90,"backAzimuth":270}',
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
		Loading…
	
		Reference in New Issue