diff --git a/src/modules/auth/adapters/primaries/auth.controller.ts b/src/modules/auth/adapters/primaries/auth.controller.ts index 161f123..515b62c 100644 --- a/src/modules/auth/adapters/primaries/auth.controller.ts +++ b/src/modules/auth/adapters/primaries/auth.controller.ts @@ -1,6 +1,6 @@ import { Mapper } from '@automapper/core'; import { InjectMapper } from '@automapper/nestjs'; -import { Controller } from '@nestjs/common'; +import { Controller, UsePipes } from '@nestjs/common'; import { CommandBus, QueryBus } from '@nestjs/cqrs'; import { GrpcMethod, RpcException } from '@nestjs/microservices'; import { DatabaseException } from 'src/modules/database/src/exceptions/DatabaseException'; @@ -21,8 +21,15 @@ import { Auth } from '../../domain/entities/auth'; import { Username } from '../../domain/entities/username'; import { ValidateAuthQuery } from '../../queries/validate-auth.query'; import { AuthPresenter } from './auth.presenter'; +import { RpcValidationPipe } from './rpc.validation-pipe'; import { UsernamePresenter } from './username.presenter'; +@UsePipes( + new RpcValidationPipe({ + whitelist: true, + forbidUnknownValues: false, + }), +) @Controller() export class AuthController { constructor( diff --git a/src/modules/auth/adapters/secondaries/auth.messager.ts b/src/modules/auth/adapters/secondaries/auth.messager.ts new file mode 100644 index 0000000..7bf4e2e --- /dev/null +++ b/src/modules/auth/adapters/secondaries/auth.messager.ts @@ -0,0 +1,14 @@ +import { AmqpConnection } from '@golevelup/nestjs-rabbitmq'; +import { Injectable } from '@nestjs/common'; +import { IMessageBroker } from '../../domain/interfaces/message-broker'; + +@Injectable() +export class AuthMessager extends IMessageBroker { + constructor(private readonly _amqpConnection: AmqpConnection) { + super('auth'); + } + + publish(routingKey: string, message: string): void { + this._amqpConnection.publish(this.exchange, routingKey, message); + } +} diff --git a/src/modules/auth/adapters/secondaries/logging.messager.ts b/src/modules/auth/adapters/secondaries/logging.messager.ts new file mode 100644 index 0000000..00988ec --- /dev/null +++ b/src/modules/auth/adapters/secondaries/logging.messager.ts @@ -0,0 +1,14 @@ +import { AmqpConnection } from '@golevelup/nestjs-rabbitmq'; +import { Injectable } from '@nestjs/common'; +import { IMessageBroker } from '../../domain/interfaces/message-broker'; + +@Injectable() +export class LoggingMessager extends IMessageBroker { + constructor(private readonly _amqpConnection: AmqpConnection) { + super('logging'); + } + + publish(routingKey: string, message: string): void { + this._amqpConnection.publish(this.exchange, routingKey, message); + } +} diff --git a/src/modules/auth/auth.module.ts b/src/modules/auth/auth.module.ts index e3e0e1c..38a5720 100644 --- a/src/modules/auth/auth.module.ts +++ b/src/modules/auth/auth.module.ts @@ -28,6 +28,10 @@ import { AuthMessagerController } from './adapters/primaries/auth-messager.contr name: 'user', type: 'topic', }, + { + name: 'logging', + type: 'topic', + }, ], uri: configService.get('RMQ_URI'), enableControllerDiscovery: true, diff --git a/src/modules/auth/domain/interfaces/message-broker.ts b/src/modules/auth/domain/interfaces/message-broker.ts new file mode 100644 index 0000000..594aa43 --- /dev/null +++ b/src/modules/auth/domain/interfaces/message-broker.ts @@ -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; +} diff --git a/src/modules/auth/domain/usecases/add-username.usecase.ts b/src/modules/auth/domain/usecases/add-username.usecase.ts index 2469d51..57752d5 100644 --- a/src/modules/auth/domain/usecases/add-username.usecase.ts +++ b/src/modules/auth/domain/usecases/add-username.usecase.ts @@ -1,11 +1,15 @@ import { CommandHandler } from '@nestjs/cqrs'; +import { LoggingMessager } from '../../adapters/secondaries/logging.messager'; import { UsernameRepository } from '../../adapters/secondaries/username.repository'; import { AddUsernameCommand } from '../../commands/add-username.command'; import { Username } from '../entities/username'; @CommandHandler(AddUsernameCommand) export class AddUsernameUseCase { - constructor(private readonly _usernameRepository: UsernameRepository) {} + constructor( + private readonly _usernameRepository: UsernameRepository, + private readonly _loggingMessager: LoggingMessager, + ) {} async execute(command: AddUsernameCommand): Promise { const { uuid, username, type } = command.addUsernameRequest; @@ -15,8 +19,15 @@ export class AddUsernameUseCase { type, username, }); - } catch (e) { - throw e; + } catch (error) { + this._loggingMessager.publish( + 'auth.username.add.warning', + JSON.stringify({ + command, + error, + }), + ); + throw error; } } } diff --git a/src/modules/auth/domain/usecases/create-auth.usecase.ts b/src/modules/auth/domain/usecases/create-auth.usecase.ts index 978b7be..c4e0a07 100644 --- a/src/modules/auth/domain/usecases/create-auth.usecase.ts +++ b/src/modules/auth/domain/usecases/create-auth.usecase.ts @@ -4,12 +4,14 @@ import { CreateAuthCommand } from '../../commands/create-auth.command'; import { Auth } from '../entities/auth'; import * as bcrypt from 'bcrypt'; import { UsernameRepository } from '../../adapters/secondaries/username.repository'; +import { LoggingMessager } from '../../adapters/secondaries/logging.messager'; @CommandHandler(CreateAuthCommand) export class CreateAuthUseCase { constructor( private readonly _authRepository: AuthRepository, private readonly _usernameRepository: UsernameRepository, + private readonly _loggingMessager: LoggingMessager, ) {} async execute(command: CreateAuthCommand): Promise { @@ -28,8 +30,15 @@ export class CreateAuthUseCase { }); return auth; - } catch (e) { - throw e; + } catch (error) { + this._loggingMessager.publish( + 'auth.create.critical', + JSON.stringify({ + command, + error, + }), + ); + throw error; } } } diff --git a/src/modules/auth/domain/usecases/delete-auth.usecase.ts b/src/modules/auth/domain/usecases/delete-auth.usecase.ts index 4bcbb02..07905ce 100644 --- a/src/modules/auth/domain/usecases/delete-auth.usecase.ts +++ b/src/modules/auth/domain/usecases/delete-auth.usecase.ts @@ -1,5 +1,6 @@ import { CommandHandler } from '@nestjs/cqrs'; import { AuthRepository } from '../../adapters/secondaries/auth.repository'; +import { LoggingMessager } from '../../adapters/secondaries/logging.messager'; import { UsernameRepository } from '../../adapters/secondaries/username.repository'; import { DeleteAuthCommand } from '../../commands/delete-auth.command'; @@ -8,6 +9,7 @@ export class DeleteAuthUseCase { constructor( private readonly _authRepository: AuthRepository, private readonly _usernameRepository: UsernameRepository, + private readonly _loggingMessager: LoggingMessager, ) {} async execute(command: DeleteAuthCommand) { @@ -18,8 +20,15 @@ export class DeleteAuthUseCase { return await this._authRepository.delete({ uuid: command.deleteAuthRequest.uuid, }); - } catch (e) { - throw e; + } catch (error) { + this._loggingMessager.publish( + 'auth.delete.critical', + JSON.stringify({ + command, + error, + }), + ); + throw error; } } } diff --git a/src/modules/auth/domain/usecases/delete-username.usecase.ts b/src/modules/auth/domain/usecases/delete-username.usecase.ts index 8d5250a..27e7800 100644 --- a/src/modules/auth/domain/usecases/delete-username.usecase.ts +++ b/src/modules/auth/domain/usecases/delete-username.usecase.ts @@ -1,11 +1,15 @@ import { UnauthorizedException } from '@nestjs/common'; import { CommandHandler } from '@nestjs/cqrs'; +import { LoggingMessager } from '../../adapters/secondaries/logging.messager'; import { UsernameRepository } from '../../adapters/secondaries/username.repository'; import { DeleteUsernameCommand } from '../../commands/delete-username.command'; @CommandHandler(DeleteUsernameCommand) export class DeleteUsernameUseCase { - constructor(private readonly _usernameRepository: UsernameRepository) {} + constructor( + private readonly _usernameRepository: UsernameRepository, + private readonly _loggingMessager: LoggingMessager, + ) {} async execute(command: DeleteUsernameCommand) { try { @@ -20,8 +24,15 @@ export class DeleteUsernameUseCase { return await this._usernameRepository.delete({ username }); } throw new UnauthorizedException(); - } catch (e) { - throw e; + } catch (error) { + this._loggingMessager.publish( + 'auth.username.delete.warning', + JSON.stringify({ + command, + error, + }), + ); + throw error; } } } diff --git a/src/modules/auth/domain/usecases/update-password.usecase.ts b/src/modules/auth/domain/usecases/update-password.usecase.ts index 41074ba..b8bf22d 100644 --- a/src/modules/auth/domain/usecases/update-password.usecase.ts +++ b/src/modules/auth/domain/usecases/update-password.usecase.ts @@ -3,10 +3,14 @@ import { AuthRepository } from '../../adapters/secondaries/auth.repository'; import { Auth } from '../entities/auth'; import * as bcrypt from 'bcrypt'; import { UpdatePasswordCommand } from '../../commands/update-password.command'; +import { LoggingMessager } from '../../adapters/secondaries/logging.messager'; @CommandHandler(UpdatePasswordCommand) export class UpdatePasswordUseCase { - constructor(private readonly _authRepository: AuthRepository) {} + constructor( + private readonly _authRepository: AuthRepository, + private readonly _loggingMessager: LoggingMessager, + ) {} async execute(command: UpdatePasswordCommand): Promise { const { uuid, password } = command.updatePasswordRequest; @@ -16,8 +20,15 @@ export class UpdatePasswordUseCase { return await this._authRepository.update(uuid, { password: hash, }); - } catch (e) { - throw e; + } catch (error) { + this._loggingMessager.publish( + 'auth.password.update.warning', + JSON.stringify({ + command, + error, + }), + ); + throw error; } } } diff --git a/src/modules/auth/domain/usecases/update-username.usecase.ts b/src/modules/auth/domain/usecases/update-username.usecase.ts index 9eec927..35c51ac 100644 --- a/src/modules/auth/domain/usecases/update-username.usecase.ts +++ b/src/modules/auth/domain/usecases/update-username.usecase.ts @@ -2,6 +2,7 @@ import { Mapper } from '@automapper/core'; import { InjectMapper } from '@automapper/nestjs'; import { BadRequestException } from '@nestjs/common'; import { CommandBus, CommandHandler } from '@nestjs/cqrs'; +import { LoggingMessager } from '../../adapters/secondaries/logging.messager'; import { UsernameRepository } from '../../adapters/secondaries/username.repository'; import { AddUsernameCommand } from '../../commands/add-username.command'; import { UpdateUsernameCommand } from '../../commands/update-username.command'; @@ -15,6 +16,7 @@ export class UpdateUsernameUseCase { private readonly _usernameRepository: UsernameRepository, private readonly _commandBus: CommandBus, @InjectMapper() private readonly _mapper: Mapper, + private readonly _loggingMessager: LoggingMessager, ) {} async execute(command: UpdateUsernameCommand): Promise { @@ -38,8 +40,15 @@ export class UpdateUsernameUseCase { username, }, ); - } catch (e) { - throw e; + } catch (error) { + this._loggingMessager.publish( + 'auth.username.update.warning', + JSON.stringify({ + command, + error, + }), + ); + throw error; } } const addUsernameRequest = this._mapper.map( diff --git a/src/modules/auth/tests/unit/add-username.usecase.spec.ts b/src/modules/auth/tests/unit/add-username.usecase.spec.ts index 6062794..092e830 100644 --- a/src/modules/auth/tests/unit/add-username.usecase.spec.ts +++ b/src/modules/auth/tests/unit/add-username.usecase.spec.ts @@ -8,6 +8,7 @@ import { Type } from '../../domain/dtos/type.enum'; import { AddUsernameRequest } from '../../domain/dtos/add-username.request'; import { AddUsernameCommand } from '../../commands/add-username.command'; import { AddUsernameUseCase } from '../../domain/usecases/add-username.usecase'; +import { LoggingMessager } from '../../adapters/secondaries/logging.messager'; const addUsernameRequest: AddUsernameRequest = { uuid: 'bb281075-1b98-4456-89d6-c643d3044a91', @@ -22,6 +23,10 @@ const mockUsernameRepository = { create: jest.fn().mockResolvedValue(addUsernameRequest), }; +const mockMessager = { + publish: jest.fn().mockImplementation(), +}; + describe('AddUsernameUseCase', () => { let addUsernameUseCase: AddUsernameUseCase; @@ -33,6 +38,10 @@ describe('AddUsernameUseCase', () => { provide: UsernameRepository, useValue: mockUsernameRepository, }, + { + provide: LoggingMessager, + useValue: mockMessager, + }, AddUsernameUseCase, AuthProfile, ], diff --git a/src/modules/auth/tests/unit/create-auth.usecase.spec.ts b/src/modules/auth/tests/unit/create-auth.usecase.spec.ts index f536b9c..e93fb00 100644 --- a/src/modules/auth/tests/unit/create-auth.usecase.spec.ts +++ b/src/modules/auth/tests/unit/create-auth.usecase.spec.ts @@ -9,6 +9,7 @@ import { CreateAuthUseCase } from '../../domain/usecases/create-auth.usecase'; import * as bcrypt from 'bcrypt'; import { UsernameRepository } from '../../adapters/secondaries/username.repository'; import { Type } from '../../domain/dtos/type.enum'; +import { LoggingMessager } from '../../adapters/secondaries/logging.messager'; const newAuthRequest: CreateAuthRequest = { uuid: 'bb281075-1b98-4456-89d6-c643d3044a91', @@ -33,6 +34,10 @@ const mockUsernameRepository = { }), }; +const mockMessager = { + publish: jest.fn().mockImplementation(), +}; + describe('CreateAuthUseCase', () => { let createAuthUseCase: CreateAuthUseCase; @@ -48,6 +53,10 @@ describe('CreateAuthUseCase', () => { provide: UsernameRepository, useValue: mockUsernameRepository, }, + { + provide: LoggingMessager, + useValue: mockMessager, + }, CreateAuthUseCase, ], }).compile(); diff --git a/src/modules/auth/tests/unit/delete-auth.usecase.spec.ts b/src/modules/auth/tests/unit/delete-auth.usecase.spec.ts index c0b5117..9db5ab2 100644 --- a/src/modules/auth/tests/unit/delete-auth.usecase.spec.ts +++ b/src/modules/auth/tests/unit/delete-auth.usecase.spec.ts @@ -2,6 +2,7 @@ import { classes } from '@automapper/classes'; import { AutomapperModule } from '@automapper/nestjs'; import { Test, TestingModule } from '@nestjs/testing'; import { AuthRepository } from '../../adapters/secondaries/auth.repository'; +import { LoggingMessager } from '../../adapters/secondaries/logging.messager'; import { UsernameRepository } from '../../adapters/secondaries/username.repository'; import { DeleteAuthCommand } from '../../commands/delete-auth.command'; import { DeleteAuthRequest } from '../../domain/dtos/delete-auth.request'; @@ -44,6 +45,10 @@ const mockUsernameRepository = { deleteMany: jest.fn().mockResolvedValue(undefined), }; +const mockMessager = { + publish: jest.fn().mockImplementation(), +}; + describe('DeleteAuthUseCase', () => { let deleteAuthUseCase: DeleteAuthUseCase; @@ -59,6 +64,10 @@ describe('DeleteAuthUseCase', () => { provide: UsernameRepository, useValue: mockUsernameRepository, }, + { + provide: LoggingMessager, + useValue: mockMessager, + }, DeleteAuthUseCase, ], }).compile(); diff --git a/src/modules/auth/tests/unit/delete-username.usecase.spec.ts b/src/modules/auth/tests/unit/delete-username.usecase.spec.ts index 3e8cdd1..1e583c8 100644 --- a/src/modules/auth/tests/unit/delete-username.usecase.spec.ts +++ b/src/modules/auth/tests/unit/delete-username.usecase.spec.ts @@ -2,6 +2,7 @@ import { classes } from '@automapper/classes'; import { AutomapperModule } from '@automapper/nestjs'; import { UnauthorizedException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; +import { LoggingMessager } from '../../adapters/secondaries/logging.messager'; import { UsernameRepository } from '../../adapters/secondaries/username.repository'; import { DeleteUsernameCommand } from '../../commands/delete-username.command'; import { DeleteUsernameRequest } from '../../domain/dtos/delete-username.request'; @@ -65,6 +66,10 @@ const mockUsernameRepository = { delete: jest.fn().mockResolvedValue(undefined), }; +const mockMessager = { + publish: jest.fn().mockImplementation(), +}; + describe('DeleteUsernameUseCase', () => { let deleteUsernameUseCase: DeleteUsernameUseCase; @@ -76,6 +81,10 @@ describe('DeleteUsernameUseCase', () => { provide: UsernameRepository, useValue: mockUsernameRepository, }, + { + provide: LoggingMessager, + useValue: mockMessager, + }, DeleteUsernameUseCase, ], }).compile(); diff --git a/src/modules/auth/tests/unit/update-password.usecase.spec.ts b/src/modules/auth/tests/unit/update-password.usecase.spec.ts index bd388c2..b074c5b 100644 --- a/src/modules/auth/tests/unit/update-password.usecase.spec.ts +++ b/src/modules/auth/tests/unit/update-password.usecase.spec.ts @@ -7,6 +7,7 @@ import * as bcrypt from 'bcrypt'; import { UpdatePasswordRequest } from '../../domain/dtos/update-password.request'; import { UpdatePasswordCommand } from '../../commands/update-password.command'; import { UpdatePasswordUseCase } from '../../domain/usecases/update-password.usecase'; +import { LoggingMessager } from '../../adapters/secondaries/logging.messager'; const updatePasswordRequest: UpdatePasswordRequest = { uuid: 'bb281075-1b98-4456-89d6-c643d3044a91', @@ -23,6 +24,10 @@ const mockAuthRepository = { }), }; +const mockMessager = { + publish: jest.fn().mockImplementation(), +}; + describe('UpdatePasswordUseCase', () => { let updatePasswordUseCase: UpdatePasswordUseCase; @@ -34,6 +39,10 @@ describe('UpdatePasswordUseCase', () => { provide: AuthRepository, useValue: mockAuthRepository, }, + { + provide: LoggingMessager, + useValue: mockMessager, + }, UpdatePasswordUseCase, ], }).compile(); diff --git a/src/modules/auth/tests/unit/update-username.usecase.spec.ts b/src/modules/auth/tests/unit/update-username.usecase.spec.ts index a9ddc3a..45e10e7 100644 --- a/src/modules/auth/tests/unit/update-username.usecase.spec.ts +++ b/src/modules/auth/tests/unit/update-username.usecase.spec.ts @@ -10,6 +10,7 @@ import { UpdateUsernameUseCase } from '../../domain/usecases/update-username.use import { CommandBus } from '@nestjs/cqrs'; import { UsernameProfile } from '../../mappers/username.profile'; import { BadRequestException } from '@nestjs/common'; +import { LoggingMessager } from '../../adapters/secondaries/logging.messager'; const existingUsername = { uuid: 'bb281075-1b98-4456-89d6-c643d3044a91', @@ -59,6 +60,10 @@ const mockAddUsernameCommand = { execute: jest.fn().mockResolvedValue(newUsernameRequest), }; +const mockMessager = { + publish: jest.fn().mockImplementation(), +}; + describe('UpdateUsernameUseCase', () => { let updateUsernameUseCase: UpdateUsernameUseCase; @@ -74,6 +79,10 @@ describe('UpdateUsernameUseCase', () => { provide: CommandBus, useValue: mockAddUsernameCommand, }, + { + provide: LoggingMessager, + useValue: mockMessager, + }, UpdateUsernameUseCase, UsernameProfile, ],