refactor to ddh, first commit
This commit is contained in:
		
							parent
							
								
									0a6e4c0bf6
								
							
						
					
					
						commit
						ce48890a66
					
				
							
								
								
									
										30
									
								
								.env.dist
								
								
								
								
							
							
						
						
									
										30
									
								
								.env.dist
								
								
								
								
							| 
						 | 
					@ -4,6 +4,21 @@ SERVICE_PORT=5005
 | 
				
			||||||
SERVICE_CONFIGURATION_DOMAIN=MATCHER
 | 
					SERVICE_CONFIGURATION_DOMAIN=MATCHER
 | 
				
			||||||
HEALTH_SERVICE_PORT=6005
 | 
					HEALTH_SERVICE_PORT=6005
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# PRISMA
 | 
				
			||||||
 | 
					DATABASE_URL="postgresql://mobicoop:mobicoop@v3-db:5432/mobicoop?schema=matcher"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# MESSAGE BROKER
 | 
				
			||||||
 | 
					MESSAGE_BROKER_URI=amqp://v3-broker:5672
 | 
				
			||||||
 | 
					MESSAGE_BROKER_EXCHANGE=mobicoop
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# REDIS
 | 
				
			||||||
 | 
					REDIS_HOST=v3-redis
 | 
				
			||||||
 | 
					REDIS_PASSWORD=redis
 | 
				
			||||||
 | 
					REDIS_PORT=6379
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# CACHE
 | 
				
			||||||
 | 
					CACHE_TTL=5000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# DEFAULT CONFIGURATION
 | 
					# DEFAULT CONFIGURATION
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# default identifier used for match requests
 | 
					# default identifier used for match requests
 | 
				
			||||||
| 
						 | 
					@ -41,18 +56,3 @@ MAX_DETOUR_DURATION_RATIO=0.3
 | 
				
			||||||
GEOROUTER_TYPE=graphhopper
 | 
					GEOROUTER_TYPE=graphhopper
 | 
				
			||||||
# georouter url
 | 
					# georouter url
 | 
				
			||||||
GEOROUTER_URL=http://localhost:8989
 | 
					GEOROUTER_URL=http://localhost:8989
 | 
				
			||||||
 | 
					 | 
				
			||||||
# PRISMA
 | 
					 | 
				
			||||||
DATABASE_URL="postgresql://mobicoop:mobicoop@v3-db:5432/mobicoop?schema=matcher"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# RABBIT MQ
 | 
					 | 
				
			||||||
RMQ_URI=amqp://v3-broker:5672
 | 
					 | 
				
			||||||
RMQ_EXCHANGE=mobicoop
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# REDIS
 | 
					 | 
				
			||||||
REDIS_HOST=v3-redis
 | 
					 | 
				
			||||||
REDIS_PASSWORD=redis
 | 
					 | 
				
			||||||
REDIS_PORT=6379
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# CACHE
 | 
					 | 
				
			||||||
CACHE_TTL=5000
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,8 +44,9 @@ GEOROUTER_URL=http://localhost:8989
 | 
				
			||||||
# PRISMA
 | 
					# PRISMA
 | 
				
			||||||
DATABASE_URL="postgresql://mobicoop:mobicoop@v3-db:5432/mobicoop?schema=public"
 | 
					DATABASE_URL="postgresql://mobicoop:mobicoop@v3-db:5432/mobicoop?schema=public"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# RABBIT MQ
 | 
					# MESSAGE BROKER
 | 
				
			||||||
RMQ_URI=amqp://v3-broker:5672
 | 
					MESSAGE_BROKER_URI=amqp://v3-broker:5672
 | 
				
			||||||
 | 
					MESSAGE_BROKER_EXCHANGE=mobicoop
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# REDIS
 | 
					# REDIS
 | 
				
			||||||
REDIS_IMAGE=redis:7.0-alpine
 | 
					REDIS_IMAGE=redis:7.0-alpine
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					export const MESSAGE_BROKER_PUBLISHER = Symbol('MESSAGE_BROKER_PUBLISHER');
 | 
				
			||||||
 | 
					export const MESSAGE_PUBLISHER = Symbol('MESSAGE_PUBLISHER');
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,66 @@
 | 
				
			||||||
 | 
					import { classes } from '@automapper/classes';
 | 
				
			||||||
 | 
					import { AutomapperModule } from '@automapper/nestjs';
 | 
				
			||||||
 | 
					import { Module } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { ConfigModule, ConfigService } from '@nestjs/config';
 | 
				
			||||||
 | 
					import { HealthModule } from './modules/health/health.module';
 | 
				
			||||||
 | 
					import { MatcherModule } from './modules/matcher/matcher.module';
 | 
				
			||||||
 | 
					import { AdModule } from './modules/ad/ad.module';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  ConfigurationModule,
 | 
				
			||||||
 | 
					  ConfigurationModuleOptions,
 | 
				
			||||||
 | 
					} from '@mobicoop/configuration-module';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  MessageBrokerModule,
 | 
				
			||||||
 | 
					  MessageBrokerModuleOptions,
 | 
				
			||||||
 | 
					} from '@mobicoop/message-broker-module';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Module({
 | 
				
			||||||
 | 
					  imports: [
 | 
				
			||||||
 | 
					    ConfigModule.forRoot({ isGlobal: true }),
 | 
				
			||||||
 | 
					    ConfigurationModule.forRootAsync({
 | 
				
			||||||
 | 
					      imports: [ConfigModule],
 | 
				
			||||||
 | 
					      inject: [ConfigService],
 | 
				
			||||||
 | 
					      useFactory: async (
 | 
				
			||||||
 | 
					        configService: ConfigService,
 | 
				
			||||||
 | 
					      ): Promise<ConfigurationModuleOptions> => ({
 | 
				
			||||||
 | 
					        domain: configService.get<string>('SERVICE_CONFIGURATION_DOMAIN'),
 | 
				
			||||||
 | 
					        messageBroker: {
 | 
				
			||||||
 | 
					          uri: configService.get<string>('MESSAGE_BROKER_URI'),
 | 
				
			||||||
 | 
					          exchange: configService.get<string>('MESSAGE_BROKER_EXCHANGE'),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        redis: {
 | 
				
			||||||
 | 
					          host: configService.get<string>('REDIS_HOST'),
 | 
				
			||||||
 | 
					          password: configService.get<string>('REDIS_PASSWORD'),
 | 
				
			||||||
 | 
					          port: configService.get<number>('REDIS_PORT'),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        setConfigurationBrokerQueue: 'matcher-configuration-create-update',
 | 
				
			||||||
 | 
					        deleteConfigurationQueue: 'matcher-configuration-delete',
 | 
				
			||||||
 | 
					        propagateConfigurationQueue: 'matcher-configuration-propagate',
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    MessageBrokerModule.forRootAsync({
 | 
				
			||||||
 | 
					      imports: [ConfigModule],
 | 
				
			||||||
 | 
					      inject: [ConfigService],
 | 
				
			||||||
 | 
					      useFactory: async (
 | 
				
			||||||
 | 
					        configService: ConfigService,
 | 
				
			||||||
 | 
					      ): Promise<MessageBrokerModuleOptions> => ({
 | 
				
			||||||
 | 
					        uri: configService.get<string>('MESSAGE_BROKER_URI'),
 | 
				
			||||||
 | 
					        exchange: configService.get<string>('MESSAGE_BROKER_EXCHANGE'),
 | 
				
			||||||
 | 
					        handlers: {
 | 
				
			||||||
 | 
					          adCreated: {
 | 
				
			||||||
 | 
					            routingKey: 'ad.created',
 | 
				
			||||||
 | 
					            queue: 'matcher-ad-created',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        name: 'matcher',
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    AutomapperModule.forRoot({ strategyInitializer: classes() }),
 | 
				
			||||||
 | 
					    HealthModule,
 | 
				
			||||||
 | 
					    MatcherModule,
 | 
				
			||||||
 | 
					    AdModule,
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  controllers: [],
 | 
				
			||||||
 | 
					  providers: [],
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AppModule {}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					export interface IPublishMessage {
 | 
				
			||||||
 | 
					  publish(routingKey: string, message: string): void;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,4 @@
 | 
				
			||||||
 | 
					export const PARAMS_PROVIDER = Symbol();
 | 
				
			||||||
 | 
					export const GEOROUTER_CREATOR = Symbol();
 | 
				
			||||||
 | 
					export const TIMEZONE_FINDER = Symbol();
 | 
				
			||||||
 | 
					export const DIRECTION_ENCODER = Symbol();
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,61 @@
 | 
				
			||||||
 | 
					import { Module } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { AdMessagerService } from './adapters/primaries/ad-messager.service';
 | 
				
			||||||
 | 
					import { AdProfile } from './mappers/ad.profile';
 | 
				
			||||||
 | 
					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 { GeoTimezoneFinder } from '../geography/adapters/secondaries/geo-timezone-finder';
 | 
				
			||||||
 | 
					import { DefaultParamsProvider } from './adapters/secondaries/default-params.provider';
 | 
				
			||||||
 | 
					import { GeorouterCreator } from '../geography/adapters/secondaries/georouter-creator';
 | 
				
			||||||
 | 
					import { GeographyModule } from '../geography/geography.module';
 | 
				
			||||||
 | 
					import { HttpModule } from '@nestjs/axios';
 | 
				
			||||||
 | 
					import { PostgresDirectionEncoder } from '../geography/adapters/secondaries/postgres-direction-encoder';
 | 
				
			||||||
 | 
					import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  MESSAGE_BROKER_PUBLISHER,
 | 
				
			||||||
 | 
					  MESSAGE_PUBLISHER,
 | 
				
			||||||
 | 
					} from '../../app.constants';
 | 
				
			||||||
 | 
					import { MessagePublisher } from './adapters/secondaries/message-publisher';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  DIRECTION_ENCODER,
 | 
				
			||||||
 | 
					  GEOROUTER_CREATOR,
 | 
				
			||||||
 | 
					  PARAMS_PROVIDER,
 | 
				
			||||||
 | 
					  TIMEZONE_FINDER,
 | 
				
			||||||
 | 
					} from './ad.constants';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Module({
 | 
				
			||||||
 | 
					  imports: [GeographyModule, DatabaseModule, CqrsModule, HttpModule],
 | 
				
			||||||
 | 
					  providers: [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      provide: PARAMS_PROVIDER,
 | 
				
			||||||
 | 
					      useClass: DefaultParamsProvider,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      provide: GEOROUTER_CREATOR,
 | 
				
			||||||
 | 
					      useClass: GeorouterCreator,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      provide: TIMEZONE_FINDER,
 | 
				
			||||||
 | 
					      useClass: GeoTimezoneFinder,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      provide: DIRECTION_ENCODER,
 | 
				
			||||||
 | 
					      useClass: PostgresDirectionEncoder,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      provide: MESSAGE_BROKER_PUBLISHER,
 | 
				
			||||||
 | 
					      useClass: MessageBrokerPublisher,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      provide: MESSAGE_PUBLISHER,
 | 
				
			||||||
 | 
					      useClass: MessagePublisher,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    AdProfile,
 | 
				
			||||||
 | 
					    AdRepository,
 | 
				
			||||||
 | 
					    CreateAdUseCase,
 | 
				
			||||||
 | 
					    AdMessagerService,
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  exports: [],
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AdModule {}
 | 
				
			||||||
| 
						 | 
					@ -1,20 +1,21 @@
 | 
				
			||||||
import { RabbitSubscribe } from '@golevelup/nestjs-rabbitmq';
 | 
					import { Controller, Inject } from '@nestjs/common';
 | 
				
			||||||
import { Controller } from '@nestjs/common';
 | 
					import { CommandBus } from '@nestjs/cqrs';
 | 
				
			||||||
import { CommandBus, QueryBus } 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 { validateOrReject } from 'class-validator';
 | 
					import { validateOrReject } from 'class-validator';
 | 
				
			||||||
import { Messager } from '../secondaries/messager';
 | 
					 | 
				
			||||||
import { plainToInstance } from 'class-transformer';
 | 
					import { plainToInstance } from 'class-transformer';
 | 
				
			||||||
import { DatabaseException } from 'src/modules/database/exceptions/database.exception';
 | 
					import { DatabaseException } from 'src/modules/database/exceptions/database.exception';
 | 
				
			||||||
import { ExceptionCode } from 'src/modules/utils/exception-code.enum';
 | 
					import { ExceptionCode } from 'src/modules/utils/exception-code.enum';
 | 
				
			||||||
 | 
					import { IPublishMessage } from 'src/interfaces/message-publisher';
 | 
				
			||||||
 | 
					import { MESSAGE_PUBLISHER } from 'src/app.constants';
 | 
				
			||||||
 | 
					import { RabbitSubscribe } from '@mobicoop/message-broker-module';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Controller()
 | 
					@Controller()
 | 
				
			||||||
export class AdMessagerController {
 | 
					export class AdMessagerService {
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    private readonly messager: Messager,
 | 
					    @Inject(MESSAGE_PUBLISHER)
 | 
				
			||||||
 | 
					    private readonly messagePublisher: IPublishMessage,
 | 
				
			||||||
    private readonly commandBus: CommandBus,
 | 
					    private readonly commandBus: CommandBus,
 | 
				
			||||||
    private readonly queryBus: QueryBus,
 | 
					 | 
				
			||||||
  ) {}
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @RabbitSubscribe({
 | 
					  @RabbitSubscribe({
 | 
				
			||||||
| 
						 | 
					@ -28,7 +29,7 @@ export class AdMessagerController {
 | 
				
			||||||
      // validate instance
 | 
					      // validate instance
 | 
				
			||||||
      await validateOrReject(createAdRequest);
 | 
					      await validateOrReject(createAdRequest);
 | 
				
			||||||
      // validate nested objects (fixes direct nested validation bug)
 | 
					      // validate nested objects (fixes direct nested validation bug)
 | 
				
			||||||
      for (const waypoint of createAdRequest.waypoints) {
 | 
					      for (const waypoint of createAdRequest.addresses) {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
          await validateOrReject(waypoint);
 | 
					          await validateOrReject(waypoint);
 | 
				
			||||||
        } catch (e) {
 | 
					        } catch (e) {
 | 
				
			||||||
| 
						 | 
					@ -36,7 +37,7 @@ export class AdMessagerController {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      this.messager.publish(
 | 
					      this.messagePublisher.publish(
 | 
				
			||||||
        'matcher.ad.crit',
 | 
					        'matcher.ad.crit',
 | 
				
			||||||
        JSON.stringify({
 | 
					        JSON.stringify({
 | 
				
			||||||
          message: `Can't validate message : ${message}`,
 | 
					          message: `Can't validate message : ${message}`,
 | 
				
			||||||
| 
						 | 
					@ -49,7 +50,7 @@ export class AdMessagerController {
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      if (e instanceof DatabaseException) {
 | 
					      if (e instanceof DatabaseException) {
 | 
				
			||||||
        if (e.message.includes('already exists')) {
 | 
					        if (e.message.includes('already exists')) {
 | 
				
			||||||
          this.messager.publish(
 | 
					          this.messagePublisher.publish(
 | 
				
			||||||
            'matcher.ad.crit',
 | 
					            'matcher.ad.crit',
 | 
				
			||||||
            JSON.stringify({
 | 
					            JSON.stringify({
 | 
				
			||||||
              code: ExceptionCode.ALREADY_EXISTS,
 | 
					              code: ExceptionCode.ALREADY_EXISTS,
 | 
				
			||||||
| 
						 | 
					@ -59,7 +60,7 @@ export class AdMessagerController {
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (e.message.includes("Can't reach database server")) {
 | 
					        if (e.message.includes("Can't reach database server")) {
 | 
				
			||||||
          this.messager.publish(
 | 
					          this.messagePublisher.publish(
 | 
				
			||||||
            'matcher.ad.crit',
 | 
					            'matcher.ad.crit',
 | 
				
			||||||
            JSON.stringify({
 | 
					            JSON.stringify({
 | 
				
			||||||
              code: ExceptionCode.UNAVAILABLE,
 | 
					              code: ExceptionCode.UNAVAILABLE,
 | 
				
			||||||
| 
						 | 
					@ -69,7 +70,7 @@ export class AdMessagerController {
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      this.messager.publish(
 | 
					      this.messagePublisher.publish(
 | 
				
			||||||
        'logging.matcher.ad.crit',
 | 
					        'logging.matcher.ad.crit',
 | 
				
			||||||
        JSON.stringify({
 | 
					        JSON.stringify({
 | 
				
			||||||
          message,
 | 
					          message,
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					import { Inject, Injectable } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { MESSAGE_BROKER_PUBLISHER } from '../../../../app.constants';
 | 
				
			||||||
 | 
					import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
 | 
				
			||||||
 | 
					import { IPublishMessage } from '../../../../interfaces/message-publisher';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class MessagePublisher implements IPublishMessage {
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    @Inject(MESSAGE_BROKER_PUBLISHER)
 | 
				
			||||||
 | 
					    private readonly messageBrokerPublisher: MessageBrokerPublisher,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  publish = (routingKey: string, message: string): void => {
 | 
				
			||||||
 | 
					    this.messageBrokerPublisher.publish(routingKey, message);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -119,7 +119,7 @@ export class CreateAdRequest {
 | 
				
			||||||
  @IsArray()
 | 
					  @IsArray()
 | 
				
			||||||
  @ArrayMinSize(2)
 | 
					  @ArrayMinSize(2)
 | 
				
			||||||
  @AutoMap(() => [Coordinate])
 | 
					  @AutoMap(() => [Coordinate])
 | 
				
			||||||
  waypoints: Coordinate[];
 | 
					  addresses: Coordinate[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @IsNumber()
 | 
					  @IsNumber()
 | 
				
			||||||
  @AutoMap()
 | 
					  @AutoMap()
 | 
				
			||||||
| 
						 | 
					@ -21,22 +21,22 @@ export class Geography {
 | 
				
			||||||
  ): Promise<void> => {
 | 
					  ): Promise<void> => {
 | 
				
			||||||
    const paths: Path[] = this.getPaths(roles);
 | 
					    const paths: Path[] = this.getPaths(roles);
 | 
				
			||||||
    const routes = await georouter.route(paths, settings);
 | 
					    const routes = await georouter.route(paths, settings);
 | 
				
			||||||
    if (routes.some((route) => route.key == RouteKey.COMMON)) {
 | 
					    if (routes.some((route) => route.key == RouteType.COMMON)) {
 | 
				
			||||||
      this.driverRoute = routes.find(
 | 
					      this.driverRoute = routes.find(
 | 
				
			||||||
        (route) => route.key == RouteKey.COMMON,
 | 
					        (route) => route.key == RouteType.COMMON,
 | 
				
			||||||
      ).route;
 | 
					      ).route;
 | 
				
			||||||
      this.passengerRoute = routes.find(
 | 
					      this.passengerRoute = routes.find(
 | 
				
			||||||
        (route) => route.key == RouteKey.COMMON,
 | 
					        (route) => route.key == RouteType.COMMON,
 | 
				
			||||||
      ).route;
 | 
					      ).route;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      if (routes.some((route) => route.key == RouteKey.DRIVER)) {
 | 
					      if (routes.some((route) => route.key == RouteType.DRIVER)) {
 | 
				
			||||||
        this.driverRoute = routes.find(
 | 
					        this.driverRoute = routes.find(
 | 
				
			||||||
          (route) => route.key == RouteKey.DRIVER,
 | 
					          (route) => route.key == RouteType.DRIVER,
 | 
				
			||||||
        ).route;
 | 
					        ).route;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (routes.some((route) => route.key == RouteKey.PASSENGER)) {
 | 
					      if (routes.some((route) => route.key == RouteType.PASSENGER)) {
 | 
				
			||||||
        this.passengerRoute = routes.find(
 | 
					        this.passengerRoute = routes.find(
 | 
				
			||||||
          (route) => route.key == RouteKey.PASSENGER,
 | 
					          (route) => route.key == RouteType.PASSENGER,
 | 
				
			||||||
        ).route;
 | 
					        ).route;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -48,7 +48,7 @@ export class Geography {
 | 
				
			||||||
      if (this.coordinates.length == 2) {
 | 
					      if (this.coordinates.length == 2) {
 | 
				
			||||||
        // 2 points => same route for driver and passenger
 | 
					        // 2 points => same route for driver and passenger
 | 
				
			||||||
        const commonPath: Path = {
 | 
					        const commonPath: Path = {
 | 
				
			||||||
          key: RouteKey.COMMON,
 | 
					          key: RouteType.COMMON,
 | 
				
			||||||
          points: this.coordinates,
 | 
					          points: this.coordinates,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        paths.push(commonPath);
 | 
					        paths.push(commonPath);
 | 
				
			||||||
| 
						 | 
					@ -69,14 +69,14 @@ export class Geography {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private createDriverPath = (): Path => {
 | 
					  private createDriverPath = (): Path => {
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      key: RouteKey.DRIVER,
 | 
					      key: RouteType.DRIVER,
 | 
				
			||||||
      points: this.coordinates,
 | 
					      points: this.coordinates,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private createPassengerPath = (): Path => {
 | 
					  private createPassengerPath = (): Path => {
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      key: RouteKey.PASSENGER,
 | 
					      key: RouteType.PASSENGER,
 | 
				
			||||||
      points: [
 | 
					      points: [
 | 
				
			||||||
        this.coordinates[0],
 | 
					        this.coordinates[0],
 | 
				
			||||||
        this.coordinates[this.coordinates.length - 1],
 | 
					        this.coordinates[this.coordinates.length - 1],
 | 
				
			||||||
| 
						 | 
					@ -85,7 +85,7 @@ export class Geography {
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export enum RouteKey {
 | 
					export enum RouteType {
 | 
				
			||||||
  COMMON = 'common',
 | 
					  COMMON = 'common',
 | 
				
			||||||
  DRIVER = 'driver',
 | 
					  DRIVER = 'driver',
 | 
				
			||||||
  PASSENGER = 'passenger',
 | 
					  PASSENGER = 'passenger',
 | 
				
			||||||
| 
						 | 
					@ -16,6 +16,12 @@ import { Geography } from '../entities/geography';
 | 
				
			||||||
import { IEncodeDirection } from '../../../geography/domain/interfaces/direction-encoder.interface';
 | 
					import { IEncodeDirection } from '../../../geography/domain/interfaces/direction-encoder.interface';
 | 
				
			||||||
import { TimeConverter } from '../entities/time-converter';
 | 
					import { TimeConverter } from '../entities/time-converter';
 | 
				
			||||||
import { Coordinate } from '../../../geography/domain/entities/coordinate';
 | 
					import { Coordinate } from '../../../geography/domain/entities/coordinate';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  DIRECTION_ENCODER,
 | 
				
			||||||
 | 
					  GEOROUTER_CREATOR,
 | 
				
			||||||
 | 
					  PARAMS_PROVIDER,
 | 
				
			||||||
 | 
					  TIMEZONE_FINDER,
 | 
				
			||||||
 | 
					} from '../../ad.constants';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@CommandHandler(CreateAdCommand)
 | 
					@CommandHandler(CreateAdCommand)
 | 
				
			||||||
export class CreateAdUseCase {
 | 
					export class CreateAdUseCase {
 | 
				
			||||||
| 
						 | 
					@ -29,13 +35,13 @@ export class CreateAdUseCase {
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    @InjectMapper() private readonly mapper: Mapper,
 | 
					    @InjectMapper() private readonly mapper: Mapper,
 | 
				
			||||||
    private readonly adRepository: AdRepository,
 | 
					    private readonly adRepository: AdRepository,
 | 
				
			||||||
    @Inject('ParamsProvider')
 | 
					    @Inject(PARAMS_PROVIDER)
 | 
				
			||||||
    private readonly defaultParamsProvider: IProvideParams,
 | 
					    private readonly defaultParamsProvider: IProvideParams,
 | 
				
			||||||
    @Inject('GeorouterCreator')
 | 
					    @Inject(GEOROUTER_CREATOR)
 | 
				
			||||||
    private readonly georouterCreator: ICreateGeorouter,
 | 
					    private readonly georouterCreator: ICreateGeorouter,
 | 
				
			||||||
    @Inject('TimezoneFinder')
 | 
					    @Inject(TIMEZONE_FINDER)
 | 
				
			||||||
    private readonly timezoneFinder: IFindTimezone,
 | 
					    private readonly timezoneFinder: IFindTimezone,
 | 
				
			||||||
    @Inject('DirectionEncoder')
 | 
					    @Inject(DIRECTION_ENCODER)
 | 
				
			||||||
    private readonly directionEncoder: IEncodeDirection,
 | 
					    private readonly directionEncoder: IEncodeDirection,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    this.defaultParams = defaultParamsProvider.getParams();
 | 
					    this.defaultParams = defaultParamsProvider.getParams();
 | 
				
			||||||
| 
						 | 
					@ -48,8 +54,8 @@ export class CreateAdUseCase {
 | 
				
			||||||
  async execute(command: CreateAdCommand): Promise<Ad> {
 | 
					  async execute(command: CreateAdCommand): Promise<Ad> {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      this.ad = this.mapper.map(command.createAdRequest, CreateAdRequest, Ad);
 | 
					      this.ad = this.mapper.map(command.createAdRequest, CreateAdRequest, Ad);
 | 
				
			||||||
      this.setTimezone(command.createAdRequest.waypoints);
 | 
					      this.setTimezone(command.createAdRequest.addresses);
 | 
				
			||||||
      this.setGeography(command.createAdRequest.waypoints);
 | 
					      this.setGeography(command.createAdRequest.addresses);
 | 
				
			||||||
      this.setRoles(command.createAdRequest);
 | 
					      this.setRoles(command.createAdRequest);
 | 
				
			||||||
      await this.geography.createRoutes(this.roles, this.georouter, {
 | 
					      await this.geography.createRoutes(this.roles, this.georouter, {
 | 
				
			||||||
        withDistance: false,
 | 
					        withDistance: false,
 | 
				
			||||||
| 
						 | 
					@ -97,7 +103,7 @@ export class CreateAdUseCase {
 | 
				
			||||||
      ? this.geography.driverRoute.backAzimuth
 | 
					      ? this.geography.driverRoute.backAzimuth
 | 
				
			||||||
      : this.geography.passengerRoute.backAzimuth;
 | 
					      : this.geography.passengerRoute.backAzimuth;
 | 
				
			||||||
    this.ad.waypoints = this.directionEncoder.encode(
 | 
					    this.ad.waypoints = this.directionEncoder.encode(
 | 
				
			||||||
      command.createAdRequest.waypoints,
 | 
					      command.createAdRequest.addresses,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    this.ad.direction = this.geography.driverRoute
 | 
					    this.ad.direction = this.geography.driverRoute
 | 
				
			||||||
      ? this.directionEncoder.encode(this.geography.driverRoute.points)
 | 
					      ? this.directionEncoder.encode(this.geography.driverRoute.points)
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,36 @@
 | 
				
			||||||
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
 | 
					import { MessagePublisher } from '../../../../adapters/secondaries/message-publisher';
 | 
				
			||||||
 | 
					import { MESSAGE_BROKER_PUBLISHER } from '../../../../../../app.constants';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockMessageBrokerPublisher = {
 | 
				
			||||||
 | 
					  publish: jest.fn().mockImplementation(),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Messager', () => {
 | 
				
			||||||
 | 
					  let messagePublisher: MessagePublisher;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(async () => {
 | 
				
			||||||
 | 
					    const module: TestingModule = await Test.createTestingModule({
 | 
				
			||||||
 | 
					      imports: [],
 | 
				
			||||||
 | 
					      providers: [
 | 
				
			||||||
 | 
					        MessagePublisher,
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          provide: MESSAGE_BROKER_PUBLISHER,
 | 
				
			||||||
 | 
					          useValue: mockMessageBrokerPublisher,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    }).compile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    messagePublisher = module.get<MessagePublisher>(MessagePublisher);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should be defined', () => {
 | 
				
			||||||
 | 
					    expect(messagePublisher).toBeDefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should publish a message', async () => {
 | 
				
			||||||
 | 
					    jest.spyOn(mockMessageBrokerPublisher, 'publish');
 | 
				
			||||||
 | 
					    messagePublisher.publish('ad.info', 'my-test');
 | 
				
			||||||
 | 
					    expect(mockMessageBrokerPublisher.publish).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -8,9 +8,15 @@ import { CreateAdCommand } from '../../../commands/create-ad.command';
 | 
				
			||||||
import { Ad } from '../../../domain/entities/ad';
 | 
					import { Ad } from '../../../domain/entities/ad';
 | 
				
			||||||
import { AdProfile } from '../../../mappers/ad.profile';
 | 
					import { AdProfile } from '../../../mappers/ad.profile';
 | 
				
			||||||
import { Frequency } from '../../../domain/types/frequency.enum';
 | 
					import { Frequency } from '../../../domain/types/frequency.enum';
 | 
				
			||||||
import { RouteKey } from '../../../domain/entities/geography';
 | 
					import { RouteType } from '../../../domain/entities/geography';
 | 
				
			||||||
import { DatabaseException } from '../../../../database/exceptions/database.exception';
 | 
					import { DatabaseException } from '../../../../database/exceptions/database.exception';
 | 
				
			||||||
import { Route } from '../../../../geography/domain/entities/route';
 | 
					import { Route } from '../../../../geography/domain/entities/route';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  DIRECTION_ENCODER,
 | 
				
			||||||
 | 
					  GEOROUTER_CREATOR,
 | 
				
			||||||
 | 
					  PARAMS_PROVIDER,
 | 
				
			||||||
 | 
					  TIMEZONE_FINDER,
 | 
				
			||||||
 | 
					} from '../../../ad.constants';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const mockAdRepository = {
 | 
					const mockAdRepository = {
 | 
				
			||||||
  createAd: jest.fn().mockImplementation((ad) => {
 | 
					  createAd: jest.fn().mockImplementation((ad) => {
 | 
				
			||||||
| 
						 | 
					@ -23,7 +29,7 @@ const mockGeorouterCreator = {
 | 
				
			||||||
  create: jest.fn().mockImplementation(() => ({
 | 
					  create: jest.fn().mockImplementation(() => ({
 | 
				
			||||||
    route: jest.fn().mockImplementation(() => [
 | 
					    route: jest.fn().mockImplementation(() => [
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        key: RouteKey.DRIVER,
 | 
					        key: RouteType.DRIVER,
 | 
				
			||||||
        route: <Route>{
 | 
					        route: <Route>{
 | 
				
			||||||
          points: [],
 | 
					          points: [],
 | 
				
			||||||
          fwdAzimuth: 0,
 | 
					          fwdAzimuth: 0,
 | 
				
			||||||
| 
						 | 
					@ -33,7 +39,7 @@ const mockGeorouterCreator = {
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        key: RouteKey.PASSENGER,
 | 
					        key: RouteType.PASSENGER,
 | 
				
			||||||
        route: <Route>{
 | 
					        route: <Route>{
 | 
				
			||||||
          points: [],
 | 
					          points: [],
 | 
				
			||||||
          fwdAzimuth: 0,
 | 
					          fwdAzimuth: 0,
 | 
				
			||||||
| 
						 | 
					@ -43,7 +49,7 @@ const mockGeorouterCreator = {
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        key: RouteKey.COMMON,
 | 
					        key: RouteType.COMMON,
 | 
				
			||||||
        route: <Route>{
 | 
					        route: <Route>{
 | 
				
			||||||
          points: [],
 | 
					          points: [],
 | 
				
			||||||
          fwdAzimuth: 0,
 | 
					          fwdAzimuth: 0,
 | 
				
			||||||
| 
						 | 
					@ -94,7 +100,7 @@ const createAdRequest: CreateAdRequest = {
 | 
				
			||||||
  seatsDriver: 3,
 | 
					  seatsDriver: 3,
 | 
				
			||||||
  seatsPassenger: 1,
 | 
					  seatsPassenger: 1,
 | 
				
			||||||
  strict: false,
 | 
					  strict: false,
 | 
				
			||||||
  waypoints: [
 | 
					  addresses: [
 | 
				
			||||||
    { lon: 6, lat: 45 },
 | 
					    { lon: 6, lat: 45 },
 | 
				
			||||||
    { lon: 6.5, lat: 45.5 },
 | 
					    { lon: 6.5, lat: 45.5 },
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
| 
						 | 
					@ -124,19 +130,19 @@ describe('CreateAdUseCase', () => {
 | 
				
			||||||
          useValue: mockAdRepository,
 | 
					          useValue: mockAdRepository,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          provide: 'GeorouterCreator',
 | 
					          provide: GEOROUTER_CREATOR,
 | 
				
			||||||
          useValue: mockGeorouterCreator,
 | 
					          useValue: mockGeorouterCreator,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          provide: 'ParamsProvider',
 | 
					          provide: PARAMS_PROVIDER,
 | 
				
			||||||
          useValue: mockParamsProvider,
 | 
					          useValue: mockParamsProvider,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          provide: 'TimezoneFinder',
 | 
					          provide: TIMEZONE_FINDER,
 | 
				
			||||||
          useValue: mockTimezoneFinder,
 | 
					          useValue: mockTimezoneFinder,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          provide: 'DirectionEncoder',
 | 
					          provide: DIRECTION_ENCODER,
 | 
				
			||||||
          useValue: mockDirectionEncoder,
 | 
					          useValue: mockDirectionEncoder,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        AdProfile,
 | 
					        AdProfile,
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,7 @@ import { GraphhopperGeorouter } from './graphhopper-georouter';
 | 
				
			||||||
import { HttpService } from '@nestjs/axios';
 | 
					import { HttpService } from '@nestjs/axios';
 | 
				
			||||||
import { Geodesic } from './geodesic';
 | 
					import { Geodesic } from './geodesic';
 | 
				
			||||||
import { GeographyException } from '../../exceptions/geography.exception';
 | 
					import { GeographyException } from '../../exceptions/geography.exception';
 | 
				
			||||||
import { ExceptionCode } from '../../..//utils/exception-code.enum';
 | 
					import { ExceptionCode } from '../../../utils/exception-code.enum';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class GeorouterCreator implements ICreateGeorouter {
 | 
					export class GeorouterCreator implements ICreateGeorouter {
 | 
				
			||||||
| 
						 | 
					@ -3,12 +3,12 @@ import { IGeorouter } from '../../domain/interfaces/georouter.interface';
 | 
				
			||||||
import { Injectable } from '@nestjs/common';
 | 
					import { Injectable } from '@nestjs/common';
 | 
				
			||||||
import { catchError, lastValueFrom, map } from 'rxjs';
 | 
					import { catchError, lastValueFrom, map } from 'rxjs';
 | 
				
			||||||
import { AxiosError, AxiosResponse } from 'axios';
 | 
					import { AxiosError, AxiosResponse } from 'axios';
 | 
				
			||||||
import { IGeodesic } from '../../../geography/domain/interfaces/geodesic.interface';
 | 
					import { IGeodesic } from '../../domain/interfaces/geodesic.interface';
 | 
				
			||||||
import { GeorouterSettings } from '../../domain/types/georouter-settings.type';
 | 
					import { GeorouterSettings } from '../../domain/types/georouter-settings.type';
 | 
				
			||||||
import { Path } from '../../domain/types/path.type';
 | 
					import { Path } from '../../domain/types/path.type';
 | 
				
			||||||
import { NamedRoute } from '../../domain/types/named-route';
 | 
					import { NamedRoute } from '../../domain/types/named-route';
 | 
				
			||||||
import { GeographyException } from '../../exceptions/geography.exception';
 | 
					import { GeographyException } from '../../exceptions/geography.exception';
 | 
				
			||||||
import { ExceptionCode } from '../../..//utils/exception-code.enum';
 | 
					import { ExceptionCode } from '../../../utils/exception-code.enum';
 | 
				
			||||||
import { Route } from '../../domain/entities/route';
 | 
					import { Route } from '../../domain/entities/route';
 | 
				
			||||||
import { SpacetimePoint } from '../../domain/entities/spacetime-point';
 | 
					import { SpacetimePoint } from '../../domain/entities/spacetime-point';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					import { Point } from './point.type';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Path = {
 | 
				
			||||||
 | 
					  key: string;
 | 
				
			||||||
 | 
					  points: Point[];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
import { Controller } from '@nestjs/common';
 | 
					import { Controller } from '@nestjs/common';
 | 
				
			||||||
import { GrpcMethod } from '@nestjs/microservices';
 | 
					import { GrpcMethod } from '@nestjs/microservices';
 | 
				
			||||||
import { PrismaHealthIndicatorUseCase } from '../../domain/usecases/prisma.health-indicator.usecase';
 | 
					import { RepositoriesHealthIndicatorUseCase } from '../../domain/usecases/repositories.health-indicator.usecase';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum ServingStatus {
 | 
					enum ServingStatus {
 | 
				
			||||||
  UNKNOWN = 0,
 | 
					  UNKNOWN = 0,
 | 
				
			||||||
| 
						 | 
					@ -19,7 +19,7 @@ interface HealthCheckResponse {
 | 
				
			||||||
@Controller()
 | 
					@Controller()
 | 
				
			||||||
export class HealthServerController {
 | 
					export class HealthServerController {
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    private readonly prismaHealthIndicatorUseCase: PrismaHealthIndicatorUseCase,
 | 
					    private readonly repositoriesHealthIndicatorUseCase: RepositoriesHealthIndicatorUseCase,
 | 
				
			||||||
  ) {}
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @GrpcMethod('Health', 'Check')
 | 
					  @GrpcMethod('Health', 'Check')
 | 
				
			||||||
| 
						 | 
					@ -29,12 +29,12 @@ export class HealthServerController {
 | 
				
			||||||
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
					    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
				
			||||||
    metadata: any,
 | 
					    metadata: any,
 | 
				
			||||||
  ): Promise<HealthCheckResponse> {
 | 
					  ): Promise<HealthCheckResponse> {
 | 
				
			||||||
    const healthCheck = await this.prismaHealthIndicatorUseCase.isHealthy(
 | 
					    const healthCheck = await this.repositoriesHealthIndicatorUseCase.isHealthy(
 | 
				
			||||||
      'prisma',
 | 
					      'repositories',
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      status:
 | 
					      status:
 | 
				
			||||||
        healthCheck['prisma'].status == 'up'
 | 
					        healthCheck['repositories'].status == 'up'
 | 
				
			||||||
          ? ServingStatus.SERVING
 | 
					          ? ServingStatus.SERVING
 | 
				
			||||||
          : ServingStatus.NOT_SERVING,
 | 
					          : ServingStatus.NOT_SERVING,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,37 @@
 | 
				
			||||||
 | 
					import { Controller, Get, Inject } from '@nestjs/common';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  HealthCheckService,
 | 
				
			||||||
 | 
					  HealthCheck,
 | 
				
			||||||
 | 
					  HealthCheckResult,
 | 
				
			||||||
 | 
					} from '@nestjs/terminus';
 | 
				
			||||||
 | 
					import { MESSAGE_PUBLISHER } from 'src/app.constants';
 | 
				
			||||||
 | 
					import { IPublishMessage } from 'src/interfaces/message-publisher';
 | 
				
			||||||
 | 
					import { RepositoriesHealthIndicatorUseCase } from '../../domain/usecases/repositories.health-indicator.usecase';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Controller('health')
 | 
				
			||||||
 | 
					export class HealthController {
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    private readonly repositoriesHealthIndicatorUseCase: RepositoriesHealthIndicatorUseCase,
 | 
				
			||||||
 | 
					    private healthCheckService: HealthCheckService,
 | 
				
			||||||
 | 
					    @Inject(MESSAGE_PUBLISHER)
 | 
				
			||||||
 | 
					    private readonly messagePublisher: IPublishMessage,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Get()
 | 
				
			||||||
 | 
					  @HealthCheck()
 | 
				
			||||||
 | 
					  async check() {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      return await this.healthCheckService.check([
 | 
				
			||||||
 | 
					        async () =>
 | 
				
			||||||
 | 
					          this.repositoriesHealthIndicatorUseCase.isHealthy('repositories'),
 | 
				
			||||||
 | 
					      ]);
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      const healthCheckResult: HealthCheckResult = error.response;
 | 
				
			||||||
 | 
					      this.messagePublisher.publish(
 | 
				
			||||||
 | 
					        'logging.user.health.crit',
 | 
				
			||||||
 | 
					        JSON.stringify(healthCheckResult.error),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      throw error;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					import { Inject, Injectable } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { MESSAGE_BROKER_PUBLISHER } from '../../../../app.constants';
 | 
				
			||||||
 | 
					import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
 | 
				
			||||||
 | 
					import { IPublishMessage } from 'src/interfaces/message-publisher';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class MessagePublisher implements IPublishMessage {
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    @Inject(MESSAGE_BROKER_PUBLISHER)
 | 
				
			||||||
 | 
					    private readonly messageBrokerPublisher: MessageBrokerPublisher,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  publish = (routingKey: string, message: string): void => {
 | 
				
			||||||
 | 
					    this.messageBrokerPublisher.publish(routingKey, message);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					export interface ICheckRepository {
 | 
				
			||||||
 | 
					  healthCheck(): Promise<boolean>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,33 @@
 | 
				
			||||||
 | 
					import { Injectable } from '@nestjs/common';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  HealthCheckError,
 | 
				
			||||||
 | 
					  HealthIndicator,
 | 
				
			||||||
 | 
					  HealthIndicatorResult,
 | 
				
			||||||
 | 
					} from '@nestjs/terminus';
 | 
				
			||||||
 | 
					import { ICheckRepository } from '../interfaces/check-repository.interface';
 | 
				
			||||||
 | 
					import { AdRepository } from '../../../ad/adapters/secondaries/ad.repository';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class RepositoriesHealthIndicatorUseCase extends HealthIndicator {
 | 
				
			||||||
 | 
					  private checkRepositories: ICheckRepository[];
 | 
				
			||||||
 | 
					  constructor(private readonly adRepository: AdRepository) {
 | 
				
			||||||
 | 
					    super();
 | 
				
			||||||
 | 
					    this.checkRepositories = [adRepository];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  isHealthy = async (key: string): Promise<HealthIndicatorResult> => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await Promise.all(
 | 
				
			||||||
 | 
					        this.checkRepositories.map(
 | 
				
			||||||
 | 
					          async (checkRepository: ICheckRepository) => {
 | 
				
			||||||
 | 
					            await checkRepository.healthCheck();
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      return this.getStatus(key, true);
 | 
				
			||||||
 | 
					    } catch (e: any) {
 | 
				
			||||||
 | 
					      throw new HealthCheckError('Repository', {
 | 
				
			||||||
 | 
					        repository: e.message,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,28 @@
 | 
				
			||||||
 | 
					import { Module } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { HealthServerController } from './adapters/primaries/health-server.controller';
 | 
				
			||||||
 | 
					import { DatabaseModule } from '../database/database.module';
 | 
				
			||||||
 | 
					import { HealthController } from './adapters/primaries/health.controller';
 | 
				
			||||||
 | 
					import { TerminusModule } from '@nestjs/terminus';
 | 
				
			||||||
 | 
					import { MESSAGE_BROKER_PUBLISHER, MESSAGE_PUBLISHER } from 'src/app.constants';
 | 
				
			||||||
 | 
					import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
 | 
				
			||||||
 | 
					import { MessagePublisher } from './adapters/secondaries/message-publisher';
 | 
				
			||||||
 | 
					import { RepositoriesHealthIndicatorUseCase } from './domain/usecases/repositories.health-indicator.usecase';
 | 
				
			||||||
 | 
					import { AdRepository } from '../ad/adapters/secondaries/ad.repository';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Module({
 | 
				
			||||||
 | 
					  imports: [TerminusModule, DatabaseModule],
 | 
				
			||||||
 | 
					  controllers: [HealthServerController, HealthController],
 | 
				
			||||||
 | 
					  providers: [
 | 
				
			||||||
 | 
					    RepositoriesHealthIndicatorUseCase,
 | 
				
			||||||
 | 
					    AdRepository,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      provide: MESSAGE_BROKER_PUBLISHER,
 | 
				
			||||||
 | 
					      useClass: MessageBrokerPublisher,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      provide: MESSAGE_PUBLISHER,
 | 
				
			||||||
 | 
					      useClass: MessagePublisher,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class HealthModule {}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,36 @@
 | 
				
			||||||
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
 | 
					import { MessagePublisher } from '../../adapters/secondaries/message-publisher';
 | 
				
			||||||
 | 
					import { MESSAGE_BROKER_PUBLISHER } from '../../../../app.constants';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockMessageBrokerPublisher = {
 | 
				
			||||||
 | 
					  publish: jest.fn().mockImplementation(),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Messager', () => {
 | 
				
			||||||
 | 
					  let messagePublisher: MessagePublisher;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeAll(async () => {
 | 
				
			||||||
 | 
					    const module: TestingModule = await Test.createTestingModule({
 | 
				
			||||||
 | 
					      imports: [],
 | 
				
			||||||
 | 
					      providers: [
 | 
				
			||||||
 | 
					        MessagePublisher,
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          provide: MESSAGE_BROKER_PUBLISHER,
 | 
				
			||||||
 | 
					          useValue: mockMessageBrokerPublisher,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    }).compile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    messagePublisher = module.get<MessagePublisher>(MessagePublisher);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should be defined', () => {
 | 
				
			||||||
 | 
					    expect(messagePublisher).toBeDefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should publish a message', async () => {
 | 
				
			||||||
 | 
					    jest.spyOn(mockMessageBrokerPublisher, 'publish');
 | 
				
			||||||
 | 
					    messagePublisher.publish('health.info', 'my-test');
 | 
				
			||||||
 | 
					    expect(mockMessageBrokerPublisher.publish).toHaveBeenCalledTimes(1);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,7 @@
 | 
				
			||||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
import { PrismaHealthIndicatorUseCase } from '../../domain/usecases/prisma.health-indicator.usecase';
 | 
					 | 
				
			||||||
import { HealthCheckError, HealthIndicatorResult } from '@nestjs/terminus';
 | 
					import { HealthCheckError, HealthIndicatorResult } from '@nestjs/terminus';
 | 
				
			||||||
 | 
					import { RepositoriesHealthIndicatorUseCase } from '../../domain/usecases/repositories.health-indicator.usecase';
 | 
				
			||||||
import { AdRepository } from '../../../ad/adapters/secondaries/ad.repository';
 | 
					import { AdRepository } from '../../../ad/adapters/secondaries/ad.repository';
 | 
				
			||||||
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const mockAdRepository = {
 | 
					const mockAdRepository = {
 | 
				
			||||||
  healthCheck: jest
 | 
					  healthCheck: jest
 | 
				
			||||||
| 
						 | 
					@ -11,47 +10,45 @@ const mockAdRepository = {
 | 
				
			||||||
      return Promise.resolve(true);
 | 
					      return Promise.resolve(true);
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    .mockImplementation(() => {
 | 
					    .mockImplementation(() => {
 | 
				
			||||||
      throw new PrismaClientKnownRequestError('Service unavailable', {
 | 
					      throw new Error('an error occured in the repository');
 | 
				
			||||||
        code: 'code',
 | 
					 | 
				
			||||||
        clientVersion: 'version',
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('PrismaHealthIndicatorUseCase', () => {
 | 
					describe('RepositoriesHealthIndicatorUseCase', () => {
 | 
				
			||||||
  let prismaHealthIndicatorUseCase: PrismaHealthIndicatorUseCase;
 | 
					  let repositoriesHealthIndicatorUseCase: RepositoriesHealthIndicatorUseCase;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  beforeAll(async () => {
 | 
					  beforeAll(async () => {
 | 
				
			||||||
    const module: TestingModule = await Test.createTestingModule({
 | 
					    const module: TestingModule = await Test.createTestingModule({
 | 
				
			||||||
      providers: [
 | 
					      providers: [
 | 
				
			||||||
 | 
					        RepositoriesHealthIndicatorUseCase,
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          provide: AdRepository,
 | 
					          provide: AdRepository,
 | 
				
			||||||
          useValue: mockAdRepository,
 | 
					          useValue: mockAdRepository,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        PrismaHealthIndicatorUseCase,
 | 
					 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
    }).compile();
 | 
					    }).compile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    prismaHealthIndicatorUseCase = module.get<PrismaHealthIndicatorUseCase>(
 | 
					    repositoriesHealthIndicatorUseCase =
 | 
				
			||||||
      PrismaHealthIndicatorUseCase,
 | 
					      module.get<RepositoriesHealthIndicatorUseCase>(
 | 
				
			||||||
    );
 | 
					        RepositoriesHealthIndicatorUseCase,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should be defined', () => {
 | 
					  it('should be defined', () => {
 | 
				
			||||||
    expect(prismaHealthIndicatorUseCase).toBeDefined();
 | 
					    expect(repositoriesHealthIndicatorUseCase).toBeDefined();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe('execute', () => {
 | 
					  describe('execute', () => {
 | 
				
			||||||
    it('should check health successfully', async () => {
 | 
					    it('should check health successfully', async () => {
 | 
				
			||||||
      const healthIndicatorResult: HealthIndicatorResult =
 | 
					      const healthIndicatorResult: HealthIndicatorResult =
 | 
				
			||||||
        await prismaHealthIndicatorUseCase.isHealthy('prisma');
 | 
					        await repositoriesHealthIndicatorUseCase.isHealthy('repositories');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(healthIndicatorResult['prisma'].status).toBe('up');
 | 
					      expect(healthIndicatorResult['repositories'].status).toBe('up');
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('should throw an error if database is unavailable', async () => {
 | 
					    it('should throw an error if database is unavailable', async () => {
 | 
				
			||||||
      await expect(
 | 
					      await expect(
 | 
				
			||||||
        prismaHealthIndicatorUseCase.isHealthy('prisma'),
 | 
					        repositoriesHealthIndicatorUseCase.isHealthy('repositories'),
 | 
				
			||||||
      ).rejects.toBeInstanceOf(HealthCheckError);
 | 
					      ).rejects.toBeInstanceOf(HealthCheckError);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					import { Inject, Injectable } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { MESSAGE_BROKER_PUBLISHER } from '../../../../app.constants';
 | 
				
			||||||
 | 
					import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
 | 
				
			||||||
 | 
					import { IPublishMessage } from '../../../../interfaces/message-publisher';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class MessagePublisher implements IPublishMessage {
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    @Inject(MESSAGE_BROKER_PUBLISHER)
 | 
				
			||||||
 | 
					    private readonly messageBrokerPublisher: MessageBrokerPublisher,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  publish = (routingKey: string, message: string): void => {
 | 
				
			||||||
 | 
					    this.messageBrokerPublisher.publish(routingKey, message);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue