diff --git a/src/app.module.ts b/src/app.module.ts index ebb535c..a457cad 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,6 +1,5 @@ import { Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; -// import { HealthModule } from './modules/health/health.module'; import { AdModule } from './modules/ad/ad.module'; import { MessageBrokerModule, diff --git a/src/libs/db/prisma-repository.base.ts b/src/libs/db/prisma-repository.base.ts index 897834a..cfb1ed7 100644 --- a/src/libs/db/prisma-repository.base.ts +++ b/src/libs/db/prisma-repository.base.ts @@ -41,6 +41,7 @@ export abstract class PrismaRepositoryBase< await this.prisma.create({ data: this.mapper.toPersistence(entity), }); + entity.publishEvents(this.logger, this.eventEmitter); } catch (e) { if (e instanceof Prisma.PrismaClientKnownRequestError) { if (e.message.includes('Already exists')) { diff --git a/src/modules/ad/ad.module.ts b/src/modules/ad/ad.module.ts index 40dd238..15bc8fb 100644 --- a/src/modules/ad/ad.module.ts +++ b/src/modules/ad/ad.module.ts @@ -22,6 +22,8 @@ import { PrismaService } from '@libs/db/prisma.service'; import { TimeConverter } from './infrastructure/time-converter'; import { FindAdByIdGrpcController } from './interface/grpc-controllers/find-ad-by-id.grpc.controller'; import { FindAdByIdQueryHandler } from './core/queries/find-ad-by-id/find-ad-by-id.query-handler'; +import { PublishMessageWhenAdIsCreatedDomainEventHandler } from './core/event-handlers/publish-message-when-ad-is-created.domain-event-handler'; +import { PublishLogMessageWhenAdIsCreatedDomainEventHandler } from './core/event-handlers/publish-log-message-when-ad-is-created.domain-event-handler'; @Module({ imports: [CqrsModule], @@ -29,6 +31,8 @@ import { FindAdByIdQueryHandler } from './core/queries/find-ad-by-id/find-ad-by- providers: [ CreateAdService, FindAdByIdQueryHandler, + PublishMessageWhenAdIsCreatedDomainEventHandler, + PublishLogMessageWhenAdIsCreatedDomainEventHandler, PrismaService, AdMapper, { diff --git a/src/modules/ad/core/event-handlers/publish-log-message-when-ad-is-created.domain-event-handler.ts b/src/modules/ad/core/event-handlers/publish-log-message-when-ad-is-created.domain-event-handler.ts new file mode 100644 index 0000000..7913c7b --- /dev/null +++ b/src/modules/ad/core/event-handlers/publish-log-message-when-ad-is-created.domain-event-handler.ts @@ -0,0 +1,21 @@ +import { MessagePublisherPort } from '@libs/ports/message-publisher.port'; +import { Inject, Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { MESSAGE_PUBLISHER } from '@src/app.constants'; +import { AdCreatedDomainEvent } from '../events/ad-created.domain-events'; + +@Injectable() +export class PublishLogMessageWhenAdIsCreatedDomainEventHandler { + constructor( + @Inject(MESSAGE_PUBLISHER) + private readonly messagePublisher: MessagePublisherPort, + ) {} + + @OnEvent(AdCreatedDomainEvent.name, { async: true, promisify: true }) + async handle(event: AdCreatedDomainEvent): Promise { + this.messagePublisher.publish( + 'logging.ad.created.info', + JSON.stringify(event), + ); + } +} diff --git a/src/modules/ad/core/event-handlers/publish-message-when-ad-is-created.domain-event-handler.ts b/src/modules/ad/core/event-handlers/publish-message-when-ad-is-created.domain-event-handler.ts new file mode 100644 index 0000000..69e4d38 --- /dev/null +++ b/src/modules/ad/core/event-handlers/publish-message-when-ad-is-created.domain-event-handler.ts @@ -0,0 +1,18 @@ +import { MessagePublisherPort } from '@libs/ports/message-publisher.port'; +import { Inject, Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { MESSAGE_PUBLISHER } from '@src/app.constants'; +import { AdCreatedDomainEvent } from '../events/ad-created.domain-events'; + +@Injectable() +export class PublishMessageWhenAdIsCreatedDomainEventHandler { + constructor( + @Inject(MESSAGE_PUBLISHER) + private readonly messagePublisher: MessagePublisherPort, + ) {} + + @OnEvent(AdCreatedDomainEvent.name, { async: true, promisify: true }) + async handle(event: AdCreatedDomainEvent): Promise { + this.messagePublisher.publish('ad.created', JSON.stringify(event)); + } +} diff --git a/src/modules/ad/tests/unit/core/publish-log-message-when-ad-is-created.domain-event-handler.spec.ts b/src/modules/ad/tests/unit/core/publish-log-message-when-ad-is-created.domain-event-handler.spec.ts new file mode 100644 index 0000000..2dc72fd --- /dev/null +++ b/src/modules/ad/tests/unit/core/publish-log-message-when-ad-is-created.domain-event-handler.spec.ts @@ -0,0 +1,94 @@ +import { Frequency } from '@modules/ad/core/ad.types'; +import { PublishLogMessageWhenAdIsCreatedDomainEventHandler } from '@modules/ad/core/event-handlers/publish-log-message-when-ad-is-created.domain-event-handler'; +import { AdCreatedDomainEvent } from '@modules/ad/core/events/ad-created.domain-events'; +import { Test, TestingModule } from '@nestjs/testing'; +import { MESSAGE_PUBLISHER } from '@src/app.constants'; + +const mockMessagePublisher = { + publish: jest.fn().mockImplementation(), +}; + +describe('Publish log message when ad is created domain event handler', () => { + let publishLogMessageWhenAdIsCreatedDomainEventHandler: PublishLogMessageWhenAdIsCreatedDomainEventHandler; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + { + provide: MESSAGE_PUBLISHER, + useValue: mockMessagePublisher, + }, + PublishLogMessageWhenAdIsCreatedDomainEventHandler, + ], + }).compile(); + + publishLogMessageWhenAdIsCreatedDomainEventHandler = + module.get( + PublishLogMessageWhenAdIsCreatedDomainEventHandler, + ); + }); + + it('should publish a log message', () => { + jest.spyOn(mockMessagePublisher, 'publish'); + const adCreatedDomainEvent: AdCreatedDomainEvent = { + id: 'some-domain-event-id', + aggregateId: 'some-aggregate-id', + userId: 'some-user-id', + driver: false, + passenger: true, + frequency: Frequency.PUNCTUAL, + fromDate: '2023-06-28', + toDate: '2023-06-28', + monTime: undefined, + tueTime: undefined, + wedTime: '07:15', + thuTime: undefined, + friTime: undefined, + satTime: undefined, + sunTime: undefined, + monMarginDuration: 900, + tueMarginDuration: 900, + wedMarginDuration: 900, + thuMarginDuration: 900, + friMarginDuration: 900, + satMarginDuration: 900, + sunMarginDuration: 900, + seatsProposed: 3, + seatsRequested: 1, + strict: false, + waypoints: [ + { + position: 0, + houseNumber: '5', + street: 'Avenue Foch', + locality: 'Nancy', + postalCode: '54000', + country: 'France', + lat: 48.689445, + lon: 6.1765102, + }, + { + position: 1, + locality: 'Paris', + postalCode: '75000', + country: 'France', + lat: 48.8566, + lon: 2.3522, + }, + ], + metadata: { + timestamp: new Date('2023-06-28T05:00:00Z').getTime(), + correlationId: 'some-correlation-id', + }, + }; + publishLogMessageWhenAdIsCreatedDomainEventHandler.handle( + adCreatedDomainEvent, + ); + expect(publishLogMessageWhenAdIsCreatedDomainEventHandler).toBeDefined(); + expect(mockMessagePublisher.publish).toHaveBeenCalledTimes(1); + expect(mockMessagePublisher.publish).toHaveBeenCalledWith( + 'logging.ad.created.info', + '{"id":"some-domain-event-id","aggregateId":"some-aggregate-id","userId":"some-user-id","driver":false,"passenger":true,"frequency":"PUNCTUAL","fromDate":"2023-06-28","toDate":"2023-06-28","wedTime":"07:15","monMarginDuration":900,"tueMarginDuration":900,"wedMarginDuration":900,"thuMarginDuration":900,"friMarginDuration":900,"satMarginDuration":900,"sunMarginDuration":900,"seatsProposed":3,"seatsRequested":1,"strict":false,"waypoints":[{"position":0,"houseNumber":"5","street":"Avenue Foch","locality":"Nancy","postalCode":"54000","country":"France","lat":48.689445,"lon":6.1765102},{"position":1,"locality":"Paris","postalCode":"75000","country":"France","lat":48.8566,"lon":2.3522}],"metadata":{"timestamp":1687928400000,"correlationId":"some-correlation-id"}}', + ); + }); +}); diff --git a/src/modules/ad/tests/unit/core/publish-message-when-ad-is-created.domain-event-handler.spec.ts b/src/modules/ad/tests/unit/core/publish-message-when-ad-is-created.domain-event-handler.spec.ts new file mode 100644 index 0000000..d07fc97 --- /dev/null +++ b/src/modules/ad/tests/unit/core/publish-message-when-ad-is-created.domain-event-handler.spec.ts @@ -0,0 +1,94 @@ +import { Frequency } from '@modules/ad/core/ad.types'; +import { PublishMessageWhenAdIsCreatedDomainEventHandler } from '@modules/ad/core/event-handlers/publish-message-when-ad-is-created.domain-event-handler'; +import { AdCreatedDomainEvent } from '@modules/ad/core/events/ad-created.domain-events'; +import { Test, TestingModule } from '@nestjs/testing'; +import { MESSAGE_PUBLISHER } from '@src/app.constants'; + +const mockMessagePublisher = { + publish: jest.fn().mockImplementation(), +}; + +describe('Publish message when ad is created domain event handler', () => { + let publishMessageWhenAdIsCreatedDomainEventHandler: PublishMessageWhenAdIsCreatedDomainEventHandler; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + { + provide: MESSAGE_PUBLISHER, + useValue: mockMessagePublisher, + }, + PublishMessageWhenAdIsCreatedDomainEventHandler, + ], + }).compile(); + + publishMessageWhenAdIsCreatedDomainEventHandler = + module.get( + PublishMessageWhenAdIsCreatedDomainEventHandler, + ); + }); + + it('should publish a message', () => { + jest.spyOn(mockMessagePublisher, 'publish'); + const adCreatedDomainEvent: AdCreatedDomainEvent = { + id: 'some-domain-event-id', + aggregateId: 'some-aggregate-id', + userId: 'some-user-id', + driver: false, + passenger: true, + frequency: Frequency.PUNCTUAL, + fromDate: '2023-06-28', + toDate: '2023-06-28', + monTime: undefined, + tueTime: undefined, + wedTime: '07:15', + thuTime: undefined, + friTime: undefined, + satTime: undefined, + sunTime: undefined, + monMarginDuration: 900, + tueMarginDuration: 900, + wedMarginDuration: 900, + thuMarginDuration: 900, + friMarginDuration: 900, + satMarginDuration: 900, + sunMarginDuration: 900, + seatsProposed: 3, + seatsRequested: 1, + strict: false, + waypoints: [ + { + position: 0, + houseNumber: '5', + street: 'Avenue Foch', + locality: 'Nancy', + postalCode: '54000', + country: 'France', + lat: 48.689445, + lon: 6.1765102, + }, + { + position: 1, + locality: 'Paris', + postalCode: '75000', + country: 'France', + lat: 48.8566, + lon: 2.3522, + }, + ], + metadata: { + timestamp: new Date('2023-06-28T05:00:00Z').getTime(), + correlationId: 'some-correlation-id', + }, + }; + publishMessageWhenAdIsCreatedDomainEventHandler.handle( + adCreatedDomainEvent, + ); + expect(publishMessageWhenAdIsCreatedDomainEventHandler).toBeDefined(); + expect(mockMessagePublisher.publish).toHaveBeenCalledTimes(1); + expect(mockMessagePublisher.publish).toHaveBeenCalledWith( + 'ad.created', + '{"id":"some-domain-event-id","aggregateId":"some-aggregate-id","userId":"some-user-id","driver":false,"passenger":true,"frequency":"PUNCTUAL","fromDate":"2023-06-28","toDate":"2023-06-28","wedTime":"07:15","monMarginDuration":900,"tueMarginDuration":900,"wedMarginDuration":900,"thuMarginDuration":900,"friMarginDuration":900,"satMarginDuration":900,"sunMarginDuration":900,"seatsProposed":3,"seatsRequested":1,"strict":false,"waypoints":[{"position":0,"houseNumber":"5","street":"Avenue Foch","locality":"Nancy","postalCode":"54000","country":"France","lat":48.689445,"lon":6.1765102},{"position":1,"locality":"Paris","postalCode":"75000","country":"France","lat":48.8566,"lon":2.3522}],"metadata":{"timestamp":1687928400000,"correlationId":"some-correlation-id"}}', + ); + }); +});