create module
This commit is contained in:
parent
d7b1c54b45
commit
afca685e3d
|
@ -16,6 +16,7 @@
|
||||||
"@grpc/grpc-js": "^1.8.13",
|
"@grpc/grpc-js": "^1.8.13",
|
||||||
"@grpc/proto-loader": "^0.7.6",
|
"@grpc/proto-loader": "^0.7.6",
|
||||||
"@liaoliaots/nestjs-redis": "^9.0.5",
|
"@liaoliaots/nestjs-redis": "^9.0.5",
|
||||||
|
"@nestjs/cache-manager": "^1.0.0",
|
||||||
"@nestjs/common": "^9.0.0",
|
"@nestjs/common": "^9.0.0",
|
||||||
"@nestjs/config": "^2.3.1",
|
"@nestjs/config": "^2.3.1",
|
||||||
"@nestjs/core": "^9.0.0",
|
"@nestjs/core": "^9.0.0",
|
||||||
|
@ -1569,6 +1570,17 @@
|
||||||
"node": ">=8"
|
"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": {
|
"node_modules/@nestjs/cli": {
|
||||||
"version": "9.3.0",
|
"version": "9.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-9.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-9.3.0.tgz",
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
"@grpc/grpc-js": "^1.8.13",
|
"@grpc/grpc-js": "^1.8.13",
|
||||||
"@grpc/proto-loader": "^0.7.6",
|
"@grpc/proto-loader": "^0.7.6",
|
||||||
"@liaoliaots/nestjs-redis": "^9.0.5",
|
"@liaoliaots/nestjs-redis": "^9.0.5",
|
||||||
|
"@nestjs/cache-manager": "^1.0.0",
|
||||||
"@nestjs/common": "^9.0.0",
|
"@nestjs/common": "^9.0.0",
|
||||||
"@nestjs/config": "^2.3.1",
|
"@nestjs/config": "^2.3.1",
|
||||||
"@nestjs/core": "^9.0.0",
|
"@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 { Module } from '@nestjs/common';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
import { ConfigurationModule } from './modules/configuration/configuration.module';
|
import { ConfigurationModule } from './modules/configuration/configuration.module';
|
||||||
|
import { HealthModule } from './modules/health/health.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot({ isGlobal: true }),
|
ConfigModule.forRoot({ isGlobal: true }),
|
||||||
AutomapperModule.forRoot({ strategyInitializer: classes() }),
|
AutomapperModule.forRoot({ strategyInitializer: classes() }),
|
||||||
ConfigurationModule,
|
ConfigurationModule,
|
||||||
|
HealthModule,
|
||||||
],
|
],
|
||||||
controllers: [],
|
controllers: [],
|
||||||
providers: [],
|
providers: [],
|
||||||
|
|
28
src/main.ts
28
src/main.ts
|
@ -1,6 +1,6 @@
|
||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
|
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
|
||||||
import { join } from 'path';
|
// import { join } from 'path';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
|
@ -8,19 +8,19 @@ async function bootstrap() {
|
||||||
app.connectMicroservice<MicroserviceOptions>({
|
app.connectMicroservice<MicroserviceOptions>({
|
||||||
transport: Transport.TCP,
|
transport: Transport.TCP,
|
||||||
});
|
});
|
||||||
app.connectMicroservice<MicroserviceOptions>({
|
// app.connectMicroservice<MicroserviceOptions>({
|
||||||
transport: Transport.GRPC,
|
// transport: Transport.GRPC,
|
||||||
options: {
|
// options: {
|
||||||
// package: ['matcher', 'health'],
|
// // package: ['matcher', 'health'],
|
||||||
package: ['health'],
|
// package: ['health'],
|
||||||
protoPath: [
|
// protoPath: [
|
||||||
// join(__dirname, 'modules/matcher/adapters/primaries/matcher.proto'),
|
// // join(__dirname, 'modules/matcher/adapters/primaries/matcher.proto'),
|
||||||
join(__dirname, 'modules/health/adapters/primaries/health.proto'),
|
// join(__dirname, 'modules/health/adapters/primaries/health.proto'),
|
||||||
],
|
// ],
|
||||||
url: process.env.SERVICE_URL + ':' + process.env.SERVICE_PORT,
|
// url: process.env.SERVICE_URL + ':' + process.env.SERVICE_PORT,
|
||||||
loader: { keepCase: true },
|
// loader: { keepCase: true },
|
||||||
},
|
// },
|
||||||
});
|
// });
|
||||||
|
|
||||||
await app.startAllMicroservices();
|
await app.startAllMicroservices();
|
||||||
await app.listen(process.env.HEALTH_SERVICE_PORT);
|
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