delete authentication, use custom logger

This commit is contained in:
sbriat
2023-07-05 17:32:21 +02:00
parent f33f679e12
commit bbcd2cdb9e
24 changed files with 269 additions and 54 deletions

1
src/app.di-tokens.ts Normal file
View File

@@ -0,0 +1 @@
export const MESSAGE_PUBLISHER = Symbol();

View File

@@ -1,20 +1,35 @@
import { classes } from '@automapper/classes';
import { AutomapperModule } from '@automapper/nestjs';
// import { classes } from '@automapper/classes';
// import { AutomapperModule } from '@automapper/nestjs';
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AuthorizationModule } from './modules/authorization/authorization.module';
import { HealthModule } from './modules/health/health.module';
import { ConfigModule, ConfigService } from '@nestjs/config';
// import { AuthorizationModule } from './modules/authorization/authorization.module';
// import { HealthModule } from './modules/health/health.module';
import { AuthenticationModule } from '@modules/newauthentication/authentication.module';
import { EventEmitterModule } from '@nestjs/event-emitter';
import {
MessageBrokerModule,
MessageBrokerModuleOptions,
} from '@mobicoop/message-broker-module';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
EventEmitterModule.forRoot(),
AutomapperModule.forRoot({ strategyInitializer: classes() }),
MessageBrokerModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (
configService: ConfigService,
): Promise<MessageBrokerModuleOptions> => ({
uri: configService.get<string>('MESSAGE_BROKER_URI'),
exchange: configService.get<string>('MESSAGE_BROKER_EXCHANGE'),
name: 'auth',
}),
}),
// AutomapperModule.forRoot({ strategyInitializer: classes() }),
AuthenticationModule,
AuthorizationModule,
HealthModule,
// AuthorizationModule,
// HealthModule,
],
controllers: [],
providers: [],

View File

@@ -49,6 +49,7 @@ export class AuthenticationMapper
createdAt: new Date(record.createdAt),
updatedAt: new Date(record.updatedAt),
props: {
userId: record.uuid,
password: record.password,
usernames: record.usernames.map((username: UsernameModel) => ({
name: username.username,

View File

@@ -6,10 +6,20 @@ import { AUTHENTICATION_REPOSITORY } from './authentication.di-tokens';
import { AuthenticationRepository } from './infrastructure/authentication.repository';
import { PrismaService } from './infrastructure/prisma.service';
import { CqrsModule } from '@nestjs/cqrs';
import { DeleteAuthenticationGrpcController } from './interface/grpc-controllers/delete-authentication.grpc.controller';
import { DeleteAuthenticationService } from './core/application/commands/delete-authentication/delete-authentication.service';
import { MESSAGE_PUBLISHER } from '@src/app.di-tokens';
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
const grpcControllers = [CreateAuthenticationGrpcController];
const grpcControllers = [
CreateAuthenticationGrpcController,
DeleteAuthenticationGrpcController,
];
const commandHandlers: Provider[] = [CreateAuthenticationService];
const commandHandlers: Provider[] = [
CreateAuthenticationService,
DeleteAuthenticationService,
];
const mappers: Provider[] = [AuthenticationMapper];
@@ -20,12 +30,25 @@ const repositories: Provider[] = [
},
];
const messageBrokers: Provider[] = [
{
provide: MESSAGE_PUBLISHER,
useClass: MessageBrokerPublisher,
},
];
const orms: Provider[] = [PrismaService];
@Module({
imports: [CqrsModule],
controllers: [...grpcControllers],
providers: [...commandHandlers, ...mappers, ...repositories, ...orms],
providers: [
...commandHandlers,
...mappers,
...repositories,
...messageBrokers,
...orms,
],
exports: [PrismaService, AuthenticationMapper, AUTHENTICATION_REPOSITORY],
})
export class AuthenticationModule {}

View File

@@ -2,11 +2,13 @@ import { Command, CommandProps } from '@mobicoop/ddd-library';
import { Username } from '../types/username';
export class CreateAuthenticationCommand extends Command {
readonly userId: string;
readonly password: string;
readonly usernames: Username[];
constructor(props: CommandProps<CreateAuthenticationCommand>) {
super(props);
this.userId = props.userId;
this.password = props.password;
this.usernames = props.usernames;
}

View File

@@ -3,7 +3,7 @@ import { Inject } from '@nestjs/common';
import {
AggregateID,
ConflictException,
UniqueConflictException,
UniqueConstraintException,
} from '@mobicoop/ddd-library';
import { CreateAuthenticationCommand } from './create-authentication.command';
import { AUTHENTICATION_REPOSITORY } from '@modules/newauthentication/authentication.di-tokens';
@@ -22,21 +22,27 @@ export class CreateAuthenticationService implements ICommandHandler {
) {}
async execute(command: CreateAuthenticationCommand): Promise<AggregateID> {
const authentication = await AuthenticationEntity.create({
password: command.password,
usernames: command.usernames,
});
const authentication: AuthenticationEntity =
await AuthenticationEntity.create({
userId: command.userId,
password: command.password,
usernames: command.usernames,
});
try {
await this.repository.insert(authentication);
return authentication.id;
return authentication.getProps().userId;
} catch (error: any) {
console.log('error', error.cause);
if (error instanceof ConflictException) {
throw new AuthenticationAlreadyExistsException(error);
}
if (
error instanceof UniqueConflictException &&
error instanceof UniqueConstraintException &&
error.message.includes('uuid')
) {
throw new AuthenticationAlreadyExistsException(error);
}
if (
error instanceof UniqueConstraintException &&
error.message.includes('username')
) {
throw new UsernameAlreadyExistsException(error);

View File

@@ -0,0 +1,10 @@
import { Command, CommandProps } from '@mobicoop/ddd-library';
export class DeleteAuthenticationCommand extends Command {
readonly userId: string;
constructor(props: CommandProps<DeleteAuthenticationCommand>) {
super(props);
this.userId = props.userId;
}
}

View File

@@ -0,0 +1,26 @@
import { Inject } from '@nestjs/common';
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { DeleteAuthenticationCommand } from './delete-authentication.command';
import { AUTHENTICATION_REPOSITORY } from '@modules/newauthentication/authentication.di-tokens';
import { AuthenticationRepositoryPort } from '../ports/authentication.repository.port';
import { AuthenticationEntity } from '@modules/newauthentication/core/domain/authentication.entity';
@CommandHandler(DeleteAuthenticationCommand)
export class DeleteAuthenticationService implements ICommandHandler {
constructor(
@Inject(AUTHENTICATION_REPOSITORY)
private readonly authenticationRepository: AuthenticationRepositoryPort,
) {}
async execute(command: DeleteAuthenticationCommand): Promise<boolean> {
const authentication: AuthenticationEntity =
await this.authenticationRepository.findOneById(command.userId, {
usernames: true,
});
authentication.delete();
const deleted: boolean = await this.authenticationRepository.delete(
authentication,
);
return deleted;
}
}

View File

@@ -1,11 +1,11 @@
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
import { v4 } from 'uuid';
import * as bcrypt from 'bcrypt';
import {
AuthenticationProps,
CreateAuthenticationProps,
} from './authentication.types';
import { AuthenticationCreatedDomainEvent } from './events/authentication-created.domain-events';
import { AuthenticationDeletedDomainEvent } from './events/authentication-deleted.domain-event';
export class AuthenticationEntity extends AggregateRoot<AuthenticationProps> {
protected readonly _id: AggregateID;
@@ -13,22 +13,30 @@ export class AuthenticationEntity extends AggregateRoot<AuthenticationProps> {
static create = async (
create: CreateAuthenticationProps,
): Promise<AuthenticationEntity> => {
const id = v4();
const props: AuthenticationProps = { ...create };
const hash = await bcrypt.hash(props.password, 10);
const authentication = new AuthenticationEntity({
id,
id: props.userId,
props: {
userId: props.userId,
password: hash,
usernames: props.usernames,
},
});
authentication.addEvent(
new AuthenticationCreatedDomainEvent({ aggregateId: id }),
new AuthenticationCreatedDomainEvent({ aggregateId: props.userId }),
);
return authentication;
};
delete(): void {
this.addEvent(
new AuthenticationDeletedDomainEvent({
aggregateId: this.id,
}),
);
}
validate(): void {
// entity business rules validation to protect it's invariant before saving entity to a database
}

View File

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

View File

@@ -0,0 +1,7 @@
import { DomainEvent, DomainEventProps } from '@mobicoop/ddd-library';
export class AuthenticationDeletedDomainEvent extends DomainEvent {
constructor(props: DomainEventProps<AuthenticationDeletedDomainEvent>) {
super(props);
}
}

View File

@@ -1,9 +0,0 @@
import { Entity } from '@mobicoop/ddd-library';
import { UsernameProps } from './username.types';
export class UsernameEntity extends Entity<UsernameProps> {
protected _id: string;
validate(): void {
throw new Error('Method not implemented.');
}
}

View File

@@ -4,12 +4,6 @@ export interface UsernameProps {
type: Type;
}
// Properties that are needed for a Username creation
export interface CreateUsernameProps {
name: string;
type: Type;
}
export enum Type {
EMAIL = 'EMAIL',
PHONE = 'PHONE',

View File

@@ -1,10 +1,15 @@
import { Injectable, Logger } from '@nestjs/common';
import { Inject, Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { PrismaRepositoryBase } from '@mobicoop/ddd-library';
import {
LoggerBase,
MessagePublisherPort,
PrismaRepositoryBase,
} from '@mobicoop/ddd-library';
import { AuthenticationEntity } from '../core/domain/authentication.entity';
import { AuthenticationRepositoryPort } from '../core/application/commands/ports/authentication.repository.port';
import { PrismaService } from './prisma.service';
import { AuthenticationMapper } from '../authentication.mapper';
import { MESSAGE_PUBLISHER } from '@src/app.di-tokens';
export type AuthenticationBaseModel = {
uuid: string;
@@ -44,13 +49,20 @@ export class AuthenticationRepository
prisma: PrismaService,
mapper: AuthenticationMapper,
eventEmitter: EventEmitter2,
@Inject(MESSAGE_PUBLISHER)
protected readonly messagePublisher: MessagePublisherPort,
) {
super(
prisma.auth,
prisma,
mapper,
eventEmitter,
new Logger(AuthenticationRepository.name),
new LoggerBase(
AuthenticationRepository.name,
'logging',
'auth',
messagePublisher,
),
);
}
}

View File

@@ -9,7 +9,7 @@ service AuthenticationService {
rpc UpdatePassword(Password) returns (Id);
rpc UpdateUsername(Username) returns (Id);
rpc DeleteUsername(Username) returns (Id);
rpc Delete(Id) returns (Empty);
rpc Delete(UserId) returns (Empty);
}
message AuthenticationByUsernamePassword {
@@ -18,17 +18,18 @@ message AuthenticationByUsernamePassword {
}
message Authentication {
repeated Username usernames = 1;
string password = 2;
string userId = 1;
repeated Username usernames = 2;
string password = 3;
}
message Password {
string id = 1;
string userId = 1;
string password = 2;
}
message Username {
string id = 1;
string userId = 1;
string name = 2;
string type = 3;
}
@@ -37,4 +38,8 @@ message Id {
string id = 1;
}
message UserId {
string userId = 1;
}
message Empty {}

View File

@@ -22,7 +22,7 @@ export class CreateAuthenticationGrpcController {
constructor(private readonly commandBus: CommandBus) {}
@GrpcMethod('AuthenticationService', 'Create')
async validate(data: CreateAuthenticationRequestDto): Promise<IdResponse> {
async create(data: CreateAuthenticationRequestDto): Promise<IdResponse> {
try {
const aggregateID: AggregateID = await this.commandBus.execute(
new CreateAuthenticationCommand(data),

View File

@@ -0,0 +1,45 @@
import {
DatabaseErrorException,
NotFoundException,
RpcExceptionCode,
RpcValidationPipe,
} from '@mobicoop/ddd-library';
import { Controller, UsePipes } from '@nestjs/common';
import { CommandBus } from '@nestjs/cqrs';
import { GrpcMethod, RpcException } from '@nestjs/microservices';
import { DeleteAuthenticationCommand } from '@modules/newauthentication/core/application/commands/delete-authentication/delete-authentication.command';
import { DeleteAuthenticationRequestDto } from './dtos/delete-authentication.request.dto';
@UsePipes(
new RpcValidationPipe({
whitelist: true,
forbidUnknownValues: false,
}),
)
@Controller()
export class DeleteAuthenticationGrpcController {
constructor(private readonly commandBus: CommandBus) {}
@GrpcMethod('AuthenticationService', 'Delete')
async delete(data: DeleteAuthenticationRequestDto): Promise<void> {
try {
await this.commandBus.execute(new DeleteAuthenticationCommand(data));
} catch (error: any) {
if (error instanceof NotFoundException)
throw new RpcException({
code: RpcExceptionCode.NOT_FOUND,
message: error.message,
});
if (error instanceof DatabaseErrorException)
throw new RpcException({
code: RpcExceptionCode.INTERNAL,
message: error.message,
});
throw new RpcException({
code: RpcExceptionCode.UNKNOWN,
message: error.message,
});
}
}
}

View File

@@ -9,6 +9,10 @@ import {
import { UsernameDto } from './username.dto';
export class CreateAuthenticationRequestDto {
@IsString()
@IsNotEmpty()
userId: string;
@Type(() => UsernameDto)
@IsArray()
@ArrayMinSize(1)

View File

@@ -0,0 +1,7 @@
import { IsNotEmpty, IsString } from 'class-validator';
export class DeleteAuthenticationRequestDto {
@IsString()
@IsNotEmpty()
userId: string;
}