diff --git a/src/modules/authentication/adapters/primaries/authentication.controller.ts b/src/modules/authentication/adapters/primaries/authentication.controller.ts index 3fb7032..987c1f0 100644 --- a/src/modules/authentication/adapters/primaries/authentication.controller.ts +++ b/src/modules/authentication/adapters/primaries/authentication.controller.ts @@ -3,7 +3,7 @@ import { InjectMapper } from '@automapper/nestjs'; 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/database.exception'; +import { DatabaseException } from 'src/modules/database/exceptions/database.exception'; import { AddUsernameCommand } from '../../commands/add-username.command'; import { CreateAuthenticationCommand } from '../../commands/create-authentication.command'; import { DeleteAuthenticationCommand } from '../../commands/delete-authentication.command'; diff --git a/src/modules/authentication/adapters/secondaries/authentication.repository.ts b/src/modules/authentication/adapters/secondaries/authentication.repository.ts index 98d1e37..e8fe067 100644 --- a/src/modules/authentication/adapters/secondaries/authentication.repository.ts +++ b/src/modules/authentication/adapters/secondaries/authentication.repository.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; -import { AuthRepository } from '../../../database/src/domain/auth-repository'; +import { AuthRepository } from '../../../database/domain/auth-repository'; import { Authentication } from '../../domain/entities/authentication'; @Injectable() export class AuthenticationRepository extends AuthRepository { - protected _model = 'auth'; + protected model = 'auth'; } diff --git a/src/modules/authentication/adapters/secondaries/messager.ts b/src/modules/authentication/adapters/secondaries/messager.ts index 0ee32e3..afbbcb4 100644 --- a/src/modules/authentication/adapters/secondaries/messager.ts +++ b/src/modules/authentication/adapters/secondaries/messager.ts @@ -6,13 +6,13 @@ import { IMessageBroker } from '../../domain/interfaces/message-broker'; @Injectable() export class Messager extends IMessageBroker { constructor( - private readonly _amqpConnection: AmqpConnection, + private readonly amqpConnection: AmqpConnection, configService: ConfigService, ) { super(configService.get('RMQ_EXCHANGE')); } - publish(routingKey: string, message: string): void { - this._amqpConnection.publish(this.exchange, routingKey, message); - } + publish = (routingKey: string, message: string): void => { + this.amqpConnection.publish(this.exchange, routingKey, message); + }; } diff --git a/src/modules/authentication/adapters/secondaries/username.repository.ts b/src/modules/authentication/adapters/secondaries/username.repository.ts index ec10d25..53a5bbe 100644 --- a/src/modules/authentication/adapters/secondaries/username.repository.ts +++ b/src/modules/authentication/adapters/secondaries/username.repository.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; -import { AuthRepository } from '../../../database/src/domain/auth-repository'; +import { AuthRepository } from '../../../database/domain/auth-repository'; import { Username } from '../../domain/entities/username'; @Injectable() export class UsernameRepository extends AuthRepository { - protected _model = 'username'; + protected model = 'username'; } diff --git a/src/modules/authentication/domain/usecases/add-username.usecase.ts b/src/modules/authentication/domain/usecases/add-username.usecase.ts index e4bf622..d2bd577 100644 --- a/src/modules/authentication/domain/usecases/add-username.usecase.ts +++ b/src/modules/authentication/domain/usecases/add-username.usecase.ts @@ -11,7 +11,7 @@ export class AddUsernameUseCase { private readonly _messager: Messager, ) {} - async execute(command: AddUsernameCommand): Promise { + execute = async (command: AddUsernameCommand): Promise => { const { uuid, username, type } = command.addUsernameRequest; try { return await this._usernameRepository.create({ @@ -29,5 +29,5 @@ export class AddUsernameUseCase { ); throw error; } - } + }; } diff --git a/src/modules/authentication/domain/usecases/create-authentication.usecase.ts b/src/modules/authentication/domain/usecases/create-authentication.usecase.ts index 261ff87..275d148 100644 --- a/src/modules/authentication/domain/usecases/create-authentication.usecase.ts +++ b/src/modules/authentication/domain/usecases/create-authentication.usecase.ts @@ -14,7 +14,9 @@ export class CreateAuthenticationUseCase { private readonly _messager: Messager, ) {} - async execute(command: CreateAuthenticationCommand): Promise { + execute = async ( + command: CreateAuthenticationCommand, + ): Promise => { const { uuid, password, ...username } = command.createAuthenticationRequest; const hash = await bcrypt.hash(password, 10); @@ -40,5 +42,5 @@ export class CreateAuthenticationUseCase { ); throw error; } - } + }; } diff --git a/src/modules/authentication/domain/usecases/delete-authentication.usecase.ts b/src/modules/authentication/domain/usecases/delete-authentication.usecase.ts index d0b3f8b..b8f4c8e 100644 --- a/src/modules/authentication/domain/usecases/delete-authentication.usecase.ts +++ b/src/modules/authentication/domain/usecases/delete-authentication.usecase.ts @@ -3,6 +3,7 @@ import { AuthenticationRepository } from '../../adapters/secondaries/authenticat import { Messager } from '../../adapters/secondaries/messager'; import { UsernameRepository } from '../../adapters/secondaries/username.repository'; import { DeleteAuthenticationCommand } from '../../commands/delete-authentication.command'; +import { Authentication } from '../entities/authentication'; @CommandHandler(DeleteAuthenticationCommand) export class DeleteAuthenticationUseCase { @@ -12,7 +13,9 @@ export class DeleteAuthenticationUseCase { private readonly _messager: Messager, ) {} - async execute(command: DeleteAuthenticationCommand) { + execute = async ( + command: DeleteAuthenticationCommand, + ): Promise => { try { await this._usernameRepository.deleteMany({ uuid: command.deleteAuthenticationRequest.uuid, @@ -30,5 +33,5 @@ export class DeleteAuthenticationUseCase { ); throw error; } - } + }; } diff --git a/src/modules/authentication/domain/usecases/delete-username.usecase.ts b/src/modules/authentication/domain/usecases/delete-username.usecase.ts index 5dbb3fb..e58a914 100644 --- a/src/modules/authentication/domain/usecases/delete-username.usecase.ts +++ b/src/modules/authentication/domain/usecases/delete-username.usecase.ts @@ -11,7 +11,7 @@ export class DeleteUsernameUseCase { private readonly _messager: Messager, ) {} - async execute(command: DeleteUsernameCommand) { + execute = async (command: DeleteUsernameCommand): Promise => { try { const { username } = command.deleteUsernameRequest; const usernameFound = await this._usernameRepository.findOne({ @@ -34,5 +34,5 @@ export class DeleteUsernameUseCase { ); throw error; } - } + }; } diff --git a/src/modules/authentication/domain/usecases/update-password.usecase.ts b/src/modules/authentication/domain/usecases/update-password.usecase.ts index f850ac5..a7e4bb4 100644 --- a/src/modules/authentication/domain/usecases/update-password.usecase.ts +++ b/src/modules/authentication/domain/usecases/update-password.usecase.ts @@ -12,7 +12,7 @@ export class UpdatePasswordUseCase { private readonly _messager: Messager, ) {} - async execute(command: UpdatePasswordCommand): Promise { + execute = async (command: UpdatePasswordCommand): Promise => { const { uuid, password } = command.updatePasswordRequest; const hash = await bcrypt.hash(password, 10); @@ -30,5 +30,5 @@ export class UpdatePasswordUseCase { ); throw error; } - } + }; } diff --git a/src/modules/authentication/domain/usecases/update-username.usecase.ts b/src/modules/authentication/domain/usecases/update-username.usecase.ts index c3d9882..24f93ef 100644 --- a/src/modules/authentication/domain/usecases/update-username.usecase.ts +++ b/src/modules/authentication/domain/usecases/update-username.usecase.ts @@ -19,7 +19,7 @@ export class UpdateUsernameUseCase { private readonly _messager: Messager, ) {} - async execute(command: UpdateUsernameCommand): Promise { + execute = async (command: UpdateUsernameCommand): Promise => { const { uuid, username, type } = command.updateUsernameRequest; if (!username) throw new BadRequestException(); // update username if it exists, otherwise create it @@ -63,5 +63,5 @@ export class UpdateUsernameUseCase { } catch (e) { throw e; } - } + }; } diff --git a/src/modules/authentication/domain/usecases/validate-authentication.usecase.ts b/src/modules/authentication/domain/usecases/validate-authentication.usecase.ts index 98bb3d5..7762938 100644 --- a/src/modules/authentication/domain/usecases/validate-authentication.usecase.ts +++ b/src/modules/authentication/domain/usecases/validate-authentication.usecase.ts @@ -14,9 +14,9 @@ export class ValidateAuthenticationUseCase { private readonly _usernameRepository: UsernameRepository, ) {} - async execute( + execute = async ( validate: ValidateAuthenticationQuery, - ): Promise { + ): Promise => { let username = new Username(); try { username = await this._usernameRepository.findOne({ @@ -37,5 +37,5 @@ export class ValidateAuthenticationUseCase { } catch (e) { throw new UnauthorizedException(); } - } + }; } diff --git a/src/modules/authentication/tests/integration/authentication.repository.spec.ts b/src/modules/authentication/tests/integration/authentication.repository.spec.ts index f0443db..ec29f55 100644 --- a/src/modules/authentication/tests/integration/authentication.repository.spec.ts +++ b/src/modules/authentication/tests/integration/authentication.repository.spec.ts @@ -1,7 +1,7 @@ import { TestingModule, Test } from '@nestjs/testing'; import { DatabaseModule } from '../../../database/database.module'; -import { PrismaService } from '../../../database/src/adapters/secondaries/prisma-service'; -import { DatabaseException } from '../../../database/src/exceptions/database.exception'; +import { PrismaService } from '../../../database/adapters/secondaries/prisma-service'; +import { DatabaseException } from '../../../database/exceptions/database.exception'; import { AuthenticationRepository } from '../../adapters/secondaries/authentication.repository'; import { v4 } from 'uuid'; import * as bcrypt from 'bcrypt'; diff --git a/src/modules/authentication/tests/integration/username.repository.spec.ts b/src/modules/authentication/tests/integration/username.repository.spec.ts index 2d13008..ccb1e3c 100644 --- a/src/modules/authentication/tests/integration/username.repository.spec.ts +++ b/src/modules/authentication/tests/integration/username.repository.spec.ts @@ -1,7 +1,7 @@ import { TestingModule, Test } from '@nestjs/testing'; import { DatabaseModule } from '../../../database/database.module'; -import { PrismaService } from '../../../database/src/adapters/secondaries/prisma-service'; -import { DatabaseException } from '../../../database/src/exceptions/database.exception'; +import { PrismaService } from '../../../database/adapters/secondaries/prisma-service'; +import { DatabaseException } from '../../../database/exceptions/database.exception'; import { v4 } from 'uuid'; import { Type } from '../../domain/dtos/type.enum'; import { UsernameRepository } from '../../adapters/secondaries/username.repository'; diff --git a/src/modules/authentication/tests/unit/validate-authentication.usecase.spec.ts b/src/modules/authentication/tests/unit/validate-authentication.usecase.spec.ts index 0c7e418..2e75726 100644 --- a/src/modules/authentication/tests/unit/validate-authentication.usecase.spec.ts +++ b/src/modules/authentication/tests/unit/validate-authentication.usecase.spec.ts @@ -9,7 +9,7 @@ import { ValidateAuthenticationQuery } from '../../queries/validate-authenticati import { UsernameRepository } from '../../adapters/secondaries/username.repository'; import { Type } from '../../domain/dtos/type.enum'; import { NotFoundException, UnauthorizedException } from '@nestjs/common'; -import { DatabaseException } from '../../../database/src/exceptions/database.exception'; +import { DatabaseException } from '../../../database/exceptions/database.exception'; import { ValidateAuthenticationRequest } from '../../domain/dtos/validate-authentication.request'; const mockAuthenticationRepository = { diff --git a/src/modules/authorization/domain/usecases/decision.usecase.ts b/src/modules/authorization/domain/usecases/decision.usecase.ts index dea61f7..a1799fd 100644 --- a/src/modules/authorization/domain/usecases/decision.usecase.ts +++ b/src/modules/authorization/domain/usecases/decision.usecase.ts @@ -5,13 +5,12 @@ import { Authorization } from '../entities/authorization'; @QueryHandler(DecisionQuery) export class DecisionUseCase { - constructor(private readonly _decisionMaker: OpaDecisionMaker) {} + constructor(private readonly decisionMaker: OpaDecisionMaker) {} - async execute(decisionQuery: DecisionQuery): Promise { - return this._decisionMaker.decide( + execute = (decisionQuery: DecisionQuery): Promise => + this.decisionMaker.decide( decisionQuery.domain, decisionQuery.action, decisionQuery.context, ); - } } diff --git a/src/modules/database/adapters/secondaries/prisma-repository.abstract.ts b/src/modules/database/adapters/secondaries/prisma-repository.abstract.ts new file mode 100644 index 0000000..c62eaf2 --- /dev/null +++ b/src/modules/database/adapters/secondaries/prisma-repository.abstract.ts @@ -0,0 +1,259 @@ +import { Injectable } from '@nestjs/common'; +import { Prisma } from '@prisma/client'; +import { DatabaseException } from '../../exceptions/database.exception'; +import { ICollection } from '../../interfaces/collection.interface'; +import { IRepository } from '../../interfaces/repository.interface'; +import { PrismaService } from './prisma-service'; + +/** + * Child classes MUST redefined _model property with appropriate model name + */ +@Injectable() +export abstract class PrismaRepository implements IRepository { + protected model: string; + + constructor(protected readonly prisma: PrismaService) {} + + findAll = async ( + page = 1, + perPage = 10, + where?: any, + include?: any, + ): Promise> => { + const [data, total] = await this.prisma.$transaction([ + this.prisma[this.model].findMany({ + where, + include, + skip: (page - 1) * perPage, + take: perPage, + }), + this.prisma[this.model].count({ + where, + }), + ]); + return Promise.resolve({ + data, + total, + }); + }; + + findOneByUuid = async (uuid: string): Promise => { + try { + const entity = await this.prisma[this.model].findUnique({ + where: { uuid }, + }); + + return entity; + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError) { + throw new DatabaseException( + Prisma.PrismaClientKnownRequestError.name, + e.code, + e.message, + ); + } else { + throw new DatabaseException(); + } + } + }; + + findOne = async (where: any, include?: any): Promise => { + try { + const entity = await this.prisma[this.model].findFirst({ + where: where, + include: include, + }); + + return entity; + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError) { + throw new DatabaseException( + Prisma.PrismaClientKnownRequestError.name, + e.code, + ); + } else { + throw new DatabaseException(); + } + } + }; + + // TODO : using any is not good, but needed for nested entities + // TODO : Refactor for good clean architecture ? + create = async (entity: Partial | any, include?: any): Promise => { + try { + const res = await this.prisma[this.model].create({ + data: entity, + include: include, + }); + + return res; + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError) { + throw new DatabaseException( + Prisma.PrismaClientKnownRequestError.name, + e.code, + e.message, + ); + } else { + throw new DatabaseException(); + } + } + }; + + update = async (uuid: string, entity: Partial): Promise => { + try { + const updatedEntity = await this.prisma[this.model].update({ + where: { uuid }, + data: entity, + }); + return updatedEntity; + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError) { + throw new DatabaseException( + Prisma.PrismaClientKnownRequestError.name, + e.code, + e.message, + ); + } else { + throw new DatabaseException(); + } + } + }; + + updateWhere = async ( + where: any, + entity: Partial | any, + include?: any, + ): Promise => { + try { + const updatedEntity = await this.prisma[this.model].update({ + where: where, + data: entity, + include: include, + }); + + return updatedEntity; + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError) { + throw new DatabaseException( + Prisma.PrismaClientKnownRequestError.name, + e.code, + e.message, + ); + } else { + throw new DatabaseException(); + } + } + }; + + delete = async (uuid: string): Promise => { + try { + const entity = await this.prisma[this.model].delete({ + where: { uuid }, + }); + + return entity; + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError) { + throw new DatabaseException( + Prisma.PrismaClientKnownRequestError.name, + e.code, + e.message, + ); + } else { + throw new DatabaseException(); + } + } + }; + + deleteMany = async (where: any): Promise => { + try { + const entity = await this.prisma[this.model].deleteMany({ + where: where, + }); + + return entity; + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError) { + throw new DatabaseException( + Prisma.PrismaClientKnownRequestError.name, + e.code, + e.message, + ); + } else { + throw new DatabaseException(); + } + } + }; + + findAllByQuery = async ( + include: string[], + where: string[], + ): Promise> => { + const query = `SELECT ${include.join(',')} FROM ${ + this.model + } WHERE ${where.join(' AND ')}`; + const data: T[] = await this.prisma.$queryRawUnsafe(query); + return Promise.resolve({ + data, + total: data.length, + }); + }; + + createWithFields = async (fields: object): Promise => { + try { + const command = `INSERT INTO ${this.model} ("${Object.keys(fields).join( + '","', + )}") VALUES (${Object.values(fields).join(',')})`; + return await this.prisma.$executeRawUnsafe(command); + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError) { + throw new DatabaseException( + Prisma.PrismaClientKnownRequestError.name, + e.code, + e.message, + ); + } else { + throw new DatabaseException(); + } + } + }; + + updateWithFields = async (uuid: string, entity: object): Promise => { + entity['"updatedAt"'] = `to_timestamp(${Date.now()} / 1000.0)`; + const values = Object.keys(entity).map((key) => `${key} = ${entity[key]}`); + try { + const command = `UPDATE ${this.model} SET ${values.join( + ', ', + )} WHERE uuid = '${uuid}'`; + return await this.prisma.$executeRawUnsafe(command); + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError) { + throw new DatabaseException( + Prisma.PrismaClientKnownRequestError.name, + e.code, + e.message, + ); + } else { + throw new DatabaseException(); + } + } + }; + + healthCheck = async (): Promise => { + try { + await this.prisma.$queryRaw`SELECT 1`; + return true; + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError) { + throw new DatabaseException( + Prisma.PrismaClientKnownRequestError.name, + e.code, + e.message, + ); + } else { + throw new DatabaseException(); + } + } + }; +} diff --git a/src/modules/database/src/adapters/secondaries/prisma-service.ts b/src/modules/database/adapters/secondaries/prisma-service.ts similarity index 100% rename from src/modules/database/src/adapters/secondaries/prisma-service.ts rename to src/modules/database/adapters/secondaries/prisma-service.ts diff --git a/src/modules/database/database.module.ts b/src/modules/database/database.module.ts index 12fcc32..2240bdb 100644 --- a/src/modules/database/database.module.ts +++ b/src/modules/database/database.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { AuthenticationRepository } from '../authentication/adapters/secondaries/authentication.repository'; import { UsernameRepository } from '../authentication/adapters/secondaries/username.repository'; -import { PrismaService } from './src/adapters/secondaries/prisma-service'; +import { PrismaService } from './adapters/secondaries/prisma-service'; @Module({ providers: [PrismaService, AuthenticationRepository, UsernameRepository], diff --git a/src/modules/database/src/domain/auth-repository.ts b/src/modules/database/domain/auth-repository.ts similarity index 100% rename from src/modules/database/src/domain/auth-repository.ts rename to src/modules/database/domain/auth-repository.ts diff --git a/src/modules/database/src/exceptions/database.exception.ts b/src/modules/database/exceptions/database.exception.ts similarity index 100% rename from src/modules/database/src/exceptions/database.exception.ts rename to src/modules/database/exceptions/database.exception.ts diff --git a/src/modules/database/src/interfaces/collection.interface.ts b/src/modules/database/interfaces/collection.interface.ts similarity index 100% rename from src/modules/database/src/interfaces/collection.interface.ts rename to src/modules/database/interfaces/collection.interface.ts diff --git a/src/modules/database/src/interfaces/repository.interface.ts b/src/modules/database/interfaces/repository.interface.ts similarity index 100% rename from src/modules/database/src/interfaces/repository.interface.ts rename to src/modules/database/interfaces/repository.interface.ts diff --git a/src/modules/database/src/adapters/secondaries/prisma-repository.abstract.ts b/src/modules/database/src/adapters/secondaries/prisma-repository.abstract.ts deleted file mode 100644 index 8cf5723..0000000 --- a/src/modules/database/src/adapters/secondaries/prisma-repository.abstract.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { PrismaClientKnownRequestError } from '@prisma/client/runtime'; -import { DatabaseException } from '../../exceptions/database.exception'; -import { ICollection } from '../../interfaces/collection.interface'; -import { IRepository } from '../../interfaces/repository.interface'; -import { PrismaService } from './prisma-service'; - -/** - * Child classes MUST redefined _model property with appropriate model name - */ -@Injectable() -export abstract class PrismaRepository implements IRepository { - protected _model: string; - - constructor(protected readonly _prisma: PrismaService) {} - - async findAll( - page = 1, - perPage = 10, - where?: any, - include?: any, - ): Promise> { - const [data, total] = await this._prisma.$transaction([ - this._prisma[this._model].findMany({ - where, - include, - skip: (page - 1) * perPage, - take: perPage, - }), - this._prisma[this._model].count({ - where, - }), - ]); - return Promise.resolve({ - data, - total, - }); - } - - async findOneByUuid(uuid: string): Promise { - try { - const entity = await this._prisma[this._model].findUnique({ - where: { uuid }, - }); - - return entity; - } catch (e) { - if (e instanceof PrismaClientKnownRequestError) { - throw new DatabaseException( - PrismaClientKnownRequestError.name, - e.code, - e.message, - ); - } else { - throw new DatabaseException(); - } - } - } - - async findOne(where: any, include?: any): Promise { - try { - const entity = await this._prisma[this._model].findFirst({ - where: where, - include: include, - }); - - return entity; - } catch (e) { - if (e instanceof PrismaClientKnownRequestError) { - throw new DatabaseException(PrismaClientKnownRequestError.name, e.code); - } else { - throw new DatabaseException(); - } - } - } - - async create(entity: Partial | any, include?: any): Promise { - try { - const res = await this._prisma[this._model].create({ - data: entity, - include: include, - }); - - return res; - } catch (e) { - if (e instanceof PrismaClientKnownRequestError) { - throw new DatabaseException( - PrismaClientKnownRequestError.name, - e.code, - e.message, - ); - } else { - throw new DatabaseException(); - } - } - } - - async update(uuid: string, entity: Partial): Promise { - try { - const updatedEntity = await this._prisma[this._model].update({ - where: { uuid }, - data: entity, - }); - return updatedEntity; - } catch (e) { - if (e instanceof PrismaClientKnownRequestError) { - throw new DatabaseException( - PrismaClientKnownRequestError.name, - e.code, - e.message, - ); - } else { - throw new DatabaseException(); - } - } - } - - async updateWhere( - where: any, - entity: Partial | any, - include?: any, - ): Promise { - try { - const updatedEntity = await this._prisma[this._model].update({ - where: where, - data: entity, - include: include, - }); - - return updatedEntity; - } catch (e) { - if (e instanceof PrismaClientKnownRequestError) { - throw new DatabaseException( - PrismaClientKnownRequestError.name, - e.code, - e.message, - ); - } else { - throw new DatabaseException(); - } - } - } - - async delete(uuid: string): Promise { - try { - const entity = await this._prisma[this._model].delete({ - where: { uuid }, - }); - - return entity; - } catch (e) { - if (e instanceof PrismaClientKnownRequestError) { - throw new DatabaseException( - PrismaClientKnownRequestError.name, - e.code, - e.message, - ); - } else { - throw new DatabaseException(); - } - } - } - - async deleteMany(where: any): Promise { - try { - const entity = await this._prisma[this._model].deleteMany({ - where: where, - }); - - return entity; - } catch (e) { - if (e instanceof PrismaClientKnownRequestError) { - throw new DatabaseException( - PrismaClientKnownRequestError.name, - e.code, - e.message, - ); - } else { - throw new DatabaseException(); - } - } - } - - async healthCheck(): Promise { - try { - await this._prisma.$queryRaw`SELECT 1`; - return true; - } catch (e) { - if (e instanceof PrismaClientKnownRequestError) { - throw new DatabaseException( - PrismaClientKnownRequestError.name, - e.code, - e.message, - ); - } else { - throw new DatabaseException(); - } - } - } -} diff --git a/src/modules/database/tests/unit/prisma-repository.spec.ts b/src/modules/database/tests/unit/prisma-repository.spec.ts index 3e2be4a..eb3bad0 100644 --- a/src/modules/database/tests/unit/prisma-repository.spec.ts +++ b/src/modules/database/tests/unit/prisma-repository.spec.ts @@ -1,9 +1,9 @@ import { Injectable } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { PrismaService } from '../../src/adapters/secondaries/prisma-service'; -import { PrismaRepository } from '../../src/adapters/secondaries/prisma-repository.abstract'; -import { DatabaseException } from '../../src/exceptions/database.exception'; -import { PrismaClientKnownRequestError } from '@prisma/client/runtime'; +import { PrismaService } from '../../adapters/secondaries/prisma-service'; +import { PrismaRepository } from '../../adapters/secondaries/prisma-repository.abstract'; +import { DatabaseException } from '../../exceptions/database.exception'; +import { Prisma } from '@prisma/client'; class FakeEntity { uuid?: string; @@ -41,7 +41,7 @@ Array.from({ length: 10 }).forEach(() => { @Injectable() class FakePrismaRepository extends PrismaRepository { - protected _model = 'fake'; + protected model = 'fake'; } class FakePrismaService extends PrismaService { @@ -57,10 +57,40 @@ const mockPrismaService = { return Promise.resolve([fakeEntities, fakeEntities.length]); }), + // eslint-disable-next-line @typescript-eslint/no-unused-vars + $queryRawUnsafe: jest.fn().mockImplementation((query?: string) => { + return Promise.resolve(fakeEntities); + }), + $executeRawUnsafe: jest + .fn() + .mockResolvedValueOnce(fakeEntityCreated) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + .mockImplementationOnce((fields: object) => { + throw new Prisma.PrismaClientKnownRequestError('unknown request', { + code: 'code', + clientVersion: 'version', + }); + }) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + .mockImplementationOnce((fields: object) => { + throw new Error('an unknown error'); + }) + .mockResolvedValueOnce(fakeEntityCreated) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + .mockImplementationOnce((fields: object) => { + throw new Prisma.PrismaClientKnownRequestError('unknown request', { + code: 'code', + clientVersion: 'version', + }); + }) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + .mockImplementationOnce((fields: object) => { + throw new Error('an unknown error'); + }), $queryRaw: jest .fn() .mockImplementationOnce(() => { - throw new PrismaClientKnownRequestError('unknown request', { + throw new Prisma.PrismaClientKnownRequestError('unknown request', { code: 'code', clientVersion: 'version', }); @@ -69,7 +99,7 @@ const mockPrismaService = { return true; }) .mockImplementation(() => { - throw new PrismaClientKnownRequestError('Database unavailable', { + throw new Prisma.PrismaClientKnownRequestError('Database unavailable', { code: 'code', clientVersion: 'version', }); @@ -80,7 +110,7 @@ const mockPrismaService = { .mockResolvedValueOnce(fakeEntityCreated) // eslint-disable-next-line @typescript-eslint/no-unused-vars .mockImplementationOnce((params?: any) => { - throw new PrismaClientKnownRequestError('unknown request', { + throw new Prisma.PrismaClientKnownRequestError('unknown request', { code: 'code', clientVersion: 'version', }); @@ -109,7 +139,7 @@ const mockPrismaService = { } if (!entity && params?.where?.uuid == 'unknown') { - throw new PrismaClientKnownRequestError('unknown request', { + throw new Prisma.PrismaClientKnownRequestError('unknown request', { code: 'code', clientVersion: 'version', }); @@ -131,7 +161,7 @@ const mockPrismaService = { }) // eslint-disable-next-line @typescript-eslint/no-unused-vars .mockImplementationOnce((params?: any) => { - throw new PrismaClientKnownRequestError('unknown request', { + throw new Prisma.PrismaClientKnownRequestError('unknown request', { code: 'code', clientVersion: 'version', }); @@ -145,14 +175,14 @@ const mockPrismaService = { .fn() // eslint-disable-next-line @typescript-eslint/no-unused-vars .mockImplementationOnce((params?: any) => { - throw new PrismaClientKnownRequestError('unknown request', { + throw new Prisma.PrismaClientKnownRequestError('unknown request', { code: 'code', clientVersion: 'version', }); }) // eslint-disable-next-line @typescript-eslint/no-unused-vars .mockImplementationOnce((params?: any) => { - throw new PrismaClientKnownRequestError('unknown request', { + throw new Prisma.PrismaClientKnownRequestError('unknown request', { code: 'code', clientVersion: 'version', }); @@ -182,7 +212,7 @@ const mockPrismaService = { .fn() // eslint-disable-next-line @typescript-eslint/no-unused-vars .mockImplementationOnce((params?: any) => { - throw new PrismaClientKnownRequestError('unknown request', { + throw new Prisma.PrismaClientKnownRequestError('unknown request', { code: 'code', clientVersion: 'version', }); @@ -206,7 +236,7 @@ const mockPrismaService = { .fn() // eslint-disable-next-line @typescript-eslint/no-unused-vars .mockImplementationOnce((params?: any) => { - throw new PrismaClientKnownRequestError('unknown request', { + throw new Prisma.PrismaClientKnownRequestError('unknown request', { code: 'code', clientVersion: 'version', }); @@ -440,6 +470,86 @@ describe('PrismaRepository', () => { }); }); + describe('findAllByquery', () => { + it('should return an array of entities', async () => { + const entities = await fakeRepository.findAllByQuery( + ['uuid', 'name'], + ['name is not null'], + ); + expect(entities).toStrictEqual({ + data: fakeEntities, + total: fakeEntities.length, + }); + }); + }); + + describe('createWithFields', () => { + it('should create an entity', async () => { + jest.spyOn(prisma, '$queryRawUnsafe'); + + const newEntity = await fakeRepository.createWithFields({ + uuid: '804319b3-a09b-4491-9f82-7976bfce0aff', + name: 'my-name', + }); + expect(newEntity).toBe(fakeEntityCreated); + expect(prisma.$queryRawUnsafe).toHaveBeenCalledTimes(1); + }); + + it('should throw a DatabaseException for client error', async () => { + await expect( + fakeRepository.createWithFields({ + uuid: '804319b3-a09b-4491-9f82-7976bfce0aff', + name: 'my-name', + }), + ).rejects.toBeInstanceOf(DatabaseException); + }); + + it('should throw a DatabaseException if uuid is not found', async () => { + await expect( + fakeRepository.createWithFields({ + name: 'my-name', + }), + ).rejects.toBeInstanceOf(DatabaseException); + }); + }); + + describe('updateWithFields', () => { + it('should update an entity', async () => { + jest.spyOn(prisma, '$queryRawUnsafe'); + + const updatedEntity = await fakeRepository.updateWithFields( + '804319b3-a09b-4491-9f82-7976bfce0aff', + { + name: 'my-name', + }, + ); + expect(updatedEntity).toBe(fakeEntityCreated); + expect(prisma.$queryRawUnsafe).toHaveBeenCalledTimes(1); + }); + + it('should throw a DatabaseException for client error', async () => { + await expect( + fakeRepository.updateWithFields( + '804319b3-a09b-4491-9f82-7976bfce0aff', + { + name: 'my-name', + }, + ), + ).rejects.toBeInstanceOf(DatabaseException); + }); + + it('should throw a DatabaseException if uuid is not found', async () => { + await expect( + fakeRepository.updateWithFields( + '804319b3-a09b-4491-9f82-7976bfce0aff', + { + name: 'my-name', + }, + ), + ).rejects.toBeInstanceOf(DatabaseException); + }); + }); + describe('healthCheck', () => { it('should throw a DatabaseException for client error', async () => { await expect(fakeRepository.healthCheck()).rejects.toBeInstanceOf( diff --git a/src/modules/health/adapters/primaries/health.controller.ts b/src/modules/health/adapters/primaries/health.controller.ts index 8f6967e..caf3cf3 100644 --- a/src/modules/health/adapters/primaries/health.controller.ts +++ b/src/modules/health/adapters/primaries/health.controller.ts @@ -11,21 +11,21 @@ import { PrismaHealthIndicatorUseCase } from '../../domain/usecases/prisma.healt @Controller('health') export class HealthController { constructor( - private readonly _prismaHealthIndicatorUseCase: PrismaHealthIndicatorUseCase, - private _healthCheckService: HealthCheckService, - private _messager: Messager, + 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'), + return await this.healthCheckService.check([ + async () => this.prismaHealthIndicatorUseCase.isHealthy('prisma'), ]); } catch (error) { const healthCheckResult: HealthCheckResult = error.response; - this._messager.publish( + this.messager.publish( 'logging.auth.health.crit', JSON.stringify(healthCheckResult.error), ); diff --git a/src/modules/health/adapters/secondaries/messager.ts b/src/modules/health/adapters/secondaries/messager.ts index 0725261..cd7e7ef 100644 --- a/src/modules/health/adapters/secondaries/messager.ts +++ b/src/modules/health/adapters/secondaries/messager.ts @@ -6,13 +6,13 @@ import { IMessageBroker } from './message-broker'; @Injectable() export class Messager extends IMessageBroker { constructor( - private readonly _amqpConnection: AmqpConnection, + private readonly amqpConnection: AmqpConnection, configService: ConfigService, ) { super(configService.get('RMQ_EXCHANGE')); } - publish(routingKey: string, message: string): void { - this._amqpConnection.publish(this.exchange, routingKey, message); - } + publish = (routingKey: string, message: string): void => { + this.amqpConnection.publish(this.exchange, routingKey, message); + }; } diff --git a/src/modules/health/domain/usecases/prisma.health-indicator.usecase.ts b/src/modules/health/domain/usecases/prisma.health-indicator.usecase.ts index 0746ee1..35b4b32 100644 --- a/src/modules/health/domain/usecases/prisma.health-indicator.usecase.ts +++ b/src/modules/health/domain/usecases/prisma.health-indicator.usecase.ts @@ -8,18 +8,18 @@ import { AuthenticationRepository } from '../../../authentication/adapters/secon @Injectable() export class PrismaHealthIndicatorUseCase extends HealthIndicator { - constructor(private readonly _repository: AuthenticationRepository) { + constructor(private readonly repository: AuthenticationRepository) { super(); } - async isHealthy(key: string): Promise { + isHealthy = async (key: string): Promise => { try { - await this._repository.healthCheck(); + await this.repository.healthCheck(); return this.getStatus(key, true); } catch (e) { throw new HealthCheckError('Prisma', { prisma: e.message, }); } - } + }; }