Merge branch 'updatePackages' into 'main'

Update packages

See merge request v3/service/matcher!13
This commit is contained in:
Sylvain Briat 2023-10-18 08:36:22 +00:00
commit 0fed45c8b0
17 changed files with 3244 additions and 1874 deletions

View File

@ -10,6 +10,7 @@ DATABASE_URL="postgresql://mobicoop:mobicoop@v3-db:5432/mobicoop?schema=matcher"
# MESSAGE BROKER # MESSAGE BROKER
MESSAGE_BROKER_URI=amqp://v3-broker:5672 MESSAGE_BROKER_URI=amqp://v3-broker:5672
MESSAGE_BROKER_EXCHANGE=mobicoop MESSAGE_BROKER_EXCHANGE=mobicoop
MESSAGE_BROKER_EXCHANGE_DURABILITY=true
# REDIS # REDIS
REDIS_HOST=v3-redis REDIS_HOST=v3-redis

4869
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "@mobicoop/matcher", "name": "@mobicoop/matcher",
"version": "1.1.0", "version": "1.2.0",
"description": "Mobicoop V3 Matcher", "description": "Mobicoop V3 Matcher",
"author": "sbriat", "author": "sbriat",
"private": true, "private": true,
@ -30,63 +30,63 @@
"migrate:deploy": "npx prisma migrate deploy" "migrate:deploy": "npx prisma migrate deploy"
}, },
"dependencies": { "dependencies": {
"@grpc/grpc-js": "^1.8.14", "@grpc/grpc-js": "^1.9.6",
"@grpc/proto-loader": "^0.7.6", "@grpc/proto-loader": "^0.7.10",
"@liaoliaots/nestjs-redis": "^9.0.5", "@songkeys/nestjs-redis": "^10.0.0",
"@mobicoop/configuration-module": "^1.2.0", "@mobicoop/configuration-module": "^3.0.0",
"@mobicoop/ddd-library": "^1.5.0", "@mobicoop/ddd-library": "^2.0.0",
"@mobicoop/health-module": "^2.0.0", "@mobicoop/health-module": "^2.3.1",
"@mobicoop/message-broker-module": "^1.2.0", "@mobicoop/message-broker-module": "^2.1.1",
"@nestjs/axios": "^2.0.0", "@nestjs/axios": "^3.0.0",
"@nestjs/cache-manager": "^1.0.0", "@nestjs/cache-manager": "^2.1.0",
"@nestjs/common": "^9.0.0", "@nestjs/common": "^10.2.7",
"@nestjs/config": "^2.3.1", "@nestjs/config": "^3.1.1",
"@nestjs/core": "^9.0.0", "@nestjs/core": "^10.2.7",
"@nestjs/cqrs": "^9.0.3", "@nestjs/cqrs": "^10.2.6",
"@nestjs/event-emitter": "^1.4.2", "@nestjs/event-emitter": "^2.0.2",
"@nestjs/microservices": "^9.4.0", "@nestjs/microservices": "^10.2.7",
"@nestjs/platform-express": "^9.0.0", "@nestjs/platform-express": "^10.2.7",
"@nestjs/terminus": "^9.2.2", "@nestjs/terminus": "^10.1.1",
"@prisma/client": "^4.13.0", "@prisma/client": "^5.4.2",
"axios": "^1.3.5", "axios": "^1.5.1",
"cache-manager": "^5.2.3", "cache-manager": "^5.2.4",
"cache-manager-ioredis-yet": "^1.1.0", "cache-manager-ioredis-yet": "^1.2.2",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.0", "class-validator": "^0.14.0",
"geo-tz": "^7.0.7", "geo-tz": "^7.0.7",
"geographiclib-geodesic": "^2.0.0", "geographiclib-geodesic": "^2.0.0",
"got": "^11.8.6", "got": "^13.0.0",
"ioredis": "^5.3.2", "ioredis": "^5.3.2",
"nestjs-request-context": "^2.1.0", "nestjs-request-context": "^3.0.0",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rxjs": "^7.2.0", "rxjs": "^7.8.1",
"timezonecomplete": "^5.12.4" "timezonecomplete": "^5.12.4"
}, },
"devDependencies": { "devDependencies": {
"@nestjs/cli": "^9.0.0", "@nestjs/cli": "^10.1.18",
"@nestjs/schematics": "^9.0.0", "@nestjs/schematics": "^10.0.2",
"@nestjs/testing": "^9.0.0", "@nestjs/testing": "^10.2.7",
"@types/express": "^4.17.13", "@types/express": "^4.17.20",
"@types/jest": "29.5.0", "@types/jest": "29.5.6",
"@types/node": "18.15.11", "@types/node": "20.8.6",
"@types/supertest": "^2.0.11", "@types/supertest": "^2.0.14",
"@types/uuid": "^9.0.2", "@types/uuid": "^9.0.5",
"@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/eslint-plugin": "^6.8.0",
"@typescript-eslint/parser": "^5.0.0", "@typescript-eslint/parser": "^6.8.0",
"dotenv-cli": "^7.2.1", "dotenv-cli": "^7.3.0",
"eslint": "^8.0.1", "eslint": "^8.51.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^5.0.1",
"jest": "29.5.0", "jest": "29.7.0",
"prettier": "^2.3.2", "prettier": "^3.0.3",
"prisma": "^4.13.0", "prisma": "^5.4.2",
"source-map-support": "^0.5.20", "source-map-support": "^0.5.21",
"supertest": "^6.1.3", "supertest": "^6.3.3",
"ts-jest": "29.0.5", "ts-jest": "29.1.1",
"ts-loader": "^9.2.3", "ts-loader": "^9.5.0",
"ts-node": "^10.0.0", "ts-node": "^10.9.1",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"typescript": "^4.7.4" "typescript": "^5.2.2"
}, },
"jest": { "jest": {
"moduleFileExtensions": [ "moduleFileExtensions": [

28
src/app.constants.ts Normal file
View File

@ -0,0 +1,28 @@
// service
export const SERVICE_NAME = 'matcher';
// grpc
export const GRPC_PACKAGE_NAME = 'matcher';
// messaging
export const AD_CREATED_MESSAGE_HANDLER = 'adCreated';
export const AD_CREATED_ROUTING_KEY = 'ad.created';
export const AD_CREATED_QUEUE = 'matcher-ad-created';
export const AD_UPDATED_MESSAGE_HANDLER = 'adUpdated';
export const AD_UPDATED_ROUTING_KEY = 'ad.updated';
export const AD_UPDATED_QUEUE = 'matcher-ad-updated';
export const AD_DELETED_MESSAGE_HANDLER = 'adDeleted';
export const AD_DELETED_ROUTING_KEY = 'ad.deleted';
export const AD_DELETED_QUEUE = 'matcher-ad-deleted';
// configuration
export const SERVICE_CONFIGURATION_SET_QUEUE = 'matcher-configuration-set';
export const SERVICE_CONFIGURATION_DELETE_QUEUE =
'matcher-configuration-delete';
export const SERVICE_CONFIGURATION_PROPAGATE_QUEUE =
'matcher-configuration-propagate';
// health
export const GRPC_HEALTH_PACKAGE_NAME = 'health';
export const HEALTH_AD_REPOSITORY = 'AdRepository';
export const HEALTH_CRITICAL_LOGGING_KEY = 'logging.matcher.health.crit';

View File

@ -14,6 +14,14 @@ import { MESSAGE_PUBLISHER } from '@modules/messager/messager.di-tokens';
import { HealthModuleOptions } from '@mobicoop/health-module/dist/core/domain/types/health.types'; import { HealthModuleOptions } from '@mobicoop/health-module/dist/core/domain/types/health.types';
import { MessagePublisherPort } from '@mobicoop/ddd-library'; import { MessagePublisherPort } from '@mobicoop/ddd-library';
import { GeographyModule } from '@modules/geography/geography.module'; import { GeographyModule } from '@modules/geography/geography.module';
import {
HEALTH_AD_REPOSITORY,
HEALTH_CRITICAL_LOGGING_KEY,
SERVICE_CONFIGURATION_DELETE_QUEUE,
SERVICE_CONFIGURATION_PROPAGATE_QUEUE,
SERVICE_CONFIGURATION_SET_QUEUE,
SERVICE_NAME,
} from './app.constants';
@Module({ @Module({
imports: [ imports: [
@ -31,18 +39,23 @@ import { GeographyModule } from '@modules/geography/geography.module';
) as string, ) as string,
messageBroker: { messageBroker: {
uri: configService.get<string>('MESSAGE_BROKER_URI') as string, uri: configService.get<string>('MESSAGE_BROKER_URI') as string,
exchange: configService.get<string>( exchange: {
'MESSAGE_BROKER_EXCHANGE', name: configService.get<string>(
) as string, 'MESSAGE_BROKER_EXCHANGE',
) as string,
durable: configService.get<boolean>(
'MESSAGE_BROKER_EXCHANGE_DURABILITY',
) as boolean,
},
}, },
redis: { redis: {
host: configService.get<string>('REDIS_HOST') as string, host: configService.get<string>('REDIS_HOST') as string,
password: configService.get<string>('REDIS_PASSWORD'), password: configService.get<string>('REDIS_PASSWORD'),
port: configService.get<number>('REDIS_PORT') as number, port: configService.get<number>('REDIS_PORT') as number,
}, },
setConfigurationBrokerQueue: 'matcher-configuration-create-update', setConfigurationQueue: SERVICE_CONFIGURATION_SET_QUEUE,
deleteConfigurationQueue: 'matcher-configuration-delete', deleteConfigurationQueue: SERVICE_CONFIGURATION_DELETE_QUEUE,
propagateConfigurationQueue: 'matcher-configuration-propagate', propagateConfigurationQueue: SERVICE_CONFIGURATION_PROPAGATE_QUEUE,
}), }),
}), }),
HealthModule.forRootAsync({ HealthModule.forRootAsync({
@ -52,11 +65,11 @@ import { GeographyModule } from '@modules/geography/geography.module';
adRepository: HealthRepositoryPort, adRepository: HealthRepositoryPort,
messagePublisher: MessagePublisherPort, messagePublisher: MessagePublisherPort,
): Promise<HealthModuleOptions> => ({ ): Promise<HealthModuleOptions> => ({
serviceName: 'matcher', serviceName: SERVICE_NAME,
criticalLoggingKey: 'logging.matcher.health.crit', criticalLoggingKey: HEALTH_CRITICAL_LOGGING_KEY,
checkRepositories: [ checkRepositories: [
{ {
name: 'AdRepository', name: HEALTH_AD_REPOSITORY,
repository: adRepository, repository: adRepository,
}, },
], ],

View File

@ -2,6 +2,7 @@ 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';
import { GRPC_HEALTH_PACKAGE_NAME, GRPC_PACKAGE_NAME } from './app.constants';
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule);
@ -11,12 +12,12 @@ async function bootstrap() {
app.connectMicroservice<MicroserviceOptions>({ app.connectMicroservice<MicroserviceOptions>({
transport: Transport.GRPC, transport: Transport.GRPC,
options: { options: {
package: ['matcher', 'health'], package: [GRPC_PACKAGE_NAME, GRPC_HEALTH_PACKAGE_NAME],
protoPath: [ protoPath: [
join(__dirname, 'modules/ad/interface/grpc-controllers/matcher.proto'), join(__dirname, 'modules/ad/interface/grpc-controllers/matcher.proto'),
join(__dirname, 'health.proto'), join(__dirname, 'health.proto'),
], ],
url: process.env.SERVICE_URL + ':' + process.env.SERVICE_PORT, url: `${process.env.SERVICE_URL}:${process.env.SERVICE_PORT}`,
loader: { keepCase: true, enums: String }, loader: { keepCase: true, enums: String },
}, },
}); });

View File

@ -36,9 +36,9 @@ import { OutputDateTimeTransformer } from './infrastructure/output-datetime-tran
import { MatchingRepository } from './infrastructure/matching.repository'; import { MatchingRepository } from './infrastructure/matching.repository';
import { MatchingMapper } from './matching.mapper'; import { MatchingMapper } from './matching.mapper';
import { CacheModule } from '@nestjs/cache-manager'; import { CacheModule } from '@nestjs/cache-manager';
import { RedisClientOptions } from '@liaoliaots/nestjs-redis';
import { ConfigModule, ConfigService } from '@nestjs/config'; import { ConfigModule, ConfigService } from '@nestjs/config';
import { redisStore } from 'cache-manager-ioredis-yet'; import { redisStore } from 'cache-manager-ioredis-yet';
import { RedisClientOptions } from '@songkeys/nestjs-redis';
const imports = [ const imports = [
CqrsModule, CqrsModule,

View File

@ -8,6 +8,7 @@ import { AdEntity } from '../core/domain/ad.entity';
import { AdMapper } from '../ad.mapper'; import { AdMapper } from '../ad.mapper';
import { ExtendedPrismaRepositoryBase } from '@mobicoop/ddd-library/dist/db/prisma-repository.base'; import { ExtendedPrismaRepositoryBase } from '@mobicoop/ddd-library/dist/db/prisma-repository.base';
import { Frequency } from '../core/domain/ad.types'; import { Frequency } from '../core/domain/ad.types';
import { SERVICE_NAME } from '@src/app.constants';
export type AdModel = { export type AdModel = {
uuid: string; uuid: string;
@ -38,7 +39,7 @@ export type AdReadModel = AdModel & {
}; };
/** /**
* The record ready to be sent to the peristence system * The record ready to be sent to the persistence system
*/ */
export type AdWriteModel = AdModel & { export type AdWriteModel = AdModel & {
schedule: { schedule: {
@ -103,7 +104,7 @@ export class AdRepository
eventEmitter, eventEmitter,
new LoggerBase({ new LoggerBase({
logger: new Logger(AdRepository.name), logger: new Logger(AdRepository.name),
domain: 'matcher', domain: SERVICE_NAME,
messagePublisher, messagePublisher,
}), }),
); );

View File

@ -1,10 +1,10 @@
import { InjectRedis } from '@liaoliaots/nestjs-redis';
import { MatchingRepositoryPort } from '../core/application/ports/matching.repository.port'; import { MatchingRepositoryPort } from '../core/application/ports/matching.repository.port';
import { MatchingEntity } from '../core/domain/matching.entity'; import { MatchingEntity } from '../core/domain/matching.entity';
import { Redis } from 'ioredis'; import { Redis } from 'ioredis';
import { MatchingMapper } from '../matching.mapper'; import { MatchingMapper } from '../matching.mapper';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { MatchingNotFoundException } from '../core/domain/matching.errors'; import { MatchingNotFoundException } from '../core/domain/matching.errors';
import { InjectRedis } from '@songkeys/nestjs-redis';
const REDIS_MATCHING_TTL = 900; const REDIS_MATCHING_TTL = 900;
const REDIS_MATCHING_KEY = 'MATCHER:MATCHING'; const REDIS_MATCHING_KEY = 'MATCHER:MATCHING';

View File

@ -1,4 +1,4 @@
import { INestApplication, Injectable, OnModuleInit } from '@nestjs/common'; import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client';
@Injectable() @Injectable()
@ -6,10 +6,4 @@ export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() { async onModuleInit() {
await this.$connect(); await this.$connect();
} }
async enableShutdownHooks(app: INestApplication) {
this.$on('beforeExit', async () => {
await app.close();
});
}
} }

View File

@ -3,13 +3,14 @@ import { RabbitSubscribe } from '@mobicoop/message-broker-module';
import { CommandBus } from '@nestjs/cqrs'; import { CommandBus } from '@nestjs/cqrs';
import { CreateAdCommand } from '@modules/ad/core/application/commands/create-ad/create-ad.command'; import { CreateAdCommand } from '@modules/ad/core/application/commands/create-ad/create-ad.command';
import { Ad } from './ad.types'; import { Ad } from './ad.types';
import { AD_CREATED_MESSAGE_HANDLER } from '@src/app.constants';
@Injectable() @Injectable()
export class AdCreatedMessageHandler { export class AdCreatedMessageHandler {
constructor(private readonly commandBus: CommandBus) {} constructor(private readonly commandBus: CommandBus) {}
@RabbitSubscribe({ @RabbitSubscribe({
name: 'adCreated', name: AD_CREATED_MESSAGE_HANDLER,
}) })
public async adCreated(message: string) { public async adCreated(message: string) {
try { try {

View File

@ -137,9 +137,8 @@ describe('create-ad.service', () => {
AdEntity.create = jest.fn().mockReturnValue({ AdEntity.create = jest.fn().mockReturnValue({
id: '047a6ecf-23d4-4d3e-877c-3225d560a8da', id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
}); });
const result: AggregateID = await createAdService.execute( const result: AggregateID =
createAdCommand, await createAdService.execute(createAdCommand);
);
expect(result).toBe('047a6ecf-23d4-4d3e-877c-3225d560a8da'); expect(result).toBe('047a6ecf-23d4-4d3e-877c-3225d560a8da');
}); });
it('should create a new ad as passenger', async () => { it('should create a new ad as passenger', async () => {

View File

@ -332,9 +332,8 @@ describe('Match Query Handler', () => {
}, },
mockRouteProvider, mockRouteProvider,
); );
const matching: MatchingResult = await matchQueryHandler.execute( const matching: MatchingResult =
matchQuery, await matchQueryHandler.execute(matchQuery);
);
expect(matching.id).toHaveLength(36); expect(matching.id).toHaveLength(36);
expect(MatchingEntity.create).toHaveBeenCalledTimes(1); expect(MatchingEntity.create).toHaveBeenCalledTimes(1);
}); });
@ -362,9 +361,8 @@ describe('Match Query Handler', () => {
}, },
mockRouteProvider, mockRouteProvider,
); );
const matching: MatchingResult = await matchQueryHandler.execute( const matching: MatchingResult =
matchQuery, await matchQueryHandler.execute(matchQuery);
);
expect(matching.id).toBe('a3b10efb-121e-4d08-9198-9f57afdb5e2d'); expect(matching.id).toBe('a3b10efb-121e-4d08-9198-9f57afdb5e2d');
expect(MatchingEntity.create).toHaveBeenCalledTimes(0); expect(MatchingEntity.create).toHaveBeenCalledTimes(0);
}); });
@ -392,9 +390,8 @@ describe('Match Query Handler', () => {
}, },
mockRouteProvider, mockRouteProvider,
); );
const matching: MatchingResult = await matchQueryHandler.execute( const matching: MatchingResult =
matchQuery, await matchQueryHandler.execute(matchQuery);
);
expect(matching.id).toHaveLength(36); expect(matching.id).toHaveLength(36);
expect(MatchingEntity.create).toHaveBeenCalledTimes(1); expect(MatchingEntity.create).toHaveBeenCalledTimes(1);
}); });

View File

@ -274,9 +274,8 @@ describe('Ad repository', () => {
}); });
it('should return an empty array of candidates if query does not return Ads', async () => { it('should return an empty array of candidates if query does not return Ads', async () => {
const candidates: AdEntity[] = await adRepository.getCandidateAds( const candidates: AdEntity[] =
'someQueryString', await adRepository.getCandidateAds('someQueryString');
);
expect(candidates.length).toBe(0); expect(candidates.length).toBe(0);
}); });
}); });

View File

@ -1,4 +1,3 @@
import { getRedisToken } from '@liaoliaots/nestjs-redis';
import { Frequency, Role } from '@modules/ad/core/domain/ad.types'; import { Frequency, Role } from '@modules/ad/core/domain/ad.types';
import { Target } from '@modules/ad/core/domain/candidate.types'; import { Target } from '@modules/ad/core/domain/candidate.types';
import { MatchEntity } from '@modules/ad/core/domain/match.entity'; import { MatchEntity } from '@modules/ad/core/domain/match.entity';
@ -8,6 +7,7 @@ import { MatchingRepository } from '@modules/ad/infrastructure/matching.reposito
import { MatchingMapper } from '@modules/ad/matching.mapper'; import { MatchingMapper } from '@modules/ad/matching.mapper';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { getRedisToken } from '@songkeys/nestjs-redis';
const mockConfigService = { const mockConfigService = {
get: jest.fn().mockImplementation((value: string) => { get: jest.fn().mockImplementation((value: string) => {

View File

@ -6,6 +6,18 @@ import {
MessageBrokerPublisher, MessageBrokerPublisher,
} from '@mobicoop/message-broker-module'; } from '@mobicoop/message-broker-module';
import { ConfigModule, ConfigService } from '@nestjs/config'; import { ConfigModule, ConfigService } from '@nestjs/config';
import {
AD_CREATED_MESSAGE_HANDLER,
AD_CREATED_QUEUE,
AD_CREATED_ROUTING_KEY,
AD_DELETED_MESSAGE_HANDLER,
AD_DELETED_QUEUE,
AD_DELETED_ROUTING_KEY,
AD_UPDATED_MESSAGE_HANDLER,
AD_UPDATED_QUEUE,
AD_UPDATED_ROUTING_KEY,
SERVICE_NAME,
} from '@src/app.constants';
const imports = [ const imports = [
MessageBrokerModule.forRootAsync({ MessageBrokerModule.forRootAsync({
@ -15,20 +27,25 @@ const imports = [
configService: ConfigService, configService: ConfigService,
): Promise<MessageBrokerModuleOptions> => ({ ): Promise<MessageBrokerModuleOptions> => ({
uri: configService.get<string>('MESSAGE_BROKER_URI') as string, uri: configService.get<string>('MESSAGE_BROKER_URI') as string,
exchange: configService.get<string>('MESSAGE_BROKER_EXCHANGE') as string, exchange: {
name: 'matcher', name: configService.get<string>('MESSAGE_BROKER_EXCHANGE') as string,
durable: configService.get<boolean>(
'MESSAGE_BROKER_EXCHANGE_DURABILITY',
) as boolean,
},
name: SERVICE_NAME,
handlers: { handlers: {
adCreated: { [AD_CREATED_MESSAGE_HANDLER]: {
routingKey: 'ad.created', routingKey: AD_CREATED_ROUTING_KEY,
queue: 'matcher-ad-created', queue: AD_CREATED_QUEUE,
}, },
adUpdated: { [AD_UPDATED_MESSAGE_HANDLER]: {
routingKey: 'ad.updated', routingKey: AD_UPDATED_ROUTING_KEY,
queue: 'matcher-ad-updated', queue: AD_UPDATED_QUEUE,
}, },
adDeleted: { [AD_DELETED_MESSAGE_HANDLER]: {
routingKey: 'ad.deleted', routingKey: AD_DELETED_ROUTING_KEY,
queue: 'matcher-ad-deleted', queue: AD_DELETED_QUEUE,
}, },
}, },
}), }),

View File

@ -15,7 +15,7 @@
"strictNullChecks": true, "strictNullChecks": true,
"noImplicitAny": true, "noImplicitAny": true,
"strictBindCallApply": false, "strictBindCallApply": false,
"forceConsistentCasingInFileNames": false, "forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": false, "noFallthroughCasesInSwitch": false,
"paths": { "paths": {
"@libs/*": ["src/libs/*"], "@libs/*": ["src/libs/*"],