update packages, refactor

This commit is contained in:
sbriat 2023-10-17 14:48:05 +02:00
parent 54c0718a5d
commit 79f42ae192
38 changed files with 3616 additions and 1977 deletions

5198
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -30,57 +30,57 @@
"migrate:deploy": "npx prisma migrate deploy" "migrate:deploy": "npx prisma migrate deploy"
}, },
"dependencies": { "dependencies": {
"@golevelup/nestjs-rabbitmq": "^3.4.0", "@golevelup/nestjs-rabbitmq": "^4.0.0",
"@grpc/grpc-js": "^1.8.0", "@grpc/grpc-js": "^1.9.5",
"@grpc/proto-loader": "^0.7.4", "@grpc/proto-loader": "^0.7.10",
"@mobicoop/ddd-library": "^1.0.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": "^1.0.1", "@nestjs/axios": "^3.0.0",
"@nestjs/common": "^9.0.0", "@nestjs/common": "^10.2.7",
"@nestjs/config": "^2.2.0", "@nestjs/config": "^3.1.1",
"@nestjs/core": "^9.0.0", "@nestjs/core": "^10.2.7",
"@nestjs/cqrs": "^9.0.1", "@nestjs/cqrs": "^10.2.6",
"@nestjs/event-emitter": "^2.0.0", "@nestjs/event-emitter": "^2.0.2",
"@nestjs/microservices": "^9.2.1", "@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.7.1", "@prisma/client": "^5.4.2",
"axios": "^1.2.2", "axios": "^1.5.1",
"bcrypt": "^5.1.0", "bcrypt": "^5.1.1",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.0", "class-validator": "^0.14.0",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2", "rimraf": "^5.0.5",
"rxjs": "^7.2.0" "rxjs": "^7.8.1"
}, },
"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/bcrypt": "^5.0.0", "@types/bcrypt": "^5.0.0",
"@types/express": "^4.17.13", "@types/express": "^4.17.19",
"@types/jest": "28.1.8", "@types/jest": "29.5.5",
"@types/node": "^16.0.0", "@types/node": "^20.8.6",
"@types/supertest": "^2.0.11", "@types/supertest": "^2.0.14",
"@types/uuid": "^9.0.0", "@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": "28.1.3", "jest": "29.7.0",
"prettier": "^2.3.2", "prettier": "^3.0.3",
"prisma": "^4.7.1", "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": "28.0.8", "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.1.0", "tsconfig-paths": "4.2.0",
"typescript": "^4.7.4", "typescript": "^5.2.2",
"uuid": "^9.0.0" "uuid": "^9.0.1"
}, },
"jest": { "jest": {
"moduleFileExtensions": [ "moduleFileExtensions": [
@ -91,6 +91,7 @@
"modulePathIgnorePatterns": [ "modulePathIgnorePatterns": [
".module.ts", ".module.ts",
".dto.ts", ".dto.ts",
".constants.ts",
".di-tokens.ts", ".di-tokens.ts",
".response.ts", ".response.ts",
".port.ts", ".port.ts",
@ -108,6 +109,7 @@
"coveragePathIgnorePatterns": [ "coveragePathIgnorePatterns": [
".module.ts", ".module.ts",
".dto.ts", ".dto.ts",
".constants.ts",
".di-tokens.ts", ".di-tokens.ts",
".response.ts", ".response.ts",
".port.ts", ".port.ts",

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

@ -0,0 +1,22 @@
// service
export const SERVICE_NAME = 'auth';
// grpc
export const GRPC_AUTHENTICATION_PACKAGE_NAME = 'authentication';
export const GRPC_AUTHORIZATION_PACKAGE_NAME = 'authorization';
export const GRPC_AUTHENTICATION_SERVICE_NAME = 'AuthenticationService';
export const GRPC_AUTHORIZATION_SERVICE_NAME = 'AuthorizationService';
// messaging
export const USER_UPDATED_MESSAGE_HANDLER = 'userUpdated';
export const USER_UPDATED_ROUTING_KEY = 'user.updated';
export const USER_UPDATED_QUEUE = 'auth-user-updated';
export const USER_DELETED_MESSAGE_HANDLER = 'userDeleted';
export const USER_DELETED_ROUTING_KEY = 'user.deleted';
export const USER_DELETED_QUEUE = 'auth-user-deleted';
// health
export const GRPC_HEALTH_PACKAGE_NAME = 'health';
export const HEALTH_AUTHENTICATION_REPOSITORY = 'AuthenticationRepository';
export const HEALTH_USERNAME_REPOSITORY = 'UsernameRepository';
export const HEALTH_CRITICAL_LOGGING_KEY = 'logging.auth.health.crit';

View File

@ -15,6 +15,12 @@ import {
import { MESSAGE_PUBLISHER } from './modules/messager/messager.di-tokens'; import { MESSAGE_PUBLISHER } from './modules/messager/messager.di-tokens';
import { MessagePublisherPort } from '@mobicoop/ddd-library'; import { MessagePublisherPort } from '@mobicoop/ddd-library';
import { MessagerModule } from './modules/messager/messager.module'; import { MessagerModule } from './modules/messager/messager.module';
import {
HEALTH_AUTHENTICATION_REPOSITORY,
HEALTH_CRITICAL_LOGGING_KEY,
HEALTH_USERNAME_REPOSITORY,
SERVICE_NAME,
} from './app.constants';
@Module({ @Module({
imports: [ imports: [
@ -32,15 +38,15 @@ import { MessagerModule } from './modules/messager/messager.module';
usernameRepository: HealthRepositoryPort, usernameRepository: HealthRepositoryPort,
messagePublisher: MessagePublisherPort, messagePublisher: MessagePublisherPort,
): Promise<HealthModuleOptions> => ({ ): Promise<HealthModuleOptions> => ({
serviceName: 'auth', serviceName: SERVICE_NAME,
criticalLoggingKey: 'logging.auth.health.crit', criticalLoggingKey: HEALTH_CRITICAL_LOGGING_KEY,
checkRepositories: [ checkRepositories: [
{ {
name: 'AuthenticationRepository', name: HEALTH_AUTHENTICATION_REPOSITORY,
repository: authenticationRepository, repository: authenticationRepository,
}, },
{ {
name: 'UsernameRepository', name: HEALTH_USERNAME_REPOSITORY,
repository: usernameRepository, repository: usernameRepository,
}, },
], ],

View File

@ -2,6 +2,11 @@ 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_AUTHENTICATION_PACKAGE_NAME,
GRPC_AUTHORIZATION_PACKAGE_NAME,
GRPC_HEALTH_PACKAGE_NAME,
} from './app.constants';
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule);
@ -11,7 +16,11 @@ async function bootstrap() {
app.connectMicroservice<MicroserviceOptions>({ app.connectMicroservice<MicroserviceOptions>({
transport: Transport.GRPC, transport: Transport.GRPC,
options: { options: {
package: ['authentication', 'authorization', 'health'], package: [
GRPC_AUTHENTICATION_PACKAGE_NAME,
GRPC_AUTHORIZATION_PACKAGE_NAME,
GRPC_HEALTH_PACKAGE_NAME,
],
protoPath: [ protoPath: [
join( join(
__dirname, __dirname,
@ -23,11 +32,11 @@ async function bootstrap() {
), ),
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 },
}, },
}); });
await app.startAllMicroservices(); await app.startAllMicroservices();
await app.listen(process.env.HEALTH_SERVICE_PORT); await app.listen(process.env.HEALTH_SERVICE_PORT as unknown as number);
} }
bootstrap(); bootstrap();

View File

@ -31,14 +31,12 @@ export class AuthenticationMapper
const record: AuthenticationWriteModel = { const record: AuthenticationWriteModel = {
uuid: copy.id, uuid: copy.id,
password: copy.password, password: copy.password,
usernames: copy.usernames usernames: {
? { create: copy.usernames.map((username: UsernameProps) => ({
create: copy.usernames.map((username: UsernameProps) => ({ username: username.name,
username: username.name, type: username.type,
type: username.type, })),
})), },
}
: undefined,
}; };
return record; return record;
}; };
@ -54,7 +52,7 @@ export class AuthenticationMapper
usernames: record.usernames?.map((username: UsernameModel) => ({ usernames: record.usernames?.map((username: UsernameModel) => ({
userId: record.uuid, userId: record.uuid,
name: username.username, name: username.username,
type: Type[username.type], type: username.type as Type,
})), })),
}, },
}); });

View File

@ -13,6 +13,7 @@ import {
AuthenticationAlreadyExistsException, AuthenticationAlreadyExistsException,
UsernameAlreadyExistsException, UsernameAlreadyExistsException,
} from '@modules/authentication/core/domain/authentication.errors'; } from '@modules/authentication/core/domain/authentication.errors';
import { Username } from '../../types/username';
@CommandHandler(CreateAuthenticationCommand) @CommandHandler(CreateAuthenticationCommand)
export class CreateAuthenticationService implements ICommandHandler { export class CreateAuthenticationService implements ICommandHandler {
@ -26,7 +27,11 @@ export class CreateAuthenticationService implements ICommandHandler {
await AuthenticationEntity.create({ await AuthenticationEntity.create({
userId: command.userId, userId: command.userId,
password: command.password, password: command.password,
usernames: command.usernames, usernames: command.usernames.map((username: Username) => ({
name: username.name,
type: username.type,
userId: command.userId,
})),
}); });
try { try {
await this.authenticationRepository.insert(authentication); await this.authenticationRepository.insert(authentication);

View File

@ -18,9 +18,8 @@ export class DeleteAuthenticationService implements ICommandHandler {
usernames: true, usernames: true,
}); });
authentication.delete(); authentication.delete();
const isDeleted: boolean = await this.authenticationRepository.delete( const isDeleted: boolean =
authentication, await this.authenticationRepository.delete(authentication);
);
return isDeleted; return isDeleted;
} }
} }

View File

@ -24,9 +24,8 @@ export class DeleteUsernameService implements ICommandHandler {
'Authentication must have at least one username', 'Authentication must have at least one username',
); );
username.delete(); username.delete();
const isDeleted: boolean = await this.usernameRepository.deleteUsername( const isDeleted: boolean =
username, await this.usernameRepository.deleteUsername(username);
);
return isDeleted; return isDeleted;
} }
} }

View File

@ -1,15 +1,15 @@
import { UsernameProps } from './username.types'; import { NameProps } from './username.types';
// All properties that an Authentication has // All properties that an Authentication has
export interface AuthenticationProps { export interface AuthenticationProps {
userId: string; userId: string;
password: string; password: string;
usernames: UsernameProps[]; usernames: NameProps[];
} }
// Properties that are needed for an Authentication creation // Properties that are needed for an Authentication creation
export interface CreateAuthenticationProps { export interface CreateAuthenticationProps {
userId: string; userId: string;
password: string; password: string;
usernames: UsernameProps[]; usernames: NameProps[];
} }

View File

@ -18,8 +18,8 @@ export class UsernameEntity extends AggregateRoot<UsernameProps> {
const username = new UsernameEntity({ const username = new UsernameEntity({
id: props.name, id: props.name,
props: { props: {
name: props.name,
userId: props.userId, userId: props.userId,
name: props.name,
type: props.type, type: props.type,
}, },
}); });

View File

@ -1,12 +1,13 @@
export interface UsernameProps { export interface UsernameProps extends NameProps {
name: string; userId: string;
userId?: string;
type: Type;
} }
export interface CreateUsernameProps { export interface CreateUsernameProps extends NameProps {
name: string;
userId: string; userId: string;
}
export interface NameProps {
name: string;
type: Type; type: Type;
} }

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

@ -19,8 +19,8 @@ type UsernameBaseModel = {
}; };
export type UsernameReadModel = UsernameBaseModel & { export type UsernameReadModel = UsernameBaseModel & {
createdAt?: Date; createdAt: Date;
updatedAt?: Date; updatedAt: Date;
}; };
export type UsernameWriteModel = UsernameBaseModel; export type UsernameWriteModel = UsernameBaseModel;

View File

@ -10,6 +10,7 @@ import { GrpcMethod, RpcException } from '@nestjs/microservices';
import { UsernameAlreadyExistsException } from '@modules/authentication/core/domain/authentication.errors'; import { UsernameAlreadyExistsException } from '@modules/authentication/core/domain/authentication.errors';
import { AddUsernameRequestDto } from './dtos/add-username.request.dto'; import { AddUsernameRequestDto } from './dtos/add-username.request.dto';
import { AddUsernameCommand } from '@modules/authentication/core/application/commands/add-username/add-username.command'; import { AddUsernameCommand } from '@modules/authentication/core/application/commands/add-username/add-username.command';
import { GRPC_AUTHENTICATION_SERVICE_NAME } from '@src/app.constants';
@UsePipes( @UsePipes(
new RpcValidationPipe({ new RpcValidationPipe({
@ -21,7 +22,7 @@ import { AddUsernameCommand } from '@modules/authentication/core/application/com
export class AddUsernameGrpcController { export class AddUsernameGrpcController {
constructor(private readonly commandBus: CommandBus) {} constructor(private readonly commandBus: CommandBus) {}
@GrpcMethod('AuthenticationService', 'AddUsername') @GrpcMethod(GRPC_AUTHENTICATION_SERVICE_NAME, 'AddUsername')
async addUsername(data: AddUsernameRequestDto): Promise<IdResponse> { async addUsername(data: AddUsernameRequestDto): Promise<IdResponse> {
try { try {
const aggregateID: AggregateID = await this.commandBus.execute( const aggregateID: AggregateID = await this.commandBus.execute(

View File

@ -10,6 +10,7 @@ import { GrpcMethod, RpcException } from '@nestjs/microservices';
import { CreateAuthenticationRequestDto } from './dtos/create-authentication.request.dto'; import { CreateAuthenticationRequestDto } from './dtos/create-authentication.request.dto';
import { CreateAuthenticationCommand } from '@modules/authentication/core/application/commands/create-authentication/create-authentication.command'; import { CreateAuthenticationCommand } from '@modules/authentication/core/application/commands/create-authentication/create-authentication.command';
import { AuthenticationAlreadyExistsException } from '@modules/authentication/core/domain/authentication.errors'; import { AuthenticationAlreadyExistsException } from '@modules/authentication/core/domain/authentication.errors';
import { GRPC_AUTHENTICATION_SERVICE_NAME } from '@src/app.constants';
@UsePipes( @UsePipes(
new RpcValidationPipe({ new RpcValidationPipe({
@ -21,7 +22,7 @@ import { AuthenticationAlreadyExistsException } from '@modules/authentication/co
export class CreateAuthenticationGrpcController { export class CreateAuthenticationGrpcController {
constructor(private readonly commandBus: CommandBus) {} constructor(private readonly commandBus: CommandBus) {}
@GrpcMethod('AuthenticationService', 'Create') @GrpcMethod(GRPC_AUTHENTICATION_SERVICE_NAME, 'Create')
async create(data: CreateAuthenticationRequestDto): Promise<IdResponse> { async create(data: CreateAuthenticationRequestDto): Promise<IdResponse> {
try { try {
const aggregateID: AggregateID = await this.commandBus.execute( const aggregateID: AggregateID = await this.commandBus.execute(

View File

@ -9,6 +9,7 @@ import { CommandBus } from '@nestjs/cqrs';
import { GrpcMethod, RpcException } from '@nestjs/microservices'; import { GrpcMethod, RpcException } from '@nestjs/microservices';
import { DeleteAuthenticationCommand } from '@modules/authentication/core/application/commands/delete-authentication/delete-authentication.command'; import { DeleteAuthenticationCommand } from '@modules/authentication/core/application/commands/delete-authentication/delete-authentication.command';
import { DeleteAuthenticationRequestDto } from './dtos/delete-authentication.request.dto'; import { DeleteAuthenticationRequestDto } from './dtos/delete-authentication.request.dto';
import { GRPC_AUTHENTICATION_SERVICE_NAME } from '@src/app.constants';
@UsePipes( @UsePipes(
new RpcValidationPipe({ new RpcValidationPipe({
@ -20,7 +21,7 @@ import { DeleteAuthenticationRequestDto } from './dtos/delete-authentication.req
export class DeleteAuthenticationGrpcController { export class DeleteAuthenticationGrpcController {
constructor(private readonly commandBus: CommandBus) {} constructor(private readonly commandBus: CommandBus) {}
@GrpcMethod('AuthenticationService', 'Delete') @GrpcMethod(GRPC_AUTHENTICATION_SERVICE_NAME, 'Delete')
async delete(data: DeleteAuthenticationRequestDto): Promise<void> { async delete(data: DeleteAuthenticationRequestDto): Promise<void> {
try { try {
await this.commandBus.execute(new DeleteAuthenticationCommand(data)); await this.commandBus.execute(new DeleteAuthenticationCommand(data));

View File

@ -9,6 +9,7 @@ import { CommandBus } from '@nestjs/cqrs';
import { GrpcMethod, RpcException } from '@nestjs/microservices'; import { GrpcMethod, RpcException } from '@nestjs/microservices';
import { DeleteUsernameRequestDto } from './dtos/delete-username.request.dto'; import { DeleteUsernameRequestDto } from './dtos/delete-username.request.dto';
import { DeleteUsernameCommand } from '@modules/authentication/core/application/commands/delete-username/delete-username.command'; import { DeleteUsernameCommand } from '@modules/authentication/core/application/commands/delete-username/delete-username.command';
import { GRPC_AUTHENTICATION_SERVICE_NAME } from '@src/app.constants';
@UsePipes( @UsePipes(
new RpcValidationPipe({ new RpcValidationPipe({
@ -20,7 +21,7 @@ import { DeleteUsernameCommand } from '@modules/authentication/core/application/
export class DeleteUsernameGrpcController { export class DeleteUsernameGrpcController {
constructor(private readonly commandBus: CommandBus) {} constructor(private readonly commandBus: CommandBus) {}
@GrpcMethod('AuthenticationService', 'DeleteUsername') @GrpcMethod(GRPC_AUTHENTICATION_SERVICE_NAME, 'DeleteUsername')
async delete(data: DeleteUsernameRequestDto): Promise<void> { async delete(data: DeleteUsernameRequestDto): Promise<void> {
try { try {
await this.commandBus.execute(new DeleteUsernameCommand(data)); await this.commandBus.execute(new DeleteUsernameCommand(data));

View File

@ -16,7 +16,7 @@ export function IsValidUsername(validationOptions?: ValidationOptions) {
options: validationOptions, options: validationOptions,
validator: { validator: {
validate(value: any, args: ValidationArguments) { validate(value: any, args: ValidationArguments) {
const usernameType: Type = args.object['type']; const usernameType: Type = (args.object as any)['type'];
switch (usernameType) { switch (usernameType) {
case Type.PHONE: case Type.PHONE:
return isPhoneNumber(value); return isPhoneNumber(value);

View File

@ -9,6 +9,7 @@ import { CommandBus } from '@nestjs/cqrs';
import { GrpcMethod, RpcException } from '@nestjs/microservices'; import { GrpcMethod, RpcException } from '@nestjs/microservices';
import { UpdatePasswordRequestDto } from './dtos/update-password.request.dto'; import { UpdatePasswordRequestDto } from './dtos/update-password.request.dto';
import { UpdatePasswordCommand } from '@modules/authentication/core/application/commands/update-password/update-password.command'; import { UpdatePasswordCommand } from '@modules/authentication/core/application/commands/update-password/update-password.command';
import { GRPC_AUTHENTICATION_SERVICE_NAME } from '@src/app.constants';
@UsePipes( @UsePipes(
new RpcValidationPipe({ new RpcValidationPipe({
@ -20,7 +21,7 @@ import { UpdatePasswordCommand } from '@modules/authentication/core/application/
export class UpdatePasswordGrpcController { export class UpdatePasswordGrpcController {
constructor(private readonly commandBus: CommandBus) {} constructor(private readonly commandBus: CommandBus) {}
@GrpcMethod('AuthenticationService', 'UpdatePassword') @GrpcMethod(GRPC_AUTHENTICATION_SERVICE_NAME, 'UpdatePassword')
async updatePassword(data: UpdatePasswordRequestDto): Promise<IdResponse> { async updatePassword(data: UpdatePasswordRequestDto): Promise<IdResponse> {
try { try {
const aggregateID: AggregateID = await this.commandBus.execute( const aggregateID: AggregateID = await this.commandBus.execute(

View File

@ -10,6 +10,7 @@ import { GrpcMethod, RpcException } from '@nestjs/microservices';
import { UsernameAlreadyExistsException } from '@modules/authentication/core/domain/authentication.errors'; import { UsernameAlreadyExistsException } from '@modules/authentication/core/domain/authentication.errors';
import { UpdateUsernameRequestDto } from './dtos/update-username.request.dto'; import { UpdateUsernameRequestDto } from './dtos/update-username.request.dto';
import { UpdateUsernameCommand } from '@modules/authentication/core/application/commands/update-username/update-username.command'; import { UpdateUsernameCommand } from '@modules/authentication/core/application/commands/update-username/update-username.command';
import { GRPC_AUTHENTICATION_SERVICE_NAME } from '@src/app.constants';
@UsePipes( @UsePipes(
new RpcValidationPipe({ new RpcValidationPipe({
@ -21,7 +22,7 @@ import { UpdateUsernameCommand } from '@modules/authentication/core/application/
export class UpdateUsernameGrpcController { export class UpdateUsernameGrpcController {
constructor(private readonly commandBus: CommandBus) {} constructor(private readonly commandBus: CommandBus) {}
@GrpcMethod('AuthenticationService', 'UpdateUsername') @GrpcMethod(GRPC_AUTHENTICATION_SERVICE_NAME, 'UpdateUsername')
async updateUsername(data: UpdateUsernameRequestDto): Promise<IdResponse> { async updateUsername(data: UpdateUsernameRequestDto): Promise<IdResponse> {
try { try {
const aggregateID: AggregateID = await this.commandBus.execute( const aggregateID: AggregateID = await this.commandBus.execute(

View File

@ -9,6 +9,7 @@ import { QueryBus } from '@nestjs/cqrs';
import { GrpcMethod, RpcException } from '@nestjs/microservices'; import { GrpcMethod, RpcException } from '@nestjs/microservices';
import { ValidateAuthenticationRequestDto } from './dtos/validate-authentication.request.dto'; import { ValidateAuthenticationRequestDto } from './dtos/validate-authentication.request.dto';
import { ValidateAuthenticationQuery } from '@modules/authentication/core/application/queries/validate-authentication/validate-authentication.query'; import { ValidateAuthenticationQuery } from '@modules/authentication/core/application/queries/validate-authentication/validate-authentication.query';
import { GRPC_AUTHENTICATION_SERVICE_NAME } from '@src/app.constants';
@UsePipes( @UsePipes(
new RpcValidationPipe({ new RpcValidationPipe({
@ -20,7 +21,7 @@ import { ValidateAuthenticationQuery } from '@modules/authentication/core/applic
export class ValidateAuthenticationGrpcController { export class ValidateAuthenticationGrpcController {
constructor(private readonly queryBus: QueryBus) {} constructor(private readonly queryBus: QueryBus) {}
@GrpcMethod('AuthenticationService', 'Validate') @GrpcMethod(GRPC_AUTHENTICATION_SERVICE_NAME, 'Validate')
async validate(data: ValidateAuthenticationRequestDto): Promise<IdResponse> { async validate(data: ValidateAuthenticationRequestDto): Promise<IdResponse> {
try { try {
const aggregateID: AggregateID = await this.queryBus.execute( const aggregateID: AggregateID = await this.queryBus.execute(

View File

@ -2,13 +2,14 @@ import { Injectable } from '@nestjs/common';
import { CommandBus } from '@nestjs/cqrs'; import { CommandBus } from '@nestjs/cqrs';
import { RabbitSubscribe } from '@mobicoop/message-broker-module'; import { RabbitSubscribe } from '@mobicoop/message-broker-module';
import { DeleteAuthenticationCommand } from '@modules/authentication/core/application/commands/delete-authentication/delete-authentication.command'; import { DeleteAuthenticationCommand } from '@modules/authentication/core/application/commands/delete-authentication/delete-authentication.command';
import { USER_DELETED_MESSAGE_HANDLER } from '@src/app.constants';
@Injectable() @Injectable()
export class UserDeletedMessageHandler { export class UserDeletedMessageHandler {
constructor(private readonly commandBus: CommandBus) {} constructor(private readonly commandBus: CommandBus) {}
@RabbitSubscribe({ @RabbitSubscribe({
name: 'userDeleted', name: USER_DELETED_MESSAGE_HANDLER,
}) })
public async userDeleted(message: string) { public async userDeleted(message: string) {
const deletedUser = JSON.parse(message); const deletedUser = JSON.parse(message);

View File

@ -3,13 +3,14 @@ import { CommandBus } from '@nestjs/cqrs';
import { RabbitSubscribe } from '@mobicoop/message-broker-module'; import { RabbitSubscribe } from '@mobicoop/message-broker-module';
import { Type } from '@modules/authentication/core/domain/username.types'; import { Type } from '@modules/authentication/core/domain/username.types';
import { UpdateUsernameCommand } from '@modules/authentication/core/application/commands/update-username/update-username.command'; import { UpdateUsernameCommand } from '@modules/authentication/core/application/commands/update-username/update-username.command';
import { USER_UPDATED_MESSAGE_HANDLER } from '@src/app.constants';
@Injectable() @Injectable()
export class UserUpdatedMessageHandler { export class UserUpdatedMessageHandler {
constructor(private readonly commandBus: CommandBus) {} constructor(private readonly commandBus: CommandBus) {}
@RabbitSubscribe({ @RabbitSubscribe({
name: 'userUpdated', name: USER_UPDATED_MESSAGE_HANDLER,
}) })
public async userUpdated(message: string) { public async userUpdated(message: string) {
const updatedUser = JSON.parse(message); const updatedUser = JSON.parse(message);

View File

@ -147,9 +147,9 @@ describe('AuthenticationRepository', () => {
}, },
}); });
expect(updatedAuthentication.uuid).toBe(uuid); expect((updatedAuthentication as any).uuid).toBe(uuid);
expect(authenticationToUpdate.updatedAt.getTime()).toBeLessThan( expect(authenticationToUpdate.updatedAt.getTime()).toBeLessThan(
updatedAuthentication.updatedAt.getTime(), (updatedAuthentication as any).updatedAt.getTime(),
); );
}); });

View File

@ -84,9 +84,8 @@ describe('UsernameRepository', () => {
}, },
}); });
const username = await usernameRepository.findByName( const username =
'john.doe@email.com', await usernameRepository.findByName('john.doe@email.com');
);
expect(username.id).toBe('john.doe@email.com'); expect(username.id).toBe('john.doe@email.com');
}); });
@ -103,9 +102,8 @@ describe('UsernameRepository', () => {
it('should create a username', async () => { it('should create a username', async () => {
const beforeCount = await prismaService.username.count(); const beforeCount = await prismaService.username.count();
const usernameToCreate: UsernameEntity = await UsernameEntity.create( const usernameToCreate: UsernameEntity =
createUsernameProps, await UsernameEntity.create(createUsernameProps);
);
await usernameRepository.insert(usernameToCreate); await usernameRepository.insert(usernameToCreate);
const afterCount = await prismaService.username.count(); const afterCount = await prismaService.username.count();
@ -122,9 +120,8 @@ describe('UsernameRepository', () => {
}, },
}); });
const usernameToCreate: UsernameEntity = await UsernameEntity.create( const usernameToCreate: UsernameEntity =
createUsernameProps, await UsernameEntity.create(createUsernameProps);
);
await expect( await expect(
usernameRepository.insert(usernameToCreate), usernameRepository.insert(usernameToCreate),
).rejects.toBeInstanceOf(UniqueConstraintException); ).rejects.toBeInstanceOf(UniqueConstraintException);
@ -141,9 +138,8 @@ describe('UsernameRepository', () => {
}, },
}); });
const toUpdate: UsernameEntity = await UsernameEntity.create( const toUpdate: UsernameEntity =
createUsernameProps, await UsernameEntity.create(createUsernameProps);
);
await usernameRepository.updateUsername( await usernameRepository.updateUsername(
usernameToUpdate.username, usernameToUpdate.username,
toUpdate, toUpdate,
@ -155,16 +151,15 @@ describe('UsernameRepository', () => {
}, },
}); });
expect(updatedUsername.username).toBe('john.doe@email.com'); expect((updatedUsername as any).username).toBe('john.doe@email.com');
expect(usernameToUpdate.updatedAt.getTime()).toBeLessThan( expect(usernameToUpdate.updatedAt.getTime()).toBeLessThan(
updatedUsername.updatedAt.getTime(), (updatedUsername as any).updatedAt.getTime(),
); );
}); });
it('should throw a DatabaseException if id is unknown', async () => { it('should throw a DatabaseException if id is unknown', async () => {
const toUpdate: UsernameEntity = await UsernameEntity.create( const toUpdate: UsernameEntity =
createUsernameProps, await UsernameEntity.create(createUsernameProps);
);
await expect( await expect(
usernameRepository.updateUsername('jane.doe@email.com', toUpdate), usernameRepository.updateUsername('jane.doe@email.com', toUpdate),
@ -181,9 +176,8 @@ describe('UsernameRepository', () => {
username: 'john.doe@email.com', username: 'john.doe@email.com',
}, },
}); });
const toDelete: UsernameEntity = await UsernameEntity.create( const toDelete: UsernameEntity =
createUsernameProps, await UsernameEntity.create(createUsernameProps);
);
await usernameRepository.deleteUsername(toDelete); await usernameRepository.deleteUsername(toDelete);
const count = await prismaService.username.count(); const count = await prismaService.username.count();
@ -191,9 +185,8 @@ describe('UsernameRepository', () => {
}); });
it('should throw a DatabaseException if username does not exist', async () => { it('should throw a DatabaseException if username does not exist', async () => {
const toDelete: UsernameEntity = await UsernameEntity.create( const toDelete: UsernameEntity =
createUsernameProps, await UsernameEntity.create(createUsernameProps);
);
await expect(usernameRepository.delete(toDelete)).rejects.toBeInstanceOf( await expect(usernameRepository.delete(toDelete)).rejects.toBeInstanceOf(
DatabaseErrorException, DatabaseErrorException,
); );

View File

@ -82,9 +82,8 @@ describe('Add Username Service', () => {
})), })),
}); });
it('should add a new username', async () => { it('should add a new username', async () => {
const result: AggregateID = await addUsernameService.execute( const result: AggregateID =
addUsernameCommand, await addUsernameService.execute(addUsernameCommand);
);
expect(result).toBe('john.doe@email.com'); expect(result).toBe('john.doe@email.com');
}); });
it('should throw a dedicated exception if username already exists for this type', async () => { it('should throw a dedicated exception if username already exists for this type', async () => {

View File

@ -80,17 +80,15 @@ describe('Authentication password validation', () => {
it('should validate a valid password', async () => { it('should validate a valid password', async () => {
const authenticationEntity: AuthenticationEntity = const authenticationEntity: AuthenticationEntity =
await AuthenticationEntity.create(createAuthenticationProps); await AuthenticationEntity.create(createAuthenticationProps);
const result: boolean = await authenticationEntity.authenticate( const result: boolean =
'somePassword', await authenticationEntity.authenticate('somePassword');
);
expect(result).toBeTruthy(); expect(result).toBeTruthy();
}); });
it('should not validate an invalid password', async () => { it('should not validate an invalid password', async () => {
const authenticationEntity: AuthenticationEntity = const authenticationEntity: AuthenticationEntity =
await AuthenticationEntity.create(createAuthenticationProps); await AuthenticationEntity.create(createAuthenticationProps);
const result: boolean = await authenticationEntity.authenticate( const result: boolean =
'someWrongPassword', await authenticationEntity.authenticate('someWrongPassword');
);
expect(result).toBeFalsy(); expect(result).toBeFalsy();
}); });
}); });

View File

@ -14,18 +14,16 @@ const createUsernameProps: CreateUsernameProps = {
describe('Username entity create', () => { describe('Username entity create', () => {
it('should create a new username entity', async () => { it('should create a new username entity', async () => {
const usernameEntity: UsernameEntity = await UsernameEntity.create( const usernameEntity: UsernameEntity =
createUsernameProps, await UsernameEntity.create(createUsernameProps);
);
expect(usernameEntity.id).toBe('john.doe@email.com'); expect(usernameEntity.id).toBe('john.doe@email.com');
expect(usernameEntity.domainEvents.length).toBe(1); expect(usernameEntity.domainEvents.length).toBe(1);
}); });
}); });
describe('Username entity update', () => { describe('Username entity update', () => {
it('should update a username entity', async () => { it('should update a username entity', async () => {
const usernameEntity: UsernameEntity = await UsernameEntity.create( const usernameEntity: UsernameEntity =
createUsernameProps, await UsernameEntity.create(createUsernameProps);
);
usernameEntity.update({ usernameEntity.update({
name: 'new-john.doe@email.com', name: 'new-john.doe@email.com',
}); });
@ -38,9 +36,8 @@ describe('Username entity update', () => {
}); });
describe('Username entity delete', () => { describe('Username entity delete', () => {
it('should delete a username entity', async () => { it('should delete a username entity', async () => {
const usernameEntity: UsernameEntity = await UsernameEntity.create( const usernameEntity: UsernameEntity =
createUsernameProps, await UsernameEntity.create(createUsernameProps);
);
usernameEntity.delete(); usernameEntity.delete();
expect(usernameEntity.domainEvents.length).toBe(2); expect(usernameEntity.domainEvents.length).toBe(2);
expect(usernameEntity.domainEvents[1]).toBeInstanceOf( expect(usernameEntity.domainEvents[1]).toBeInstanceOf(

View File

@ -85,9 +85,8 @@ describe('Username repository', () => {
it('should find a username by its name', async () => { it('should find a username by its name', async () => {
jest.spyOn(usernameRepository, 'findOne'); jest.spyOn(usernameRepository, 'findOne');
const username: UsernameEntity = await usernameRepository.findByName( const username: UsernameEntity =
'john.doe@email.com', await usernameRepository.findByName('john.doe@email.com');
);
expect(usernameRepository.findOne).toHaveBeenCalledTimes(1); expect(usernameRepository.findOne).toHaveBeenCalledTimes(1);
expect(usernameRepository.findOne).toHaveBeenCalledWith({ expect(usernameRepository.findOne).toHaveBeenCalledWith({
username: 'john.doe@email.com', username: 'john.doe@email.com',

View File

@ -55,9 +55,8 @@ describe('Add Username Grpc Controller', () => {
it('should add a new username', async () => { it('should add a new username', async () => {
jest.spyOn(mockCommandBus, 'execute'); jest.spyOn(mockCommandBus, 'execute');
const result: IdResponse = await addUsernameGrpcController.addUsername( const result: IdResponse =
addUsernameRequest, await addUsernameGrpcController.addUsername(addUsernameRequest);
);
expect(result).toBeInstanceOf(IdResponse); expect(result).toBeInstanceOf(IdResponse);
expect(result.id).toBe('john.doe@email.com'); expect(result.id).toBe('john.doe@email.com');
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1); expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);

View File

@ -42,15 +42,13 @@ export class UsernameMapper
updatedAt: new Date(record.updatedAt), updatedAt: new Date(record.updatedAt),
props: { props: {
name: record.username, name: record.username,
type: Type[record.type], type: record.type as Type,
userId: record.authUuid, userId: record.authUuid,
}, },
}); });
return entity; return entity;
}; };
toResponse = (entity: UsernameEntity): UsernameResponseDto => { toResponse = (entity: UsernameEntity): UsernameResponseDto =>
const response = new UsernameResponseDto(entity); new UsernameResponseDto(entity);
return response;
};
} }

View File

@ -6,6 +6,6 @@ export abstract class DecisionMakerPort {
abstract decide( abstract decide(
domain: Domain, domain: Domain,
action: Action, action: Action,
context: ContextItem[], context?: ContextItem[],
): Promise<boolean>; ): Promise<boolean>;
} }

View File

@ -21,12 +21,14 @@ export class OpaDecisionMaker extends DecisionMakerPort {
decide = async ( decide = async (
domain: Domain, domain: Domain,
action: Action, action: Action,
context: ContextItem[], context?: ContextItem[],
): Promise<boolean> => { ): Promise<boolean> => {
const reducedContext = context.reduce( const reducedContext = context
(obj, item) => Object.assign(obj, { [item.name]: item.value }), ? context.reduce(
{}, (obj, item) => Object.assign(obj, { [item.name]: item.value }),
); {},
)
: undefined;
try { try {
const { data } = await lastValueFrom( const { data } = await lastValueFrom(
this.httpService.post<Decision>( this.httpService.post<Decision>(

View File

@ -5,6 +5,7 @@ import { GrpcMethod, RpcException } from '@nestjs/microservices';
import { DecisionRequestDto } from './dtos/decision.request.dto'; import { DecisionRequestDto } from './dtos/decision.request.dto';
import { DecisionQuery } from '@modules/authorization/core/application/queries/decision/decision.query'; import { DecisionQuery } from '@modules/authorization/core/application/queries/decision/decision.query';
import { DecisionResponseDto } from '../dtos/decision.response.dto'; import { DecisionResponseDto } from '../dtos/decision.response.dto';
import { GRPC_AUTHORIZATION_SERVICE_NAME } from '@src/app.constants';
@UsePipes( @UsePipes(
new RpcValidationPipe({ new RpcValidationPipe({
@ -16,7 +17,7 @@ import { DecisionResponseDto } from '../dtos/decision.response.dto';
export class DecideGrpcController { export class DecideGrpcController {
constructor(private readonly queryBus: QueryBus) {} constructor(private readonly queryBus: QueryBus) {}
@GrpcMethod('AuthorizationService', 'Decide') @GrpcMethod(GRPC_AUTHORIZATION_SERVICE_NAME, 'Decide')
async decide(data: DecisionRequestDto): Promise<DecisionResponseDto> { async decide(data: DecisionRequestDto): Promise<DecisionResponseDto> {
try { try {
const allow: boolean = await this.queryBus.execute( const allow: boolean = await this.queryBus.execute(

View File

@ -49,9 +49,8 @@ describe('Decision Query Handler', () => {
value: '96d99d44-e0a6-458e-a656-de2a400d60a8', value: '96d99d44-e0a6-458e-a656-de2a400d60a8',
}, },
]); ]);
const decision: boolean = await decisionQueryHandler.execute( const decision: boolean =
decisionQuery, await decisionQueryHandler.execute(decisionQuery);
);
expect(decision).toBeTruthy(); expect(decision).toBeTruthy();
}); });
it('should return a negative decision', async () => { it('should return a negative decision', async () => {
@ -65,9 +64,8 @@ describe('Decision Query Handler', () => {
value: '96d99d44-e0a6-458e-a656-de2a400d60a9', value: '96d99d44-e0a6-458e-a656-de2a400d60a9',
}, },
]); ]);
const decision: boolean = await decisionQueryHandler.execute( const decision: boolean =
decisionQuery, await decisionQueryHandler.execute(decisionQuery);
);
expect(decision).toBeFalsy(); expect(decision).toBeFalsy();
}); });
}); });

View File

@ -6,6 +6,15 @@ 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 {
SERVICE_NAME,
USER_DELETED_MESSAGE_HANDLER,
USER_DELETED_QUEUE,
USER_DELETED_ROUTING_KEY,
USER_UPDATED_MESSAGE_HANDLER,
USER_UPDATED_QUEUE,
USER_UPDATED_ROUTING_KEY,
} from '@src/app.constants';
const imports = [ const imports = [
MessageBrokerModule.forRootAsync({ MessageBrokerModule.forRootAsync({
@ -14,17 +23,22 @@ const imports = [
useFactory: async ( useFactory: async (
configService: ConfigService, configService: ConfigService,
): Promise<MessageBrokerModuleOptions> => ({ ): Promise<MessageBrokerModuleOptions> => ({
uri: configService.get<string>('MESSAGE_BROKER_URI'), uri: configService.get<string>('MESSAGE_BROKER_URI') as string,
exchange: configService.get<string>('MESSAGE_BROKER_EXCHANGE'), exchange: {
name: 'auth', name: configService.get<string>('MESSAGE_BROKER_EXCHANGE') as string,
durable: configService.get<boolean>(
'MESSAGE_BROKER_EXCHANGE_DURABILITY',
) as boolean,
},
name: SERVICE_NAME,
handlers: { handlers: {
userUpdated: { [USER_UPDATED_MESSAGE_HANDLER]: {
routingKey: 'user.updated', routingKey: USER_UPDATED_ROUTING_KEY,
queue: 'auth-user-updated', queue: USER_UPDATED_QUEUE,
}, },
userDeleted: { [USER_DELETED_MESSAGE_HANDLER]: {
routingKey: 'user.deleted', routingKey: USER_DELETED_ROUTING_KEY,
queue: 'auth-user-deleted', queue: USER_DELETED_QUEUE,
}, },
}, },
}), }),

View File

@ -12,12 +12,13 @@
"baseUrl": "./", "baseUrl": "./",
"incremental": true, "incremental": true,
"skipLibCheck": true, "skipLibCheck": true,
"strictNullChecks": false, "strictNullChecks": true,
"noImplicitAny": false, "noImplicitAny": true,
"strictBindCallApply": false, "strictBindCallApply": false,
"forceConsistentCasingInFileNames": false, "forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": false, "noFallthroughCasesInSwitch": false,
"paths": { "paths": {
"@libs/*": ["src/libs/*"],
"@modules/*": ["src/modules/*"], "@modules/*": ["src/modules/*"],
"@src/*": ["src/*"] "@src/*": ["src/*"]
} }