create module
This commit is contained in:
		
							parent
							
								
									d7b1c54b45
								
							
						
					
					
						commit
						afca685e3d
					
				| 
						 | 
				
			
			@ -16,6 +16,7 @@
 | 
			
		|||
        "@grpc/grpc-js": "^1.8.13",
 | 
			
		||||
        "@grpc/proto-loader": "^0.7.6",
 | 
			
		||||
        "@liaoliaots/nestjs-redis": "^9.0.5",
 | 
			
		||||
        "@nestjs/cache-manager": "^1.0.0",
 | 
			
		||||
        "@nestjs/common": "^9.0.0",
 | 
			
		||||
        "@nestjs/config": "^2.3.1",
 | 
			
		||||
        "@nestjs/core": "^9.0.0",
 | 
			
		||||
| 
						 | 
				
			
			@ -1569,6 +1570,17 @@
 | 
			
		|||
        "node": ">=8"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@nestjs/cache-manager": {
 | 
			
		||||
      "version": "1.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@nestjs/cache-manager/-/cache-manager-1.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-XMNdgsj3H+Ng/SYwFl13vRGNFA3e5Obk8LNwIuHLVSocnK2exReAWtscxEjQhoBc4FW4jAYOgU/U+mt18Q9T0g==",
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "@nestjs/common": "^9.0.0",
 | 
			
		||||
        "cache-manager": "<=5",
 | 
			
		||||
        "reflect-metadata": "^0.1.12",
 | 
			
		||||
        "rxjs": "^7.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@nestjs/cli": {
 | 
			
		||||
      "version": "9.3.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-9.3.0.tgz",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -38,6 +38,7 @@
 | 
			
		|||
    "@grpc/grpc-js": "^1.8.13",
 | 
			
		||||
    "@grpc/proto-loader": "^0.7.6",
 | 
			
		||||
    "@liaoliaots/nestjs-redis": "^9.0.5",
 | 
			
		||||
    "@nestjs/cache-manager": "^1.0.0",
 | 
			
		||||
    "@nestjs/common": "^9.0.0",
 | 
			
		||||
    "@nestjs/config": "^2.3.1",
 | 
			
		||||
    "@nestjs/core": "^9.0.0",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,65 @@
 | 
			
		|||
-- CreateExtension
 | 
			
		||||
CREATE EXTENSION IF NOT EXISTS "postgis";
 | 
			
		||||
 | 
			
		||||
-- Required to use postgis extension :
 | 
			
		||||
-- set the search_path to both public and territory (where is postgis) AND the current schema
 | 
			
		||||
SET search_path TO matcher, territory, public;
 | 
			
		||||
 | 
			
		||||
-- CreateTable
 | 
			
		||||
CREATE TABLE "ad" (
 | 
			
		||||
    "uuid" UUID NOT NULL,
 | 
			
		||||
    "driver" BOOLEAN NOT NULL,
 | 
			
		||||
    "passenger" BOOLEAN NOT NULL,
 | 
			
		||||
    "frequency" INTEGER NOT NULL,
 | 
			
		||||
    "from_date" DATE NOT NULL,
 | 
			
		||||
    "to_date" DATE NOT NULL,
 | 
			
		||||
    "mon_time" TIMESTAMPTZ NOT NULL,
 | 
			
		||||
    "tue_time" TIMESTAMPTZ NOT NULL,
 | 
			
		||||
    "wed_time" TIMESTAMPTZ NOT NULL,
 | 
			
		||||
    "thu_time" TIMESTAMPTZ NOT NULL,
 | 
			
		||||
    "fri_time" TIMESTAMPTZ NOT NULL,
 | 
			
		||||
    "sat_time" TIMESTAMPTZ NOT NULL,
 | 
			
		||||
    "sun_time" TIMESTAMPTZ NOT NULL,
 | 
			
		||||
    "mon_margin" INTEGER NOT NULL,
 | 
			
		||||
    "tue_margin" INTEGER NOT NULL,
 | 
			
		||||
    "wed_margin" INTEGER NOT NULL,
 | 
			
		||||
    "thu_margin" INTEGER NOT NULL,
 | 
			
		||||
    "fri_margin" INTEGER NOT NULL,
 | 
			
		||||
    "sat_margin" INTEGER NOT NULL,
 | 
			
		||||
    "sun_margin" INTEGER NOT NULL,
 | 
			
		||||
    "driver_duration" INTEGER NOT NULL,
 | 
			
		||||
    "driver_distance" INTEGER NOT NULL,
 | 
			
		||||
    "passenger_duration" INTEGER NOT NULL,
 | 
			
		||||
    "passenger_distance" INTEGER NOT NULL,
 | 
			
		||||
    "origin_type" SMALLINT NOT NULL,
 | 
			
		||||
    "destination_type" SMALLINT NOT NULL,
 | 
			
		||||
    "waypoints" geography(LINESTRING) NOT NULL,
 | 
			
		||||
    "direction" geography(LINESTRING) NOT NULL,
 | 
			
		||||
    "fwd_azimuth" INTEGER NOT NULL,
 | 
			
		||||
    "back_azimuth" INTEGER NOT NULL,
 | 
			
		||||
    "seats_driver" SMALLINT NOT NULL,
 | 
			
		||||
    "seats_passenger" SMALLINT NOT NULL,
 | 
			
		||||
    "seats_used" SMALLINT NOT NULL,
 | 
			
		||||
    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
 | 
			
		||||
    "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
 | 
			
		||||
 | 
			
		||||
    CONSTRAINT "ad_pkey" PRIMARY KEY ("uuid")
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
-- CreateIndex
 | 
			
		||||
CREATE INDEX "ad_driver_idx" ON "ad"("driver");
 | 
			
		||||
 | 
			
		||||
-- CreateIndex
 | 
			
		||||
CREATE INDEX "ad_passenger_idx" ON "ad"("passenger");
 | 
			
		||||
 | 
			
		||||
-- CreateIndex
 | 
			
		||||
CREATE INDEX "ad_from_date_idx" ON "ad"("from_date");
 | 
			
		||||
 | 
			
		||||
-- CreateIndex
 | 
			
		||||
CREATE INDEX "ad_to_date_idx" ON "ad"("to_date");
 | 
			
		||||
 | 
			
		||||
-- CreateIndex
 | 
			
		||||
CREATE INDEX "ad_fwd_azimuth_idx" ON "ad"("fwd_azimuth");
 | 
			
		||||
 | 
			
		||||
-- CreateIndex
 | 
			
		||||
CREATE INDEX "direction_idx" ON "ad" USING GIST ("direction");
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
# Please do not edit this file manually
 | 
			
		||||
# It should be added in your version-control system (i.e. Git)
 | 
			
		||||
provider = "postgresql"
 | 
			
		||||
| 
						 | 
				
			
			@ -3,12 +3,14 @@ import { AutomapperModule } from '@automapper/nestjs';
 | 
			
		|||
import { Module } from '@nestjs/common';
 | 
			
		||||
import { ConfigModule } from '@nestjs/config';
 | 
			
		||||
import { ConfigurationModule } from './modules/configuration/configuration.module';
 | 
			
		||||
import { HealthModule } from './modules/health/health.module';
 | 
			
		||||
 | 
			
		||||
@Module({
 | 
			
		||||
  imports: [
 | 
			
		||||
    ConfigModule.forRoot({ isGlobal: true }),
 | 
			
		||||
    AutomapperModule.forRoot({ strategyInitializer: classes() }),
 | 
			
		||||
    ConfigurationModule,
 | 
			
		||||
    HealthModule,
 | 
			
		||||
  ],
 | 
			
		||||
  controllers: [],
 | 
			
		||||
  providers: [],
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										28
									
								
								src/main.ts
								
								
								
								
							
							
						
						
									
										28
									
								
								src/main.ts
								
								
								
								
							| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
import { NestFactory } from '@nestjs/core';
 | 
			
		||||
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
 | 
			
		||||
import { join } from 'path';
 | 
			
		||||
// import { join } from 'path';
 | 
			
		||||
import { AppModule } from './app.module';
 | 
			
		||||
 | 
			
		||||
async function bootstrap() {
 | 
			
		||||
| 
						 | 
				
			
			@ -8,19 +8,19 @@ async function bootstrap() {
 | 
			
		|||
  app.connectMicroservice<MicroserviceOptions>({
 | 
			
		||||
    transport: Transport.TCP,
 | 
			
		||||
  });
 | 
			
		||||
  app.connectMicroservice<MicroserviceOptions>({
 | 
			
		||||
    transport: Transport.GRPC,
 | 
			
		||||
    options: {
 | 
			
		||||
      // package: ['matcher', 'health'],
 | 
			
		||||
      package: ['health'],
 | 
			
		||||
      protoPath: [
 | 
			
		||||
        // join(__dirname, 'modules/matcher/adapters/primaries/matcher.proto'),
 | 
			
		||||
        join(__dirname, 'modules/health/adapters/primaries/health.proto'),
 | 
			
		||||
      ],
 | 
			
		||||
      url: process.env.SERVICE_URL + ':' + process.env.SERVICE_PORT,
 | 
			
		||||
      loader: { keepCase: true },
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
  // app.connectMicroservice<MicroserviceOptions>({
 | 
			
		||||
  //   transport: Transport.GRPC,
 | 
			
		||||
  //   options: {
 | 
			
		||||
  //     // package: ['matcher', 'health'],
 | 
			
		||||
  //     package: ['health'],
 | 
			
		||||
  //     protoPath: [
 | 
			
		||||
  //       // join(__dirname, 'modules/matcher/adapters/primaries/matcher.proto'),
 | 
			
		||||
  //       join(__dirname, 'modules/health/adapters/primaries/health.proto'),
 | 
			
		||||
  //     ],
 | 
			
		||||
  //     url: process.env.SERVICE_URL + ':' + process.env.SERVICE_PORT,
 | 
			
		||||
  //     loader: { keepCase: true },
 | 
			
		||||
  //   },
 | 
			
		||||
  // });
 | 
			
		||||
 | 
			
		||||
  await app.startAllMicroservices();
 | 
			
		||||
  await app.listen(process.env.HEALTH_SERVICE_PORT);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,42 @@
 | 
			
		|||
import { Controller } from '@nestjs/common';
 | 
			
		||||
import { GrpcMethod } from '@nestjs/microservices';
 | 
			
		||||
import { PrismaHealthIndicatorUseCase } from '../../domain/usecases/prisma.health-indicator.usecase';
 | 
			
		||||
 | 
			
		||||
enum ServingStatus {
 | 
			
		||||
  UNKNOWN = 0,
 | 
			
		||||
  SERVING = 1,
 | 
			
		||||
  NOT_SERVING = 2,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface HealthCheckRequest {
 | 
			
		||||
  service: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface HealthCheckResponse {
 | 
			
		||||
  status: ServingStatus;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Controller()
 | 
			
		||||
export class HealthServerController {
 | 
			
		||||
  constructor(
 | 
			
		||||
    private readonly _prismaHealthIndicatorUseCase: PrismaHealthIndicatorUseCase,
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  @GrpcMethod('Health', 'Check')
 | 
			
		||||
  async check(
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
    data: HealthCheckRequest,
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
    metadata: any,
 | 
			
		||||
  ): Promise<HealthCheckResponse> {
 | 
			
		||||
    const healthCheck = await this._prismaHealthIndicatorUseCase.isHealthy(
 | 
			
		||||
      'prisma',
 | 
			
		||||
    );
 | 
			
		||||
    return {
 | 
			
		||||
      status:
 | 
			
		||||
        healthCheck['prisma'].status == 'up'
 | 
			
		||||
          ? ServingStatus.SERVING
 | 
			
		||||
          : ServingStatus.NOT_SERVING,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,34 @@
 | 
			
		|||
import { Controller, Get } from '@nestjs/common';
 | 
			
		||||
import {
 | 
			
		||||
  HealthCheckService,
 | 
			
		||||
  HealthCheck,
 | 
			
		||||
  HealthCheckResult,
 | 
			
		||||
} from '@nestjs/terminus';
 | 
			
		||||
import { Messager } from '../secondaries/messager';
 | 
			
		||||
import { PrismaHealthIndicatorUseCase } from '../../domain/usecases/prisma.health-indicator.usecase';
 | 
			
		||||
 | 
			
		||||
@Controller('health')
 | 
			
		||||
export class HealthController {
 | 
			
		||||
  constructor(
 | 
			
		||||
    private readonly _prismaHealthIndicatorUseCase: PrismaHealthIndicatorUseCase,
 | 
			
		||||
    private _healthCheckService: HealthCheckService,
 | 
			
		||||
    private _messager: Messager,
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  @Get()
 | 
			
		||||
  @HealthCheck()
 | 
			
		||||
  async check() {
 | 
			
		||||
    try {
 | 
			
		||||
      return await this._healthCheckService.check([
 | 
			
		||||
        async () => this._prismaHealthIndicatorUseCase.isHealthy('prisma'),
 | 
			
		||||
      ]);
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      const healthCheckResult: HealthCheckResult = error.response;
 | 
			
		||||
      this._messager.publish(
 | 
			
		||||
        'logging.matcher.health.crit',
 | 
			
		||||
        JSON.stringify(healthCheckResult.error),
 | 
			
		||||
      );
 | 
			
		||||
      throw error;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,21 @@
 | 
			
		|||
syntax = "proto3";
 | 
			
		||||
 | 
			
		||||
package health;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
service Health {
 | 
			
		||||
  rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message HealthCheckRequest {
 | 
			
		||||
  string service = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message HealthCheckResponse {
 | 
			
		||||
  enum ServingStatus {
 | 
			
		||||
    UNKNOWN = 0;
 | 
			
		||||
    SERVING = 1;
 | 
			
		||||
    NOT_SERVING = 2;
 | 
			
		||||
  }
 | 
			
		||||
  ServingStatus status = 1;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
import { Injectable } from '@nestjs/common';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export abstract class IMessageBroker {
 | 
			
		||||
  exchange: string;
 | 
			
		||||
 | 
			
		||||
  constructor(exchange: string) {
 | 
			
		||||
    this.exchange = exchange;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  abstract publish(routingKey: string, message: string): void;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,18 @@
 | 
			
		|||
import { AmqpConnection } from '@golevelup/nestjs-rabbitmq';
 | 
			
		||||
import { Injectable } from '@nestjs/common';
 | 
			
		||||
import { ConfigService } from '@nestjs/config';
 | 
			
		||||
import { IMessageBroker } from './message-broker';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class Messager extends IMessageBroker {
 | 
			
		||||
  constructor(
 | 
			
		||||
    private readonly _amqpConnection: AmqpConnection,
 | 
			
		||||
    configService: ConfigService,
 | 
			
		||||
  ) {
 | 
			
		||||
    super(configService.get<string>('RMQ_EXCHANGE'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  publish(routingKey: string, message: string): void {
 | 
			
		||||
    this._amqpConnection.publish(this.exchange, routingKey, message);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,25 @@
 | 
			
		|||
import { Injectable } from '@nestjs/common';
 | 
			
		||||
import {
 | 
			
		||||
  HealthCheckError,
 | 
			
		||||
  HealthIndicator,
 | 
			
		||||
  HealthIndicatorResult,
 | 
			
		||||
} from '@nestjs/terminus';
 | 
			
		||||
import { AdRepository } from '../../../matcher/adapters/secondaries/ad.repository';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class PrismaHealthIndicatorUseCase extends HealthIndicator {
 | 
			
		||||
  constructor(private readonly _repository: AdRepository) {
 | 
			
		||||
    super();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async isHealthy(key: string): Promise<HealthIndicatorResult> {
 | 
			
		||||
    try {
 | 
			
		||||
      await this._repository.healthCheck();
 | 
			
		||||
      return this.getStatus(key, true);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      throw new HealthCheckError('Prisma', {
 | 
			
		||||
        prisma: e.message,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,34 @@
 | 
			
		|||
import { Module } from '@nestjs/common';
 | 
			
		||||
import { HealthServerController } from './adapters/primaries/health-server.controller';
 | 
			
		||||
import { PrismaHealthIndicatorUseCase } from './domain/usecases/prisma.health-indicator.usecase';
 | 
			
		||||
import { DatabaseModule } from '../database/database.module';
 | 
			
		||||
import { HealthController } from './adapters/primaries/health.controller';
 | 
			
		||||
import { TerminusModule } from '@nestjs/terminus';
 | 
			
		||||
import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq';
 | 
			
		||||
import { ConfigModule, ConfigService } from '@nestjs/config';
 | 
			
		||||
import { Messager } from './adapters/secondaries/messager';
 | 
			
		||||
import { AdRepository } from '../matcher/adapters/secondaries/ad.repository';
 | 
			
		||||
 | 
			
		||||
@Module({
 | 
			
		||||
  imports: [
 | 
			
		||||
    TerminusModule,
 | 
			
		||||
    RabbitMQModule.forRootAsync(RabbitMQModule, {
 | 
			
		||||
      imports: [ConfigModule],
 | 
			
		||||
      useFactory: async (configService: ConfigService) => ({
 | 
			
		||||
        exchanges: [
 | 
			
		||||
          {
 | 
			
		||||
            name: configService.get<string>('RMQ_EXCHANGE'),
 | 
			
		||||
            type: 'topic',
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
        uri: configService.get<string>('RMQ_URI'),
 | 
			
		||||
        connectionInitOptions: { wait: false },
 | 
			
		||||
      }),
 | 
			
		||||
      inject: [ConfigService],
 | 
			
		||||
    }),
 | 
			
		||||
    DatabaseModule,
 | 
			
		||||
  ],
 | 
			
		||||
  controllers: [HealthServerController, HealthController],
 | 
			
		||||
  providers: [PrismaHealthIndicatorUseCase, AdRepository, Messager],
 | 
			
		||||
})
 | 
			
		||||
export class HealthModule {}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,47 @@
 | 
			
		|||
import { AmqpConnection } from '@golevelup/nestjs-rabbitmq';
 | 
			
		||||
import { ConfigService } from '@nestjs/config';
 | 
			
		||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
			
		||||
import { Messager } from '../../adapters/secondaries/messager';
 | 
			
		||||
 | 
			
		||||
const mockAmqpConnection = {
 | 
			
		||||
  publish: jest.fn().mockImplementation(),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const mockConfigService = {
 | 
			
		||||
  get: jest.fn().mockResolvedValue({
 | 
			
		||||
    RMQ_EXCHANGE: 'mobicoop',
 | 
			
		||||
  }),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
describe('Messager', () => {
 | 
			
		||||
  let messager: Messager;
 | 
			
		||||
 | 
			
		||||
  beforeAll(async () => {
 | 
			
		||||
    const module: TestingModule = await Test.createTestingModule({
 | 
			
		||||
      imports: [],
 | 
			
		||||
      providers: [
 | 
			
		||||
        Messager,
 | 
			
		||||
        {
 | 
			
		||||
          provide: AmqpConnection,
 | 
			
		||||
          useValue: mockAmqpConnection,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          provide: ConfigService,
 | 
			
		||||
          useValue: mockConfigService,
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    }).compile();
 | 
			
		||||
 | 
			
		||||
    messager = module.get<Messager>(Messager);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should be defined', () => {
 | 
			
		||||
    expect(messager).toBeDefined();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should publish a message', async () => {
 | 
			
		||||
    jest.spyOn(mockAmqpConnection, 'publish');
 | 
			
		||||
    messager.publish('test.create.info', 'my-test');
 | 
			
		||||
    expect(mockAmqpConnection.publish).toHaveBeenCalledTimes(1);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,58 @@
 | 
			
		|||
import { Test, TestingModule } from '@nestjs/testing';
 | 
			
		||||
import { PrismaHealthIndicatorUseCase } from '../../domain/usecases/prisma.health-indicator.usecase';
 | 
			
		||||
import { TerritoriesRepository } from '../../../territory/adapters/secondaries/territories.repository';
 | 
			
		||||
import { PrismaClientKnownRequestError } from '@prisma/client/runtime';
 | 
			
		||||
import { HealthCheckError, HealthIndicatorResult } from '@nestjs/terminus';
 | 
			
		||||
 | 
			
		||||
const mockTerritoriesRepository = {
 | 
			
		||||
  healthCheck: jest
 | 
			
		||||
    .fn()
 | 
			
		||||
    .mockImplementationOnce(() => {
 | 
			
		||||
      return Promise.resolve(true);
 | 
			
		||||
    })
 | 
			
		||||
    .mockImplementation(() => {
 | 
			
		||||
      throw new PrismaClientKnownRequestError('Service unavailable', {
 | 
			
		||||
        code: 'code',
 | 
			
		||||
        clientVersion: 'version',
 | 
			
		||||
      });
 | 
			
		||||
    }),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
describe('PrismaHealthIndicatorUseCase', () => {
 | 
			
		||||
  let prismaHealthIndicatorUseCase: PrismaHealthIndicatorUseCase;
 | 
			
		||||
 | 
			
		||||
  beforeAll(async () => {
 | 
			
		||||
    const module: TestingModule = await Test.createTestingModule({
 | 
			
		||||
      providers: [
 | 
			
		||||
        {
 | 
			
		||||
          provide: TerritoriesRepository,
 | 
			
		||||
          useValue: mockTerritoriesRepository,
 | 
			
		||||
        },
 | 
			
		||||
        PrismaHealthIndicatorUseCase,
 | 
			
		||||
      ],
 | 
			
		||||
    }).compile();
 | 
			
		||||
 | 
			
		||||
    prismaHealthIndicatorUseCase = module.get<PrismaHealthIndicatorUseCase>(
 | 
			
		||||
      PrismaHealthIndicatorUseCase,
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should be defined', () => {
 | 
			
		||||
    expect(prismaHealthIndicatorUseCase).toBeDefined();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('execute', () => {
 | 
			
		||||
    it('should check health successfully', async () => {
 | 
			
		||||
      const healthIndicatorResult: HealthIndicatorResult =
 | 
			
		||||
        await prismaHealthIndicatorUseCase.isHealthy('prisma');
 | 
			
		||||
 | 
			
		||||
      expect(healthIndicatorResult['prisma'].status).toBe('up');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should throw an error if database is unavailable', async () => {
 | 
			
		||||
      await expect(
 | 
			
		||||
        prismaHealthIndicatorUseCase.isHealthy('prisma'),
 | 
			
		||||
      ).rejects.toBeInstanceOf(HealthCheckError);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,37 @@
 | 
			
		|||
import { Mapper } from '@automapper/core';
 | 
			
		||||
import { InjectMapper } from '@automapper/nestjs';
 | 
			
		||||
import { Controller, UsePipes } from '@nestjs/common';
 | 
			
		||||
import { CommandBus, QueryBus } from '@nestjs/cqrs';
 | 
			
		||||
import { GrpcMethod } from '@nestjs/microservices';
 | 
			
		||||
import { RpcValidationPipe } from 'src/modules/utils/pipes/rpc.validation-pipe';
 | 
			
		||||
import { MatchRequest } from '../../domain/dtos/match.request';
 | 
			
		||||
import { ICollection } from 'src/modules/database/src/interfaces/collection.interface';
 | 
			
		||||
import { Match } from '../../domain/entities/match';
 | 
			
		||||
import { MatchQuery } from '../../queries/match.query';
 | 
			
		||||
import { MatchPresenter } from '../secondaries/match.presenter';
 | 
			
		||||
 | 
			
		||||
@UsePipes(
 | 
			
		||||
  new RpcValidationPipe({
 | 
			
		||||
    whitelist: true,
 | 
			
		||||
    forbidUnknownValues: false,
 | 
			
		||||
  }),
 | 
			
		||||
)
 | 
			
		||||
@Controller()
 | 
			
		||||
export class MatcherController {
 | 
			
		||||
  constructor(
 | 
			
		||||
    private readonly _commandBus: CommandBus,
 | 
			
		||||
    private readonly _queryBus: QueryBus,
 | 
			
		||||
    @InjectMapper() private readonly _mapper: Mapper,
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  @GrpcMethod('MatcherService', 'Match')
 | 
			
		||||
  async match(data: MatchRequest): Promise<ICollection<Match>> {
 | 
			
		||||
    const matchCollection = await this._queryBus.execute(new MatchQuery(data));
 | 
			
		||||
    return Promise.resolve({
 | 
			
		||||
      data: matchCollection.data.map((match: Match) =>
 | 
			
		||||
        this._mapper.map(match, Match, MatchPresenter),
 | 
			
		||||
      ),
 | 
			
		||||
      total: matchCollection.total,
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,20 @@
 | 
			
		|||
syntax = "proto3";
 | 
			
		||||
 | 
			
		||||
package matcher;
 | 
			
		||||
 | 
			
		||||
service MatcherService {
 | 
			
		||||
  rpc Match(MatchRequest) returns (Matches);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message MatchRequest {
 | 
			
		||||
  string uuid = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message Match {
 | 
			
		||||
  string uuid = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message Matches {
 | 
			
		||||
  repeated Match data = 1;
 | 
			
		||||
  int32 total = 2;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
import { Injectable } from '@nestjs/common';
 | 
			
		||||
import { MatcherRepository } from '../../../database/src/domain/matcher-repository';
 | 
			
		||||
import { Ad } from '../../domain/entities/ad';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class AdRepository extends MatcherRepository<Ad> {
 | 
			
		||||
  protected _model = 'ad';
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
import { AutoMap } from '@automapper/classes';
 | 
			
		||||
 | 
			
		||||
export class MatchPresenter {
 | 
			
		||||
  @AutoMap()
 | 
			
		||||
  uuid: string;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
export enum Algorithm {
 | 
			
		||||
  CLASSIC = 'CLASSIC',
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,126 @@
 | 
			
		|||
import {
 | 
			
		||||
  IsArray,
 | 
			
		||||
  IsBoolean,
 | 
			
		||||
  IsDate,
 | 
			
		||||
  IsEnum,
 | 
			
		||||
  IsInt,
 | 
			
		||||
  IsNumber,
 | 
			
		||||
  IsOptional,
 | 
			
		||||
  Max,
 | 
			
		||||
  Min,
 | 
			
		||||
} from 'class-validator';
 | 
			
		||||
import { AutoMap } from '@automapper/classes';
 | 
			
		||||
import { Point } from '../entities/point.type';
 | 
			
		||||
import { Schedule } from '../entities/schedule.type';
 | 
			
		||||
import { MarginDurations } from '../entities/margin_durations.type';
 | 
			
		||||
import { Algorithm } from './algorithm.enum';
 | 
			
		||||
 | 
			
		||||
export class MatchRequest {
 | 
			
		||||
  @IsArray()
 | 
			
		||||
  @AutoMap()
 | 
			
		||||
  waypoints: Array<Point>;
 | 
			
		||||
 | 
			
		||||
  @IsDate()
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @AutoMap()
 | 
			
		||||
  departure: Date;
 | 
			
		||||
 | 
			
		||||
  @IsDate()
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @AutoMap()
 | 
			
		||||
  fromDate: Date;
 | 
			
		||||
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @AutoMap()
 | 
			
		||||
  schedule: Schedule;
 | 
			
		||||
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsBoolean()
 | 
			
		||||
  @AutoMap()
 | 
			
		||||
  driver: boolean;
 | 
			
		||||
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsBoolean()
 | 
			
		||||
  @AutoMap()
 | 
			
		||||
  passenger: boolean;
 | 
			
		||||
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsDate()
 | 
			
		||||
  @AutoMap()
 | 
			
		||||
  toDate: Date;
 | 
			
		||||
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  @AutoMap()
 | 
			
		||||
  marginDuration: number;
 | 
			
		||||
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @AutoMap()
 | 
			
		||||
  marginDurations: MarginDurations;
 | 
			
		||||
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsNumber()
 | 
			
		||||
  @AutoMap()
 | 
			
		||||
  seatsPassenger: number;
 | 
			
		||||
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsNumber()
 | 
			
		||||
  @AutoMap()
 | 
			
		||||
  seatsDriver: number;
 | 
			
		||||
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @AutoMap()
 | 
			
		||||
  strict: boolean;
 | 
			
		||||
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsEnum(Algorithm)
 | 
			
		||||
  @AutoMap()
 | 
			
		||||
  algorithm: Algorithm;
 | 
			
		||||
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsNumber()
 | 
			
		||||
  @AutoMap()
 | 
			
		||||
  remoteness: number;
 | 
			
		||||
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsBoolean()
 | 
			
		||||
  @AutoMap()
 | 
			
		||||
  useProportion: boolean;
 | 
			
		||||
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsNumber()
 | 
			
		||||
  @Min(0)
 | 
			
		||||
  @Max(1)
 | 
			
		||||
  @AutoMap()
 | 
			
		||||
  proportion: number;
 | 
			
		||||
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsBoolean()
 | 
			
		||||
  @AutoMap()
 | 
			
		||||
  useAzimuth: boolean;
 | 
			
		||||
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  @Min(0)
 | 
			
		||||
  @Max(359)
 | 
			
		||||
  @AutoMap()
 | 
			
		||||
  azimuthMargin: number;
 | 
			
		||||
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsNumber()
 | 
			
		||||
  @Min(0)
 | 
			
		||||
  @Max(1)
 | 
			
		||||
  @AutoMap()
 | 
			
		||||
  maxDetourDistanceRatio: number;
 | 
			
		||||
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsNumber()
 | 
			
		||||
  @Min(0)
 | 
			
		||||
  @Max(1)
 | 
			
		||||
  @AutoMap()
 | 
			
		||||
  maxDetourDurationRatio: number;
 | 
			
		||||
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsArray()
 | 
			
		||||
  exclusions: Array<number>;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,4 @@
 | 
			
		|||
export enum Role {
 | 
			
		||||
  DRIVER = 'DRIVER',
 | 
			
		||||
  PASSENGER = 'PASSENGER',
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
import { AutoMap } from '@automapper/classes';
 | 
			
		||||
 | 
			
		||||
export class Ad {
 | 
			
		||||
  @AutoMap()
 | 
			
		||||
  uuid: string;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
export type MarginDurations = {
 | 
			
		||||
  mon: number;
 | 
			
		||||
  tue: number;
 | 
			
		||||
  wed: number;
 | 
			
		||||
  thu: number;
 | 
			
		||||
  fri: number;
 | 
			
		||||
  sat: number;
 | 
			
		||||
  sun: number;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
import { AutoMap } from '@automapper/classes';
 | 
			
		||||
 | 
			
		||||
export class Match {
 | 
			
		||||
  @AutoMap()
 | 
			
		||||
  uuid: string;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,4 @@
 | 
			
		|||
export type Point = {
 | 
			
		||||
  lon: number;
 | 
			
		||||
  lat: number;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
export type Schedule = {
 | 
			
		||||
  mon: string;
 | 
			
		||||
  tue: string;
 | 
			
		||||
  wed: string;
 | 
			
		||||
  thu: string;
 | 
			
		||||
  fri: string;
 | 
			
		||||
  sat: string;
 | 
			
		||||
  sun: string;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,18 @@
 | 
			
		|||
import { createMap, Mapper } from '@automapper/core';
 | 
			
		||||
import { AutomapperProfile, InjectMapper } from '@automapper/nestjs';
 | 
			
		||||
import { Injectable } from '@nestjs/common';
 | 
			
		||||
import { MatchPresenter } from '../adapters/secondaries/match.presenter';
 | 
			
		||||
import { Match } from '../domain/entities/match';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class MatchProfile extends AutomapperProfile {
 | 
			
		||||
  constructor(@InjectMapper() mapper: Mapper) {
 | 
			
		||||
    super(mapper);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override get profile() {
 | 
			
		||||
    return (mapper) => {
 | 
			
		||||
      createMap(mapper, Match, MatchPresenter);
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
import { MatchRequest } from '../domain/dtos/match.request';
 | 
			
		||||
 | 
			
		||||
export class MatchQuery {
 | 
			
		||||
  matchRequest: MatchRequest;
 | 
			
		||||
 | 
			
		||||
  constructor(matchRequest?: MatchRequest) {
 | 
			
		||||
    this.matchRequest = matchRequest;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,22 +0,0 @@
 | 
			
		|||
import { ArgumentMetadata } from '@nestjs/common';
 | 
			
		||||
import { UpdateTerritoryRequest } from '../../../modules/territory/domain/dtos/update-territory.request';
 | 
			
		||||
import { RpcValidationPipe } from '../../pipes/rpc.validation-pipe';
 | 
			
		||||
 | 
			
		||||
describe('RpcValidationPipe', () => {
 | 
			
		||||
  it('should not validate request', async () => {
 | 
			
		||||
    const target: RpcValidationPipe = new RpcValidationPipe({
 | 
			
		||||
      whitelist: true,
 | 
			
		||||
      forbidUnknownValues: false,
 | 
			
		||||
    });
 | 
			
		||||
    const metadata: ArgumentMetadata = {
 | 
			
		||||
      type: 'body',
 | 
			
		||||
      metatype: UpdateTerritoryRequest,
 | 
			
		||||
      data: '',
 | 
			
		||||
    };
 | 
			
		||||
    await target
 | 
			
		||||
      .transform(<UpdateTerritoryRequest>{}, metadata)
 | 
			
		||||
      .catch((err) => {
 | 
			
		||||
        expect(err.message).toEqual('Rpc Exception');
 | 
			
		||||
      });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
		Loading…
	
		Reference in New Issue