new authorization
This commit is contained in:
parent
bbcd2cdb9e
commit
470a93879e
24
package.json
24
package.json
|
@ -91,12 +91,12 @@
|
||||||
"ts"
|
"ts"
|
||||||
],
|
],
|
||||||
"modulePathIgnorePatterns": [
|
"modulePathIgnorePatterns": [
|
||||||
".controller.ts",
|
|
||||||
".module.ts",
|
".module.ts",
|
||||||
".request.ts",
|
".dto.ts",
|
||||||
".presenter.ts",
|
".di-tokens.ts",
|
||||||
".profile.ts",
|
".response.ts",
|
||||||
".exception.ts",
|
".port.ts",
|
||||||
|
"prisma.service.ts",
|
||||||
"main.ts"
|
"main.ts"
|
||||||
],
|
],
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
|
@ -108,15 +108,19 @@
|
||||||
"**/*.(t|j)s"
|
"**/*.(t|j)s"
|
||||||
],
|
],
|
||||||
"coveragePathIgnorePatterns": [
|
"coveragePathIgnorePatterns": [
|
||||||
".controller.ts",
|
|
||||||
".module.ts",
|
".module.ts",
|
||||||
".request.ts",
|
".dto.ts",
|
||||||
".presenter.ts",
|
".di-tokens.ts",
|
||||||
".profile.ts",
|
".response.ts",
|
||||||
".exception.ts",
|
".port.ts",
|
||||||
|
"prisma.service.ts",
|
||||||
"main.ts"
|
"main.ts"
|
||||||
],
|
],
|
||||||
"coverageDirectory": "../coverage",
|
"coverageDirectory": "../coverage",
|
||||||
|
"moduleNameMapper": {
|
||||||
|
"^@modules(.*)": "<rootDir>/modules/$1",
|
||||||
|
"^@src(.*)": "<rootDir>$1"
|
||||||
|
},
|
||||||
"testEnvironment": "node"
|
"testEnvironment": "node"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { Module } from '@nestjs/common';
|
||||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
// import { AuthorizationModule } from './modules/authorization/authorization.module';
|
// import { AuthorizationModule } from './modules/authorization/authorization.module';
|
||||||
// import { HealthModule } from './modules/health/health.module';
|
// import { HealthModule } from './modules/health/health.module';
|
||||||
import { AuthenticationModule } from '@modules/newauthentication/authentication.module';
|
import { AuthenticationModule } from '@modules/authentication/authentication.module';
|
||||||
import { EventEmitterModule } from '@nestjs/event-emitter';
|
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||||
import {
|
import {
|
||||||
MessageBrokerModule,
|
MessageBrokerModule,
|
||||||
|
|
|
@ -15,7 +15,7 @@ async function bootstrap() {
|
||||||
protoPath: [
|
protoPath: [
|
||||||
join(
|
join(
|
||||||
__dirname,
|
__dirname,
|
||||||
'modules/newauthentication/interface/grpc-controllers/authentication.proto',
|
'modules/authentication/interface/grpc-controllers/authentication.proto',
|
||||||
),
|
),
|
||||||
join(
|
join(
|
||||||
__dirname,
|
__dirname,
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
export const AUTHENTICATION_REPOSITORY = Symbol('AUTHENTICATION_REPOSITORY');
|
export const AUTHENTICATION_REPOSITORY = Symbol('AUTHENTICATION_REPOSITORY');
|
||||||
|
export const USERNAME_REPOSITORY = Symbol('USERNAME_REPOSITORY');
|
|
@ -52,6 +52,7 @@ export class AuthenticationMapper
|
||||||
userId: record.uuid,
|
userId: record.uuid,
|
||||||
password: record.password,
|
password: record.password,
|
||||||
usernames: record.usernames.map((username: UsernameModel) => ({
|
usernames: record.usernames.map((username: UsernameModel) => ({
|
||||||
|
userId: record.uuid,
|
||||||
name: username.username,
|
name: username.username,
|
||||||
type: Type[username.type],
|
type: Type[username.type],
|
||||||
})),
|
})),
|
|
@ -1,66 +1,72 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module, Provider } from '@nestjs/common';
|
||||||
|
import { CreateAuthenticationGrpcController } from './interface/grpc-controllers/create-authentication.grpc.controller';
|
||||||
|
import { CreateAuthenticationService } from './core/application/commands/create-authentication/create-authentication.service';
|
||||||
|
import { AuthenticationMapper } from './authentication.mapper';
|
||||||
|
import {
|
||||||
|
AUTHENTICATION_REPOSITORY,
|
||||||
|
USERNAME_REPOSITORY,
|
||||||
|
} from './authentication.di-tokens';
|
||||||
|
import { AuthenticationRepository } from './infrastructure/authentication.repository';
|
||||||
|
import { PrismaService } from './infrastructure/prisma.service';
|
||||||
import { CqrsModule } from '@nestjs/cqrs';
|
import { CqrsModule } from '@nestjs/cqrs';
|
||||||
import { DatabaseModule } from '../database/database.module';
|
import { DeleteAuthenticationGrpcController } from './interface/grpc-controllers/delete-authentication.grpc.controller';
|
||||||
import { AuthenticationController } from './adapters/primaries/authentication.controller';
|
import { DeleteAuthenticationService } from './core/application/commands/delete-authentication/delete-authentication.service';
|
||||||
import { CreateAuthenticationUseCase } from './domain/usecases/create-authentication.usecase';
|
import { MESSAGE_PUBLISHER } from '@src/app.di-tokens';
|
||||||
import { ValidateAuthenticationUseCase } from './domain/usecases/validate-authentication.usecase';
|
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
|
||||||
import { AuthenticationProfile } from './mappers/authentication.profile';
|
import { UsernameRepository } from './infrastructure/username.repository';
|
||||||
import { AuthenticationRepository } from './adapters/secondaries/authentication.repository';
|
import { UsernameMapper } from './username.mapper';
|
||||||
import { UpdateUsernameUseCase } from './domain/usecases/update-username.usecase';
|
import { AddUsernameGrpcController } from './interface/grpc-controllers/add-username.grpc.controller';
|
||||||
import { UsernameProfile } from './mappers/username.profile';
|
import { AddUsernameService } from './core/application/commands/add-usernames/add-username.service';
|
||||||
import { AddUsernameUseCase } from './domain/usecases/add-username.usecase';
|
|
||||||
import { UpdatePasswordUseCase } from './domain/usecases/update-password.usecase';
|
const grpcControllers = [
|
||||||
import { DeleteUsernameUseCase } from './domain/usecases/delete-username.usecase';
|
CreateAuthenticationGrpcController,
|
||||||
import { DeleteAuthenticationUseCase } from './domain/usecases/delete-authentication.usecase';
|
DeleteAuthenticationGrpcController,
|
||||||
import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq';
|
AddUsernameGrpcController,
|
||||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
];
|
||||||
import { AuthenticationMessagerController } from './adapters/primaries/authentication-messager.controller';
|
|
||||||
import { Messager } from './adapters/secondaries/messager';
|
const commandHandlers: Provider[] = [
|
||||||
|
CreateAuthenticationService,
|
||||||
|
DeleteAuthenticationService,
|
||||||
|
AddUsernameService,
|
||||||
|
];
|
||||||
|
|
||||||
|
const mappers: Provider[] = [AuthenticationMapper, UsernameMapper];
|
||||||
|
|
||||||
|
const repositories: Provider[] = [
|
||||||
|
{
|
||||||
|
provide: AUTHENTICATION_REPOSITORY,
|
||||||
|
useClass: AuthenticationRepository,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: USERNAME_REPOSITORY,
|
||||||
|
useClass: UsernameRepository,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const messageBrokers: Provider[] = [
|
||||||
|
{
|
||||||
|
provide: MESSAGE_PUBLISHER,
|
||||||
|
useClass: MessageBrokerPublisher,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const orms: Provider[] = [PrismaService];
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [CqrsModule],
|
||||||
DatabaseModule,
|
controllers: [...grpcControllers],
|
||||||
CqrsModule,
|
|
||||||
RabbitMQModule.forRootAsync(RabbitMQModule, {
|
|
||||||
imports: [ConfigModule],
|
|
||||||
useFactory: async (configService: ConfigService) => ({
|
|
||||||
exchanges: [
|
|
||||||
{
|
|
||||||
name: configService.get<string>('RMQ_EXCHANGE'),
|
|
||||||
type: 'topic',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
handlers: {
|
|
||||||
userUpdate: {
|
|
||||||
exchange: configService.get<string>('RMQ_EXCHANGE'),
|
|
||||||
routingKey: 'user.update',
|
|
||||||
},
|
|
||||||
userDelete: {
|
|
||||||
exchange: configService.get<string>('RMQ_EXCHANGE'),
|
|
||||||
routingKey: 'user.delete',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
uri: configService.get<string>('RMQ_URI'),
|
|
||||||
connectionInitOptions: { wait: false },
|
|
||||||
enableControllerDiscovery: true,
|
|
||||||
}),
|
|
||||||
inject: [ConfigService],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
controllers: [AuthenticationController, AuthenticationMessagerController],
|
|
||||||
providers: [
|
providers: [
|
||||||
AuthenticationProfile,
|
...commandHandlers,
|
||||||
UsernameProfile,
|
...mappers,
|
||||||
AuthenticationRepository,
|
...repositories,
|
||||||
Messager,
|
...messageBrokers,
|
||||||
ValidateAuthenticationUseCase,
|
...orms,
|
||||||
CreateAuthenticationUseCase,
|
],
|
||||||
AddUsernameUseCase,
|
exports: [
|
||||||
UpdateUsernameUseCase,
|
PrismaService,
|
||||||
UpdatePasswordUseCase,
|
AuthenticationMapper,
|
||||||
DeleteUsernameUseCase,
|
AUTHENTICATION_REPOSITORY,
|
||||||
DeleteAuthenticationUseCase,
|
USERNAME_REPOSITORY,
|
||||||
],
|
],
|
||||||
exports: [],
|
|
||||||
})
|
})
|
||||||
export class AuthenticationModule {}
|
export class AuthenticationModule {}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { Command, CommandProps } from '@mobicoop/ddd-library';
|
||||||
|
import { Username } from '../../types/username';
|
||||||
|
|
||||||
|
export class AddUsernameCommand extends Command {
|
||||||
|
readonly userId: string;
|
||||||
|
readonly username: Username;
|
||||||
|
|
||||||
|
constructor(props: CommandProps<AddUsernameCommand>) {
|
||||||
|
super(props);
|
||||||
|
this.userId = props.userId;
|
||||||
|
this.username = props.username;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
||||||
|
import { Inject } from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
AggregateID,
|
||||||
|
ConflictException,
|
||||||
|
UniqueConstraintException,
|
||||||
|
} from '@mobicoop/ddd-library';
|
||||||
|
import {
|
||||||
|
AUTHENTICATION_REPOSITORY,
|
||||||
|
USERNAME_REPOSITORY,
|
||||||
|
} from '@modules/authentication/authentication.di-tokens';
|
||||||
|
import { AuthenticationRepositoryPort } from '../../ports/authentication.repository.port';
|
||||||
|
import { UsernameAlreadyExistsException } from '@modules/authentication/core/domain/authentication.errors';
|
||||||
|
import { AddUsernameCommand } from './add-username.command';
|
||||||
|
import { UsernameRepositoryPort } from '../../ports/username.repository.port';
|
||||||
|
import { UsernameEntity } from '@modules/authentication/core/domain/username.entity';
|
||||||
|
|
||||||
|
@CommandHandler(AddUsernameCommand)
|
||||||
|
export class AddUsernameService implements ICommandHandler {
|
||||||
|
constructor(
|
||||||
|
@Inject(AUTHENTICATION_REPOSITORY)
|
||||||
|
private readonly authenticationRepository: AuthenticationRepositoryPort,
|
||||||
|
@Inject(USERNAME_REPOSITORY)
|
||||||
|
private readonly usernameRepository: UsernameRepositoryPort,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async execute(command: AddUsernameCommand): Promise<AggregateID> {
|
||||||
|
await this.authenticationRepository.findOneById(command.userId, {
|
||||||
|
usernames: true,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const newUsername = await UsernameEntity.create({
|
||||||
|
name: command.username.name,
|
||||||
|
userId: command.userId,
|
||||||
|
type: command.username.type,
|
||||||
|
});
|
||||||
|
await this.usernameRepository.insert(newUsername);
|
||||||
|
return newUsername.id;
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error instanceof ConflictException) {
|
||||||
|
throw new UsernameAlreadyExistsException(error);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
error instanceof UniqueConstraintException &&
|
||||||
|
error.message.includes('username')
|
||||||
|
) {
|
||||||
|
throw new UsernameAlreadyExistsException(error);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { Command, CommandProps } from '@mobicoop/ddd-library';
|
import { Command, CommandProps } from '@mobicoop/ddd-library';
|
||||||
import { Username } from '../types/username';
|
import { Username } from '../../types/username';
|
||||||
|
|
||||||
export class CreateAuthenticationCommand extends Command {
|
export class CreateAuthenticationCommand extends Command {
|
||||||
readonly userId: string;
|
readonly userId: string;
|
|
@ -6,19 +6,19 @@ import {
|
||||||
UniqueConstraintException,
|
UniqueConstraintException,
|
||||||
} from '@mobicoop/ddd-library';
|
} from '@mobicoop/ddd-library';
|
||||||
import { CreateAuthenticationCommand } from './create-authentication.command';
|
import { CreateAuthenticationCommand } from './create-authentication.command';
|
||||||
import { AUTHENTICATION_REPOSITORY } from '@modules/newauthentication/authentication.di-tokens';
|
import { AUTHENTICATION_REPOSITORY } from '@modules/authentication/authentication.di-tokens';
|
||||||
import { AuthenticationRepositoryPort } from '../ports/authentication.repository.port';
|
import { AuthenticationRepositoryPort } from '../../ports/authentication.repository.port';
|
||||||
import { AuthenticationEntity } from '@modules/newauthentication/core/domain/authentication.entity';
|
import { AuthenticationEntity } from '@modules/authentication/core/domain/authentication.entity';
|
||||||
import {
|
import {
|
||||||
AuthenticationAlreadyExistsException,
|
AuthenticationAlreadyExistsException,
|
||||||
UsernameAlreadyExistsException,
|
UsernameAlreadyExistsException,
|
||||||
} from '@modules/newauthentication/core/domain/authentication.errors';
|
} from '@modules/authentication/core/domain/authentication.errors';
|
||||||
|
|
||||||
@CommandHandler(CreateAuthenticationCommand)
|
@CommandHandler(CreateAuthenticationCommand)
|
||||||
export class CreateAuthenticationService implements ICommandHandler {
|
export class CreateAuthenticationService implements ICommandHandler {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(AUTHENTICATION_REPOSITORY)
|
@Inject(AUTHENTICATION_REPOSITORY)
|
||||||
private readonly repository: AuthenticationRepositoryPort,
|
private readonly authenticationRepository: AuthenticationRepositoryPort,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(command: CreateAuthenticationCommand): Promise<AggregateID> {
|
async execute(command: CreateAuthenticationCommand): Promise<AggregateID> {
|
||||||
|
@ -29,7 +29,7 @@ export class CreateAuthenticationService implements ICommandHandler {
|
||||||
usernames: command.usernames,
|
usernames: command.usernames,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await this.repository.insert(authentication);
|
await this.authenticationRepository.insert(authentication);
|
||||||
return authentication.getProps().userId;
|
return authentication.getProps().userId;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error instanceof ConflictException) {
|
if (error instanceof ConflictException) {
|
|
@ -1,9 +1,9 @@
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { DeleteAuthenticationCommand } from './delete-authentication.command';
|
import { DeleteAuthenticationCommand } from './delete-authentication.command';
|
||||||
import { AUTHENTICATION_REPOSITORY } from '@modules/newauthentication/authentication.di-tokens';
|
import { AUTHENTICATION_REPOSITORY } from '@modules/authentication/authentication.di-tokens';
|
||||||
import { AuthenticationRepositoryPort } from '../ports/authentication.repository.port';
|
import { AuthenticationRepositoryPort } from '../../ports/authentication.repository.port';
|
||||||
import { AuthenticationEntity } from '@modules/newauthentication/core/domain/authentication.entity';
|
import { AuthenticationEntity } from '@modules/authentication/core/domain/authentication.entity';
|
||||||
|
|
||||||
@CommandHandler(DeleteAuthenticationCommand)
|
@CommandHandler(DeleteAuthenticationCommand)
|
||||||
export class DeleteAuthenticationService implements ICommandHandler {
|
export class DeleteAuthenticationService implements ICommandHandler {
|
||||||
|
@ -18,9 +18,9 @@ export class DeleteAuthenticationService implements ICommandHandler {
|
||||||
usernames: true,
|
usernames: true,
|
||||||
});
|
});
|
||||||
authentication.delete();
|
authentication.delete();
|
||||||
const deleted: boolean = await this.authenticationRepository.delete(
|
const isDeleted: boolean = await this.authenticationRepository.delete(
|
||||||
authentication,
|
authentication,
|
||||||
);
|
);
|
||||||
return deleted;
|
return isDeleted;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { RepositoryPort } from '@mobicoop/ddd-library';
|
import { RepositoryPort } from '@mobicoop/ddd-library';
|
||||||
import { AuthenticationEntity } from '@modules/newauthentication/core/domain/authentication.entity';
|
import { AuthenticationEntity } from '@modules/authentication/core/domain/authentication.entity';
|
||||||
|
|
||||||
export type AuthenticationRepositoryPort = RepositoryPort<AuthenticationEntity>;
|
export type AuthenticationRepositoryPort = RepositoryPort<AuthenticationEntity>;
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { RepositoryPort } from '@mobicoop/ddd-library';
|
||||||
|
import { UsernameEntity } from '../../domain/username.entity';
|
||||||
|
|
||||||
|
export type UsernameRepositoryPort = RepositoryPort<UsernameEntity>;
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { Type } from '@modules/authentication/core/domain/username.types';
|
||||||
|
|
||||||
|
export type Username = {
|
||||||
|
name: string;
|
||||||
|
type: Type;
|
||||||
|
};
|
|
@ -4,7 +4,7 @@ import {
|
||||||
AuthenticationProps,
|
AuthenticationProps,
|
||||||
CreateAuthenticationProps,
|
CreateAuthenticationProps,
|
||||||
} from './authentication.types';
|
} from './authentication.types';
|
||||||
import { AuthenticationCreatedDomainEvent } from './events/authentication-created.domain-events';
|
import { AuthenticationCreatedDomainEvent } from './events/authentication-created.domain-event';
|
||||||
import { AuthenticationDeletedDomainEvent } from './events/authentication-deleted.domain-event';
|
import { AuthenticationDeletedDomainEvent } from './events/authentication-deleted.domain-event';
|
||||||
|
|
||||||
export class AuthenticationEntity extends AggregateRoot<AuthenticationProps> {
|
export class AuthenticationEntity extends AggregateRoot<AuthenticationProps> {
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { DomainEvent, DomainEventProps } from '@mobicoop/ddd-library';
|
||||||
|
|
||||||
|
export class UsernameAddedDomainEvent extends DomainEvent {
|
||||||
|
constructor(props: DomainEventProps<UsernameAddedDomainEvent>) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { AggregateID, AggregateRoot } from '@mobicoop/ddd-library';
|
||||||
|
import { UsernameProps } from './username.types';
|
||||||
|
import { UsernameAddedDomainEvent } from './events/username-added.domain-event';
|
||||||
|
|
||||||
|
export class UsernameEntity extends AggregateRoot<UsernameProps> {
|
||||||
|
protected readonly _id: AggregateID;
|
||||||
|
|
||||||
|
static create = async (create: UsernameProps): Promise<UsernameEntity> => {
|
||||||
|
const props: UsernameProps = { ...create };
|
||||||
|
const username = new UsernameEntity({
|
||||||
|
id: props.name,
|
||||||
|
props: {
|
||||||
|
name: props.name,
|
||||||
|
userId: props.userId,
|
||||||
|
type: props.type,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
username.addEvent(
|
||||||
|
new UsernameAddedDomainEvent({ aggregateId: props.name }),
|
||||||
|
);
|
||||||
|
return username;
|
||||||
|
};
|
||||||
|
|
||||||
|
validate(): void {
|
||||||
|
// entity business rules validation to protect it's invariant before saving entity to a database
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
// All properties that a Username has
|
// All properties that a Username has
|
||||||
export interface UsernameProps {
|
export interface UsernameProps {
|
||||||
name: string;
|
name: string;
|
||||||
|
userId?: string;
|
||||||
type: Type;
|
type: Type;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import {
|
import {
|
||||||
LoggerBase,
|
LoggerBase,
|
||||||
|
@ -6,7 +6,7 @@ import {
|
||||||
PrismaRepositoryBase,
|
PrismaRepositoryBase,
|
||||||
} from '@mobicoop/ddd-library';
|
} from '@mobicoop/ddd-library';
|
||||||
import { AuthenticationEntity } from '../core/domain/authentication.entity';
|
import { AuthenticationEntity } from '../core/domain/authentication.entity';
|
||||||
import { AuthenticationRepositoryPort } from '../core/application/commands/ports/authentication.repository.port';
|
import { AuthenticationRepositoryPort } from '../core/application/ports/authentication.repository.port';
|
||||||
import { PrismaService } from './prisma.service';
|
import { PrismaService } from './prisma.service';
|
||||||
import { AuthenticationMapper } from '../authentication.mapper';
|
import { AuthenticationMapper } from '../authentication.mapper';
|
||||||
import { MESSAGE_PUBLISHER } from '@src/app.di-tokens';
|
import { MESSAGE_PUBLISHER } from '@src/app.di-tokens';
|
||||||
|
@ -57,12 +57,11 @@ export class AuthenticationRepository
|
||||||
prisma,
|
prisma,
|
||||||
mapper,
|
mapper,
|
||||||
eventEmitter,
|
eventEmitter,
|
||||||
new LoggerBase(
|
new LoggerBase({
|
||||||
AuthenticationRepository.name,
|
logger: new Logger(AuthenticationRepository.name),
|
||||||
'logging',
|
domain: 'auth',
|
||||||
'auth',
|
|
||||||
messagePublisher,
|
messagePublisher,
|
||||||
),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
|
import {
|
||||||
|
LoggerBase,
|
||||||
|
MessagePublisherPort,
|
||||||
|
PrismaRepositoryBase,
|
||||||
|
} from '@mobicoop/ddd-library';
|
||||||
|
import { PrismaService } from './prisma.service';
|
||||||
|
import { MESSAGE_PUBLISHER } from '@src/app.di-tokens';
|
||||||
|
import { UsernameEntity } from '../core/domain/username.entity';
|
||||||
|
import { UsernameRepositoryPort } from '../core/application/ports/username.repository.port';
|
||||||
|
import { UsernameMapper } from '../username.mapper';
|
||||||
|
|
||||||
|
export type UsernameModel = {
|
||||||
|
username: string;
|
||||||
|
authUuid: string;
|
||||||
|
type: string;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repository is used for retrieving/saving domain entities
|
||||||
|
* */
|
||||||
|
@Injectable()
|
||||||
|
export class UsernameRepository
|
||||||
|
extends PrismaRepositoryBase<UsernameEntity, UsernameModel, UsernameModel>
|
||||||
|
implements UsernameRepositoryPort
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
prisma: PrismaService,
|
||||||
|
mapper: UsernameMapper,
|
||||||
|
eventEmitter: EventEmitter2,
|
||||||
|
@Inject(MESSAGE_PUBLISHER)
|
||||||
|
protected readonly messagePublisher: MessagePublisherPort,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
prisma.username,
|
||||||
|
prisma,
|
||||||
|
mapper,
|
||||||
|
eventEmitter,
|
||||||
|
new LoggerBase({
|
||||||
|
logger: new Logger(UsernameRepository.name),
|
||||||
|
domain: 'auth',
|
||||||
|
messagePublisher,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { ResponseBase } from '@mobicoop/ddd-library';
|
||||||
|
|
||||||
|
export class UsernameResponseDto extends ResponseBase {}
|
|
@ -0,0 +1,49 @@
|
||||||
|
import {
|
||||||
|
AggregateID,
|
||||||
|
IdResponse,
|
||||||
|
RpcExceptionCode,
|
||||||
|
RpcValidationPipe,
|
||||||
|
} from '@mobicoop/ddd-library';
|
||||||
|
import { Controller, UsePipes } from '@nestjs/common';
|
||||||
|
import { CommandBus } from '@nestjs/cqrs';
|
||||||
|
import { GrpcMethod, RpcException } from '@nestjs/microservices';
|
||||||
|
import { UsernameAlreadyExistsException } from '@modules/authentication/core/domain/authentication.errors';
|
||||||
|
import { AddUsernameRequestDto } from './dtos/add-username.request.dto';
|
||||||
|
import { AddUsernameCommand } from '@modules/authentication/core/application/commands/add-usernames/add-username.command';
|
||||||
|
|
||||||
|
@UsePipes(
|
||||||
|
new RpcValidationPipe({
|
||||||
|
whitelist: true,
|
||||||
|
forbidUnknownValues: false,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
@Controller()
|
||||||
|
export class AddUsernameGrpcController {
|
||||||
|
constructor(private readonly commandBus: CommandBus) {}
|
||||||
|
|
||||||
|
@GrpcMethod('AuthenticationService', 'AddUsername')
|
||||||
|
async addUsername(data: AddUsernameRequestDto): Promise<IdResponse> {
|
||||||
|
try {
|
||||||
|
const aggregateID: AggregateID = await this.commandBus.execute(
|
||||||
|
new AddUsernameCommand({
|
||||||
|
userId: data.userId,
|
||||||
|
username: {
|
||||||
|
name: data.name,
|
||||||
|
type: data.type,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return new IdResponse(aggregateID);
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error instanceof UsernameAlreadyExistsException)
|
||||||
|
throw new RpcException({
|
||||||
|
code: RpcExceptionCode.ALREADY_EXISTS,
|
||||||
|
message: error.message,
|
||||||
|
});
|
||||||
|
throw new RpcException({
|
||||||
|
code: RpcExceptionCode.UNKNOWN,
|
||||||
|
message: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,8 +8,8 @@ import { Controller, UsePipes } from '@nestjs/common';
|
||||||
import { CommandBus } from '@nestjs/cqrs';
|
import { CommandBus } from '@nestjs/cqrs';
|
||||||
import { GrpcMethod, RpcException } from '@nestjs/microservices';
|
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/newauthentication/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/newauthentication/core/domain/authentication.errors';
|
import { AuthenticationAlreadyExistsException } from '@modules/authentication/core/domain/authentication.errors';
|
||||||
|
|
||||||
@UsePipes(
|
@UsePipes(
|
||||||
new RpcValidationPipe({
|
new RpcValidationPipe({
|
|
@ -7,7 +7,7 @@ import {
|
||||||
import { Controller, UsePipes } from '@nestjs/common';
|
import { Controller, UsePipes } from '@nestjs/common';
|
||||||
import { CommandBus } from '@nestjs/cqrs';
|
import { CommandBus } from '@nestjs/cqrs';
|
||||||
import { GrpcMethod, RpcException } from '@nestjs/microservices';
|
import { GrpcMethod, RpcException } from '@nestjs/microservices';
|
||||||
import { DeleteAuthenticationCommand } from '@modules/newauthentication/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';
|
||||||
|
|
||||||
@UsePipes(
|
@UsePipes(
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
import { UsernameDto } from './username.dto';
|
||||||
|
|
||||||
|
export class AddUsernameRequestDto extends UsernameDto {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
userId: string;
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { Type } from '@modules/authentication/core/domain/username.types';
|
||||||
|
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
import { IsValidUsername } from './validators/decorators/is-valid-username.decorator';
|
||||||
|
|
||||||
|
export class UsernameDto {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsValidUsername({
|
||||||
|
message: 'Invalid username',
|
||||||
|
})
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@IsEnum(Type)
|
||||||
|
@IsNotEmpty()
|
||||||
|
type: Type;
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { Type } from '@modules/authentication/core/domain/username.types';
|
||||||
|
import {
|
||||||
|
registerDecorator,
|
||||||
|
ValidationOptions,
|
||||||
|
ValidationArguments,
|
||||||
|
isEmail,
|
||||||
|
isPhoneNumber,
|
||||||
|
} from 'class-validator';
|
||||||
|
|
||||||
|
export function IsValidUsername(validationOptions?: ValidationOptions) {
|
||||||
|
return function (object: any, propertyName: string) {
|
||||||
|
registerDecorator({
|
||||||
|
name: 'isValidUsername',
|
||||||
|
target: object.constructor,
|
||||||
|
propertyName: propertyName,
|
||||||
|
options: validationOptions,
|
||||||
|
validator: {
|
||||||
|
validate(value: any, args: ValidationArguments) {
|
||||||
|
const usernameType: Type = args.object['type'];
|
||||||
|
switch (usernameType) {
|
||||||
|
case Type.PHONE:
|
||||||
|
return isPhoneNumber(value);
|
||||||
|
case Type.EMAIL:
|
||||||
|
return isEmail(value);
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
import { IdResponse } from '@mobicoop/ddd-library';
|
||||||
|
import { RpcExceptionCode } from '@mobicoop/ddd-library';
|
||||||
|
import { UsernameAlreadyExistsException } from '@modules/authentication/core/domain/authentication.errors';
|
||||||
|
import { Type } from '@modules/authentication/core/domain/username.types';
|
||||||
|
import { AddUsernameGrpcController } from '@modules/authentication/interface/grpc-controllers/add-username.grpc.controller';
|
||||||
|
import { AddUsernameRequestDto } from '@modules/authentication/interface/grpc-controllers/dtos/add-username.request.dto';
|
||||||
|
import { CommandBus } from '@nestjs/cqrs';
|
||||||
|
import { RpcException } from '@nestjs/microservices';
|
||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
|
||||||
|
const addUsernameRequest: AddUsernameRequestDto = {
|
||||||
|
userId: '78153e03-4861-4f58-a705-88526efee53b',
|
||||||
|
name: 'john.doe@email.com',
|
||||||
|
type: Type.EMAIL,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockCommandBus = {
|
||||||
|
execute: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementationOnce(() => 'john.doe@email.com')
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
throw new UsernameAlreadyExistsException();
|
||||||
|
})
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
throw new Error();
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Add Username Grpc Controller', () => {
|
||||||
|
let addUsernameGrpcController: AddUsernameGrpcController;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: CommandBus,
|
||||||
|
useValue: mockCommandBus,
|
||||||
|
},
|
||||||
|
AddUsernameGrpcController,
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
addUsernameGrpcController = module.get<AddUsernameGrpcController>(
|
||||||
|
AddUsernameGrpcController,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(addUsernameGrpcController).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add a new username', async () => {
|
||||||
|
jest.spyOn(mockCommandBus, 'execute');
|
||||||
|
const result: IdResponse = await addUsernameGrpcController.addUsername(
|
||||||
|
addUsernameRequest,
|
||||||
|
);
|
||||||
|
expect(result).toBeInstanceOf(IdResponse);
|
||||||
|
expect(result.id).toBe('john.doe@email.com');
|
||||||
|
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a dedicated RpcException if username already exists', async () => {
|
||||||
|
jest.spyOn(mockCommandBus, 'execute');
|
||||||
|
expect.assertions(3);
|
||||||
|
try {
|
||||||
|
await addUsernameGrpcController.addUsername(addUsernameRequest);
|
||||||
|
} catch (e: any) {
|
||||||
|
expect(e).toBeInstanceOf(RpcException);
|
||||||
|
expect(e.error.code).toBe(RpcExceptionCode.ALREADY_EXISTS);
|
||||||
|
}
|
||||||
|
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a generic RpcException', async () => {
|
||||||
|
jest.spyOn(mockCommandBus, 'execute');
|
||||||
|
expect.assertions(3);
|
||||||
|
try {
|
||||||
|
await addUsernameGrpcController.addUsername(addUsernameRequest);
|
||||||
|
} catch (e: any) {
|
||||||
|
expect(e).toBeInstanceOf(RpcException);
|
||||||
|
expect(e.error.code).toBe(RpcExceptionCode.UNKNOWN);
|
||||||
|
}
|
||||||
|
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,99 @@
|
||||||
|
import { IdResponse } from '@mobicoop/ddd-library';
|
||||||
|
import { RpcExceptionCode } from '@mobicoop/ddd-library';
|
||||||
|
import { AuthenticationAlreadyExistsException } from '@modules/authentication/core/domain/authentication.errors';
|
||||||
|
import { Type } from '@modules/authentication/core/domain/username.types';
|
||||||
|
import { CreateAuthenticationGrpcController } from '@modules/authentication/interface/grpc-controllers/create-authentication.grpc.controller';
|
||||||
|
import { CreateAuthenticationRequestDto } from '@modules/authentication/interface/grpc-controllers/dtos/create-authentication.request.dto';
|
||||||
|
import { CommandBus } from '@nestjs/cqrs';
|
||||||
|
import { RpcException } from '@nestjs/microservices';
|
||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
|
||||||
|
const createAuthenticationRequest: CreateAuthenticationRequestDto = {
|
||||||
|
userId: '78153e03-4861-4f58-a705-88526efee53b',
|
||||||
|
password: 'John123',
|
||||||
|
usernames: [
|
||||||
|
{
|
||||||
|
name: 'john.doe@email.com',
|
||||||
|
type: Type.EMAIL,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockCommandBus = {
|
||||||
|
execute: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementationOnce(() => '78153e03-4861-4f58-a705-88526efee53b')
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
throw new AuthenticationAlreadyExistsException();
|
||||||
|
})
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
throw new Error();
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Create Authentication Grpc Controller', () => {
|
||||||
|
let createAuthenticationGrpcController: CreateAuthenticationGrpcController;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: CommandBus,
|
||||||
|
useValue: mockCommandBus,
|
||||||
|
},
|
||||||
|
CreateAuthenticationGrpcController,
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
createAuthenticationGrpcController =
|
||||||
|
module.get<CreateAuthenticationGrpcController>(
|
||||||
|
CreateAuthenticationGrpcController,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(createAuthenticationGrpcController).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a new authentication', async () => {
|
||||||
|
jest.spyOn(mockCommandBus, 'execute');
|
||||||
|
const result: IdResponse = await createAuthenticationGrpcController.create(
|
||||||
|
createAuthenticationRequest,
|
||||||
|
);
|
||||||
|
expect(result).toBeInstanceOf(IdResponse);
|
||||||
|
expect(result.id).toBe('78153e03-4861-4f58-a705-88526efee53b');
|
||||||
|
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a dedicated RpcException if authentication already exists', async () => {
|
||||||
|
jest.spyOn(mockCommandBus, 'execute');
|
||||||
|
expect.assertions(3);
|
||||||
|
try {
|
||||||
|
await createAuthenticationGrpcController.create(
|
||||||
|
createAuthenticationRequest,
|
||||||
|
);
|
||||||
|
} catch (e: any) {
|
||||||
|
expect(e).toBeInstanceOf(RpcException);
|
||||||
|
expect(e.error.code).toBe(RpcExceptionCode.ALREADY_EXISTS);
|
||||||
|
}
|
||||||
|
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a generic RpcException', async () => {
|
||||||
|
jest.spyOn(mockCommandBus, 'execute');
|
||||||
|
expect.assertions(3);
|
||||||
|
try {
|
||||||
|
await createAuthenticationGrpcController.create(
|
||||||
|
createAuthenticationRequest,
|
||||||
|
);
|
||||||
|
} catch (e: any) {
|
||||||
|
expect(e).toBeInstanceOf(RpcException);
|
||||||
|
expect(e.error.code).toBe(RpcExceptionCode.UNKNOWN);
|
||||||
|
}
|
||||||
|
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,108 @@
|
||||||
|
import {
|
||||||
|
DatabaseErrorException,
|
||||||
|
NotFoundException,
|
||||||
|
} from '@mobicoop/ddd-library';
|
||||||
|
import { RpcExceptionCode } from '@mobicoop/ddd-library';
|
||||||
|
import { DeleteAuthenticationGrpcController } from '@modules/authentication/interface/grpc-controllers/delete-authentication.grpc.controller';
|
||||||
|
import { DeleteAuthenticationRequestDto } from '@modules/authentication/interface/grpc-controllers/dtos/delete-authentication.request.dto';
|
||||||
|
import { CommandBus } from '@nestjs/cqrs';
|
||||||
|
import { RpcException } from '@nestjs/microservices';
|
||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
|
||||||
|
const deleteAuthenticationRequest: DeleteAuthenticationRequestDto = {
|
||||||
|
userId: '78153e03-4861-4f58-a705-88526efee53b',
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockCommandBus = {
|
||||||
|
execute: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementationOnce(() => ({}))
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
throw new NotFoundException();
|
||||||
|
})
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
throw new DatabaseErrorException();
|
||||||
|
})
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
throw new Error();
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Delete Authentication Grpc Controller', () => {
|
||||||
|
let deleteAuthenticationGrpcController: DeleteAuthenticationGrpcController;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: CommandBus,
|
||||||
|
useValue: mockCommandBus,
|
||||||
|
},
|
||||||
|
DeleteAuthenticationGrpcController,
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
deleteAuthenticationGrpcController =
|
||||||
|
module.get<DeleteAuthenticationGrpcController>(
|
||||||
|
DeleteAuthenticationGrpcController,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(deleteAuthenticationGrpcController).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a new authentication', async () => {
|
||||||
|
jest.spyOn(mockCommandBus, 'execute');
|
||||||
|
await deleteAuthenticationGrpcController.delete(
|
||||||
|
deleteAuthenticationRequest,
|
||||||
|
);
|
||||||
|
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a dedicated RpcException if authentication does not exist', async () => {
|
||||||
|
jest.spyOn(mockCommandBus, 'execute');
|
||||||
|
expect.assertions(3);
|
||||||
|
try {
|
||||||
|
await deleteAuthenticationGrpcController.delete(
|
||||||
|
deleteAuthenticationRequest,
|
||||||
|
);
|
||||||
|
} catch (e: any) {
|
||||||
|
expect(e).toBeInstanceOf(RpcException);
|
||||||
|
expect(e.error.code).toBe(RpcExceptionCode.NOT_FOUND);
|
||||||
|
}
|
||||||
|
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a dedicated RpcException if a database error occurs', async () => {
|
||||||
|
jest.spyOn(mockCommandBus, 'execute');
|
||||||
|
expect.assertions(3);
|
||||||
|
try {
|
||||||
|
await deleteAuthenticationGrpcController.delete(
|
||||||
|
deleteAuthenticationRequest,
|
||||||
|
);
|
||||||
|
} catch (e: any) {
|
||||||
|
expect(e).toBeInstanceOf(RpcException);
|
||||||
|
expect(e.error.code).toBe(RpcExceptionCode.INTERNAL);
|
||||||
|
}
|
||||||
|
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a generic RpcException', async () => {
|
||||||
|
jest.spyOn(mockCommandBus, 'execute');
|
||||||
|
expect.assertions(3);
|
||||||
|
try {
|
||||||
|
await deleteAuthenticationGrpcController.delete(
|
||||||
|
deleteAuthenticationRequest,
|
||||||
|
);
|
||||||
|
} catch (e: any) {
|
||||||
|
expect(e).toBeInstanceOf(RpcException);
|
||||||
|
expect(e.error.code).toBe(RpcExceptionCode.UNKNOWN);
|
||||||
|
}
|
||||||
|
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { Type } from '@modules/authentication/core/domain/username.types';
|
||||||
|
import { IsValidUsername } from '@modules/authentication/interface/grpc-controllers/dtos/validators/decorators/is-valid-username.decorator';
|
||||||
|
import { Validator } from 'class-validator';
|
||||||
|
|
||||||
|
describe('Username decorator', () => {
|
||||||
|
class MyClass {
|
||||||
|
@IsValidUsername({
|
||||||
|
message: 'Invalid username',
|
||||||
|
})
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
type: Type;
|
||||||
|
}
|
||||||
|
it('should return a property decorator has a function', () => {
|
||||||
|
const isValidUsername = IsValidUsername();
|
||||||
|
expect(typeof isValidUsername).toBe('function');
|
||||||
|
});
|
||||||
|
it('should validate a valid phone username', async () => {
|
||||||
|
const myClassInstance = new MyClass();
|
||||||
|
myClassInstance.name = '+33611223344';
|
||||||
|
myClassInstance.type = Type.PHONE;
|
||||||
|
const validator = new Validator();
|
||||||
|
const validation = await validator.validate(myClassInstance);
|
||||||
|
expect(validation.length).toBe(0);
|
||||||
|
});
|
||||||
|
it('should validate a valid email username', async () => {
|
||||||
|
const myClassInstance = new MyClass();
|
||||||
|
myClassInstance.name = 'john.doe@email.com';
|
||||||
|
myClassInstance.type = Type.EMAIL;
|
||||||
|
const validator = new Validator();
|
||||||
|
const validation = await validator.validate(myClassInstance);
|
||||||
|
expect(validation.length).toBe(0);
|
||||||
|
});
|
||||||
|
it('should not validate an invalid phone username', async () => {
|
||||||
|
const myClassInstance = new MyClass();
|
||||||
|
myClassInstance.name = '11223344';
|
||||||
|
myClassInstance.type = Type.PHONE;
|
||||||
|
const validator = new Validator();
|
||||||
|
const validation = await validator.validate(myClassInstance);
|
||||||
|
expect(validation.length).toBe(1);
|
||||||
|
});
|
||||||
|
it('should not validate an invalid email username', async () => {
|
||||||
|
const myClassInstance = new MyClass();
|
||||||
|
myClassInstance.name = 'john.doe.email.com';
|
||||||
|
myClassInstance.type = Type.EMAIL;
|
||||||
|
const validator = new Validator();
|
||||||
|
const validation = await validator.validate(myClassInstance);
|
||||||
|
expect(validation.length).toBe(1);
|
||||||
|
});
|
||||||
|
it('should not validate if type is not set', async () => {
|
||||||
|
const myClassInstance = new MyClass();
|
||||||
|
myClassInstance.name = 'john.doe@email.com';
|
||||||
|
const validator = new Validator();
|
||||||
|
const validation = await validator.validate(myClassInstance);
|
||||||
|
expect(validation.length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { Mapper } from '@mobicoop/ddd-library';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Type } from './core/domain/username.types';
|
||||||
|
import { UsernameEntity } from './core/domain/username.entity';
|
||||||
|
import { UsernameModel } from './infrastructure/username.repository';
|
||||||
|
import { UsernameResponseDto } from './interface/dtos/username.response.dto';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapper constructs objects that are used in different layers:
|
||||||
|
* Record is an object that is stored in a database,
|
||||||
|
* Entity is an object that is used in application domain layer,
|
||||||
|
* and a ResponseDTO is an object returned to a user (usually as json).
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UsernameMapper
|
||||||
|
implements
|
||||||
|
Mapper<UsernameEntity, UsernameModel, UsernameModel, UsernameResponseDto>
|
||||||
|
{
|
||||||
|
toPersistence = (entity: UsernameEntity): UsernameModel => {
|
||||||
|
const copy = entity.getProps();
|
||||||
|
const record: UsernameModel = {
|
||||||
|
authUuid: copy.userId,
|
||||||
|
username: copy.name,
|
||||||
|
type: copy.type,
|
||||||
|
createdAt: copy.createdAt,
|
||||||
|
updatedAt: copy.updatedAt,
|
||||||
|
};
|
||||||
|
return record;
|
||||||
|
};
|
||||||
|
|
||||||
|
toDomain = (record: UsernameModel): UsernameEntity => {
|
||||||
|
const entity = new UsernameEntity({
|
||||||
|
id: record.authUuid,
|
||||||
|
createdAt: new Date(record.createdAt),
|
||||||
|
updatedAt: new Date(record.updatedAt),
|
||||||
|
props: {
|
||||||
|
name: record.username,
|
||||||
|
type: Type[record.type],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return entity;
|
||||||
|
};
|
||||||
|
|
||||||
|
toResponse = (entity: UsernameEntity): UsernameResponseDto => {
|
||||||
|
const response = new UsernameResponseDto(entity);
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { AuthenticationRepository } from '../authentication/adapters/secondaries/authentication.repository';
|
import { AuthenticationRepository } from '../oldauthentication/adapters/secondaries/authentication.repository';
|
||||||
import { UsernameRepository } from '../authentication/adapters/secondaries/username.repository';
|
import { UsernameRepository } from '../oldauthentication/adapters/secondaries/username.repository';
|
||||||
import { PrismaService } from './adapters/secondaries/prisma-service';
|
import { PrismaService } from './adapters/secondaries/prisma-service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {
|
||||||
HealthIndicator,
|
HealthIndicator,
|
||||||
HealthIndicatorResult,
|
HealthIndicatorResult,
|
||||||
} from '@nestjs/terminus';
|
} from '@nestjs/terminus';
|
||||||
import { AuthenticationRepository } from '../../../authentication/adapters/secondaries/authentication.repository';
|
import { AuthenticationRepository } from '../../../oldauthentication/adapters/secondaries/authentication.repository';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PrismaHealthIndicatorUseCase extends HealthIndicator {
|
export class PrismaHealthIndicatorUseCase extends HealthIndicator {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { HealthServerController } from './adapters/primaries/health-server.controller';
|
import { HealthServerController } from './adapters/primaries/health-server.controller';
|
||||||
import { PrismaHealthIndicatorUseCase } from './domain/usecases/prisma.health-indicator.usecase';
|
import { PrismaHealthIndicatorUseCase } from './domain/usecases/prisma.health-indicator.usecase';
|
||||||
import { AuthenticationRepository } from '../authentication/adapters/secondaries/authentication.repository';
|
import { AuthenticationRepository } from '../oldauthentication/adapters/secondaries/authentication.repository';
|
||||||
import { DatabaseModule } from '../database/database.module';
|
import { DatabaseModule } from '../database/database.module';
|
||||||
import { HealthController } from './adapters/primaries/health.controller';
|
import { HealthController } from './adapters/primaries/health.controller';
|
||||||
import { TerminusModule } from '@nestjs/terminus';
|
import { TerminusModule } from '@nestjs/terminus';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { PrismaHealthIndicatorUseCase } from '../../domain/usecases/prisma.health-indicator.usecase';
|
import { PrismaHealthIndicatorUseCase } from '../../domain/usecases/prisma.health-indicator.usecase';
|
||||||
import { AuthenticationRepository } from '../../../authentication/adapters/secondaries/authentication.repository';
|
import { AuthenticationRepository } from '../../../oldauthentication/adapters/secondaries/authentication.repository';
|
||||||
import { PrismaClientKnownRequestError } from '@prisma/client/runtime';
|
import { PrismaClientKnownRequestError } from '@prisma/client/runtime';
|
||||||
import { HealthCheckError, HealthIndicatorResult } from '@nestjs/terminus';
|
import { HealthCheckError, HealthIndicatorResult } from '@nestjs/terminus';
|
||||||
|
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
import { Module, Provider } from '@nestjs/common';
|
|
||||||
import { CreateAuthenticationGrpcController } from './interface/grpc-controllers/create-authentication.grpc.controller';
|
|
||||||
import { CreateAuthenticationService } from './core/application/commands/create-authentication/create-authentication.service';
|
|
||||||
import { AuthenticationMapper } from './authentication.mapper';
|
|
||||||
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,
|
|
||||||
DeleteAuthenticationGrpcController,
|
|
||||||
];
|
|
||||||
|
|
||||||
const commandHandlers: Provider[] = [
|
|
||||||
CreateAuthenticationService,
|
|
||||||
DeleteAuthenticationService,
|
|
||||||
];
|
|
||||||
|
|
||||||
const mappers: Provider[] = [AuthenticationMapper];
|
|
||||||
|
|
||||||
const repositories: Provider[] = [
|
|
||||||
{
|
|
||||||
provide: AUTHENTICATION_REPOSITORY,
|
|
||||||
useClass: AuthenticationRepository,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const messageBrokers: Provider[] = [
|
|
||||||
{
|
|
||||||
provide: MESSAGE_PUBLISHER,
|
|
||||||
useClass: MessageBrokerPublisher,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const orms: Provider[] = [PrismaService];
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [CqrsModule],
|
|
||||||
controllers: [...grpcControllers],
|
|
||||||
providers: [
|
|
||||||
...commandHandlers,
|
|
||||||
...mappers,
|
|
||||||
...repositories,
|
|
||||||
...messageBrokers,
|
|
||||||
...orms,
|
|
||||||
],
|
|
||||||
exports: [PrismaService, AuthenticationMapper, AUTHENTICATION_REPOSITORY],
|
|
||||||
})
|
|
||||||
export class AuthenticationModule {}
|
|
|
@ -1,6 +0,0 @@
|
||||||
import { Type } from '@modules/newauthentication/core/domain/username.types';
|
|
||||||
|
|
||||||
export type Username = {
|
|
||||||
name: string;
|
|
||||||
type: Type;
|
|
||||||
};
|
|
|
@ -1,11 +0,0 @@
|
||||||
import { Type } from '@modules/newauthentication/core/domain/username.types';
|
|
||||||
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
|
|
||||||
|
|
||||||
export class UsernameDto {
|
|
||||||
@IsString()
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@IsEnum(Type)
|
|
||||||
@IsNotEmpty()
|
|
||||||
type: Type;
|
|
||||||
}
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { CqrsModule } from '@nestjs/cqrs';
|
||||||
|
import { DatabaseModule } from '../database/database.module';
|
||||||
|
import { AuthenticationController } from './adapters/primaries/authentication.controller';
|
||||||
|
import { CreateAuthenticationUseCase } from './domain/usecases/create-authentication.usecase';
|
||||||
|
import { ValidateAuthenticationUseCase } from './domain/usecases/validate-authentication.usecase';
|
||||||
|
import { AuthenticationProfile } from './mappers/authentication.profile';
|
||||||
|
import { AuthenticationRepository } from './adapters/secondaries/authentication.repository';
|
||||||
|
import { UpdateUsernameUseCase } from './domain/usecases/update-username.usecase';
|
||||||
|
import { UsernameProfile } from './mappers/username.profile';
|
||||||
|
import { AddUsernameUseCase } from './domain/usecases/add-username.usecase';
|
||||||
|
import { UpdatePasswordUseCase } from './domain/usecases/update-password.usecase';
|
||||||
|
import { DeleteUsernameUseCase } from './domain/usecases/delete-username.usecase';
|
||||||
|
import { DeleteAuthenticationUseCase } from './domain/usecases/delete-authentication.usecase';
|
||||||
|
import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq';
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
import { AuthenticationMessagerController } from './adapters/primaries/authentication-messager.controller';
|
||||||
|
import { Messager } from './adapters/secondaries/messager';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
DatabaseModule,
|
||||||
|
CqrsModule,
|
||||||
|
RabbitMQModule.forRootAsync(RabbitMQModule, {
|
||||||
|
imports: [ConfigModule],
|
||||||
|
useFactory: async (configService: ConfigService) => ({
|
||||||
|
exchanges: [
|
||||||
|
{
|
||||||
|
name: configService.get<string>('RMQ_EXCHANGE'),
|
||||||
|
type: 'topic',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
handlers: {
|
||||||
|
userUpdate: {
|
||||||
|
exchange: configService.get<string>('RMQ_EXCHANGE'),
|
||||||
|
routingKey: 'user.update',
|
||||||
|
},
|
||||||
|
userDelete: {
|
||||||
|
exchange: configService.get<string>('RMQ_EXCHANGE'),
|
||||||
|
routingKey: 'user.delete',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
uri: configService.get<string>('RMQ_URI'),
|
||||||
|
connectionInitOptions: { wait: false },
|
||||||
|
enableControllerDiscovery: true,
|
||||||
|
}),
|
||||||
|
inject: [ConfigService],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
controllers: [AuthenticationController, AuthenticationMessagerController],
|
||||||
|
providers: [
|
||||||
|
AuthenticationProfile,
|
||||||
|
UsernameProfile,
|
||||||
|
AuthenticationRepository,
|
||||||
|
Messager,
|
||||||
|
ValidateAuthenticationUseCase,
|
||||||
|
CreateAuthenticationUseCase,
|
||||||
|
AddUsernameUseCase,
|
||||||
|
UpdateUsernameUseCase,
|
||||||
|
UpdatePasswordUseCase,
|
||||||
|
DeleteUsernameUseCase,
|
||||||
|
DeleteAuthenticationUseCase,
|
||||||
|
],
|
||||||
|
exports: [],
|
||||||
|
})
|
||||||
|
export class AuthenticationModule {}
|
|
@ -1,5 +1,5 @@
|
||||||
import { ArgumentMetadata } from '@nestjs/common';
|
import { ArgumentMetadata } from '@nestjs/common';
|
||||||
import { ValidateAuthenticationRequest } from '../../../modules/authentication/domain/dtos/validate-authentication.request';
|
import { ValidateAuthenticationRequest } from '../../../modules/oldauthentication/domain/dtos/validate-authentication.request';
|
||||||
import { RpcValidationPipe } from '../../pipes/rpc.validation-pipe';
|
import { RpcValidationPipe } from '../../pipes/rpc.validation-pipe';
|
||||||
|
|
||||||
describe('RpcValidationPipe', () => {
|
describe('RpcValidationPipe', () => {
|
||||||
|
|
Loading…
Reference in New Issue