WIP handle unique constraint exception
This commit is contained in:
parent
0162066557
commit
f33f679e12
File diff suppressed because it is too large
Load Diff
|
@ -36,11 +36,13 @@
|
||||||
"@golevelup/nestjs-rabbitmq": "^3.4.0",
|
"@golevelup/nestjs-rabbitmq": "^3.4.0",
|
||||||
"@grpc/grpc-js": "^1.8.0",
|
"@grpc/grpc-js": "^1.8.0",
|
||||||
"@grpc/proto-loader": "^0.7.4",
|
"@grpc/proto-loader": "^0.7.4",
|
||||||
|
"@mobicoop/ddd-library": "file:../../packages/dddlibrary",
|
||||||
"@nestjs/axios": "^1.0.1",
|
"@nestjs/axios": "^1.0.1",
|
||||||
"@nestjs/common": "^9.0.0",
|
"@nestjs/common": "^9.0.0",
|
||||||
"@nestjs/config": "^2.2.0",
|
"@nestjs/config": "^2.2.0",
|
||||||
"@nestjs/core": "^9.0.0",
|
"@nestjs/core": "^9.0.0",
|
||||||
"@nestjs/cqrs": "^9.0.1",
|
"@nestjs/cqrs": "^9.0.1",
|
||||||
|
"@nestjs/event-emitter": "^2.0.0",
|
||||||
"@nestjs/microservices": "^9.2.1",
|
"@nestjs/microservices": "^9.2.1",
|
||||||
"@nestjs/platform-express": "^9.0.0",
|
"@nestjs/platform-express": "^9.0.0",
|
||||||
"@nestjs/terminus": "^9.2.2",
|
"@nestjs/terminus": "^9.2.2",
|
||||||
|
|
|
@ -14,7 +14,7 @@ CREATE TABLE "auth" (
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "username" (
|
CREATE TABLE "username" (
|
||||||
"username" TEXT NOT NULL,
|
"username" TEXT NOT NULL,
|
||||||
"uuid" UUID NOT NULL,
|
"authUuid" UUID NOT NULL,
|
||||||
"type" "Type" NOT NULL DEFAULT 'EMAIL',
|
"type" "Type" NOT NULL DEFAULT 'EMAIL',
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
@ -23,4 +23,7 @@ CREATE TABLE "username" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "username_uuid_type_key" ON "username"("uuid", "type");
|
CREATE UNIQUE INDEX "username_authUuid_type_key" ON "username"("authUuid", "type");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "username" ADD CONSTRAINT "username_authUuid_fkey" FOREIGN KEY ("authUuid") REFERENCES "auth"("uuid") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
@ -11,22 +11,24 @@ datasource db {
|
||||||
}
|
}
|
||||||
|
|
||||||
model Auth {
|
model Auth {
|
||||||
uuid String @id @db.Uuid
|
uuid String @id @default(uuid()) @db.Uuid
|
||||||
password String
|
password String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
usernames Username[]
|
||||||
|
|
||||||
@@map("auth")
|
@@map("auth")
|
||||||
}
|
}
|
||||||
|
|
||||||
model Username {
|
model Username {
|
||||||
username String @id
|
username String @id
|
||||||
uuid String @db.Uuid
|
authUuid String @db.Uuid
|
||||||
type Type @default(EMAIL) // type is needed in case of username update
|
type Type @default(EMAIL) // type is needed in case of username update
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
Auth Auth @relation(fields: [authUuid], references: [uuid], onDelete: Cascade)
|
||||||
|
|
||||||
@@unique([uuid, type])
|
@@unique([authUuid, type])
|
||||||
@@map("username")
|
@@map("username")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,15 @@ import { classes } from '@automapper/classes';
|
||||||
import { AutomapperModule } from '@automapper/nestjs';
|
import { AutomapperModule } from '@automapper/nestjs';
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
import { AuthenticationModule } from './modules/authentication/authentication.module';
|
|
||||||
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 { EventEmitterModule } from '@nestjs/event-emitter';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot({ isGlobal: true }),
|
ConfigModule.forRoot({ isGlobal: true }),
|
||||||
|
EventEmitterModule.forRoot(),
|
||||||
AutomapperModule.forRoot({ strategyInitializer: classes() }),
|
AutomapperModule.forRoot({ strategyInitializer: classes() }),
|
||||||
AuthenticationModule,
|
AuthenticationModule,
|
||||||
AuthorizationModule,
|
AuthorizationModule,
|
||||||
|
|
|
@ -15,7 +15,7 @@ async function bootstrap() {
|
||||||
protoPath: [
|
protoPath: [
|
||||||
join(
|
join(
|
||||||
__dirname,
|
__dirname,
|
||||||
'modules/authentication/adapters/primaries/authentication.proto',
|
'modules/newauthentication/interface/grpc-controllers/authentication.proto',
|
||||||
),
|
),
|
||||||
join(
|
join(
|
||||||
__dirname,
|
__dirname,
|
||||||
|
|
|
@ -79,7 +79,7 @@ export abstract class PrismaRepository<T> implements IRepository<T> {
|
||||||
|
|
||||||
// TODO : using any is not good, but needed for nested entities
|
// TODO : using any is not good, but needed for nested entities
|
||||||
// TODO : Refactor for good clean architecture ?
|
// TODO : Refactor for good clean architecture ?
|
||||||
create = async (entity: Partial<T> | any, include?: any): Promise<T> => {
|
async create(entity: Partial<T> | any, include?: any): Promise<T> {
|
||||||
try {
|
try {
|
||||||
const res = await this.prisma[this.model].create({
|
const res = await this.prisma[this.model].create({
|
||||||
data: entity,
|
data: entity,
|
||||||
|
@ -98,7 +98,7 @@ export abstract class PrismaRepository<T> implements IRepository<T> {
|
||||||
throw new DatabaseException();
|
throw new DatabaseException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
update = async (uuid: string, entity: Partial<T>): Promise<T> => {
|
update = async (uuid: string, entity: Partial<T>): Promise<T> => {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export const AUTHENTICATION_REPOSITORY = Symbol('AUTHENTICATION_REPOSITORY');
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { Mapper } from '@mobicoop/ddd-library';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { AuthenticationEntity } from './core/domain/authentication.entity';
|
||||||
|
import { AuthenticationResponseDto } from './interface/dtos/authentication.response.dto';
|
||||||
|
import {
|
||||||
|
AuthenticationReadModel,
|
||||||
|
AuthenticationWriteModel,
|
||||||
|
UsernameModel,
|
||||||
|
} from './infrastructure/authentication.repository';
|
||||||
|
import { Type, UsernameProps } from './core/domain/username.types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 AuthenticationMapper
|
||||||
|
implements
|
||||||
|
Mapper<
|
||||||
|
AuthenticationEntity,
|
||||||
|
AuthenticationReadModel,
|
||||||
|
AuthenticationWriteModel,
|
||||||
|
AuthenticationResponseDto
|
||||||
|
>
|
||||||
|
{
|
||||||
|
toPersistence = (entity: AuthenticationEntity): AuthenticationWriteModel => {
|
||||||
|
const copy = entity.getProps();
|
||||||
|
const record: AuthenticationWriteModel = {
|
||||||
|
uuid: copy.id,
|
||||||
|
password: copy.password,
|
||||||
|
usernames: {
|
||||||
|
create: copy.usernames.map((username: UsernameProps) => ({
|
||||||
|
username: username.name,
|
||||||
|
type: username.type,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
createdAt: copy.createdAt,
|
||||||
|
updatedAt: copy.updatedAt,
|
||||||
|
};
|
||||||
|
return record;
|
||||||
|
};
|
||||||
|
|
||||||
|
toDomain = (record: AuthenticationReadModel): AuthenticationEntity => {
|
||||||
|
const entity = new AuthenticationEntity({
|
||||||
|
id: record.uuid,
|
||||||
|
createdAt: new Date(record.createdAt),
|
||||||
|
updatedAt: new Date(record.updatedAt),
|
||||||
|
props: {
|
||||||
|
password: record.password,
|
||||||
|
usernames: record.usernames.map((username: UsernameModel) => ({
|
||||||
|
name: username.username,
|
||||||
|
type: Type[username.type],
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return entity;
|
||||||
|
};
|
||||||
|
|
||||||
|
toResponse = (entity: AuthenticationEntity): AuthenticationResponseDto => {
|
||||||
|
const response = new AuthenticationResponseDto(entity);
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
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';
|
||||||
|
|
||||||
|
const grpcControllers = [CreateAuthenticationGrpcController];
|
||||||
|
|
||||||
|
const commandHandlers: Provider[] = [CreateAuthenticationService];
|
||||||
|
|
||||||
|
const mappers: Provider[] = [AuthenticationMapper];
|
||||||
|
|
||||||
|
const repositories: Provider[] = [
|
||||||
|
{
|
||||||
|
provide: AUTHENTICATION_REPOSITORY,
|
||||||
|
useClass: AuthenticationRepository,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const orms: Provider[] = [PrismaService];
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [CqrsModule],
|
||||||
|
controllers: [...grpcControllers],
|
||||||
|
providers: [...commandHandlers, ...mappers, ...repositories, ...orms],
|
||||||
|
exports: [PrismaService, AuthenticationMapper, AUTHENTICATION_REPOSITORY],
|
||||||
|
})
|
||||||
|
export class AuthenticationModule {}
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { Command, CommandProps } from '@mobicoop/ddd-library';
|
||||||
|
import { Username } from '../types/username';
|
||||||
|
|
||||||
|
export class CreateAuthenticationCommand extends Command {
|
||||||
|
readonly password: string;
|
||||||
|
readonly usernames: Username[];
|
||||||
|
|
||||||
|
constructor(props: CommandProps<CreateAuthenticationCommand>) {
|
||||||
|
super(props);
|
||||||
|
this.password = props.password;
|
||||||
|
this.usernames = props.usernames;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
||||||
|
import { Inject } from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
AggregateID,
|
||||||
|
ConflictException,
|
||||||
|
UniqueConflictException,
|
||||||
|
} from '@mobicoop/ddd-library';
|
||||||
|
import { CreateAuthenticationCommand } from './create-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';
|
||||||
|
import {
|
||||||
|
AuthenticationAlreadyExistsException,
|
||||||
|
UsernameAlreadyExistsException,
|
||||||
|
} from '@modules/newauthentication/core/domain/authentication.errors';
|
||||||
|
|
||||||
|
@CommandHandler(CreateAuthenticationCommand)
|
||||||
|
export class CreateAuthenticationService implements ICommandHandler {
|
||||||
|
constructor(
|
||||||
|
@Inject(AUTHENTICATION_REPOSITORY)
|
||||||
|
private readonly repository: AuthenticationRepositoryPort,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async execute(command: CreateAuthenticationCommand): Promise<AggregateID> {
|
||||||
|
const authentication = await AuthenticationEntity.create({
|
||||||
|
password: command.password,
|
||||||
|
usernames: command.usernames,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.repository.insert(authentication);
|
||||||
|
return authentication.id;
|
||||||
|
} catch (error: any) {
|
||||||
|
console.log('error', error.cause);
|
||||||
|
if (error instanceof ConflictException) {
|
||||||
|
throw new AuthenticationAlreadyExistsException(error);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
error instanceof UniqueConflictException &&
|
||||||
|
error.message.includes('username')
|
||||||
|
) {
|
||||||
|
throw new UsernameAlreadyExistsException(error);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { RepositoryPort } from '@mobicoop/ddd-library';
|
||||||
|
import { AuthenticationEntity } from '@modules/newauthentication/core/domain/authentication.entity';
|
||||||
|
|
||||||
|
export type AuthenticationRepositoryPort = RepositoryPort<AuthenticationEntity>;
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { Type } from '@modules/newauthentication/core/domain/username.types';
|
||||||
|
|
||||||
|
export type Username = {
|
||||||
|
name: string;
|
||||||
|
type: Type;
|
||||||
|
};
|
|
@ -0,0 +1,35 @@
|
||||||
|
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';
|
||||||
|
|
||||||
|
export class AuthenticationEntity extends AggregateRoot<AuthenticationProps> {
|
||||||
|
protected readonly _id: AggregateID;
|
||||||
|
|
||||||
|
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,
|
||||||
|
props: {
|
||||||
|
password: hash,
|
||||||
|
usernames: props.usernames,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
authentication.addEvent(
|
||||||
|
new AuthenticationCreatedDomainEvent({ aggregateId: id }),
|
||||||
|
);
|
||||||
|
return authentication;
|
||||||
|
};
|
||||||
|
|
||||||
|
validate(): void {
|
||||||
|
// entity business rules validation to protect it's invariant before saving entity to a database
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { ExceptionBase } from '@mobicoop/ddd-library';
|
||||||
|
|
||||||
|
export class AuthenticationAlreadyExistsException extends ExceptionBase {
|
||||||
|
static readonly message = 'Authentication already exists';
|
||||||
|
|
||||||
|
public readonly code = 'AUTHENTICATION.ALREADY_EXISTS';
|
||||||
|
|
||||||
|
constructor(cause?: Error, metadata?: unknown) {
|
||||||
|
super(AuthenticationAlreadyExistsException.message, cause, metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UsernameAlreadyExistsException extends ExceptionBase {
|
||||||
|
static readonly message = 'Username already exists';
|
||||||
|
|
||||||
|
public readonly code = 'USERNAME.ALREADY_EXISTS';
|
||||||
|
|
||||||
|
constructor(cause?: Error, metadata?: unknown) {
|
||||||
|
super(UsernameAlreadyExistsException.message, cause, metadata);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { UsernameProps } from './username.types';
|
||||||
|
|
||||||
|
// All properties that an Authentication has
|
||||||
|
export interface AuthenticationProps {
|
||||||
|
password: string;
|
||||||
|
usernames: UsernameProps[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Properties that are needed for an Authentication creation
|
||||||
|
export interface CreateAuthenticationProps {
|
||||||
|
password: string;
|
||||||
|
usernames: UsernameProps[];
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { DomainEvent, DomainEventProps } from '@mobicoop/ddd-library';
|
||||||
|
|
||||||
|
export class AuthenticationCreatedDomainEvent extends DomainEvent {
|
||||||
|
constructor(props: DomainEventProps<AuthenticationCreatedDomainEvent>) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
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.');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
// All properties that a Username has
|
||||||
|
export interface UsernameProps {
|
||||||
|
name: string;
|
||||||
|
type: Type;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Properties that are needed for a Username creation
|
||||||
|
export interface CreateUsernameProps {
|
||||||
|
name: string;
|
||||||
|
type: Type;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Type {
|
||||||
|
EMAIL = 'EMAIL',
|
||||||
|
PHONE = 'PHONE',
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
|
import { 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';
|
||||||
|
|
||||||
|
export type AuthenticationBaseModel = {
|
||||||
|
uuid: string;
|
||||||
|
password: string;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AuthenticationReadModel = AuthenticationBaseModel & {
|
||||||
|
usernames: UsernameModel[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AuthenticationWriteModel = AuthenticationBaseModel & {
|
||||||
|
usernames: {
|
||||||
|
create: UsernameModel[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UsernameModel = {
|
||||||
|
username: string;
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repository is used for retrieving/saving domain entities
|
||||||
|
* */
|
||||||
|
@Injectable()
|
||||||
|
export class AuthenticationRepository
|
||||||
|
extends PrismaRepositoryBase<
|
||||||
|
AuthenticationEntity,
|
||||||
|
AuthenticationReadModel,
|
||||||
|
AuthenticationWriteModel
|
||||||
|
>
|
||||||
|
implements AuthenticationRepositoryPort
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
prisma: PrismaService,
|
||||||
|
mapper: AuthenticationMapper,
|
||||||
|
eventEmitter: EventEmitter2,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
prisma.auth,
|
||||||
|
prisma,
|
||||||
|
mapper,
|
||||||
|
eventEmitter,
|
||||||
|
new Logger(AuthenticationRepository.name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { INestApplication, Injectable, OnModuleInit } from '@nestjs/common';
|
||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PrismaService extends PrismaClient implements OnModuleInit {
|
||||||
|
async onModuleInit() {
|
||||||
|
await this.$connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
async enableShutdownHooks(app: INestApplication) {
|
||||||
|
this.$on('beforeExit', async () => {
|
||||||
|
await app.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { PaginatedResponseDto } from '@mobicoop/ddd-library';
|
||||||
|
import { AuthenticationResponseDto } from './authentication.response.dto';
|
||||||
|
|
||||||
|
export class AauthenticationPaginatedResponseDto extends PaginatedResponseDto<AuthenticationResponseDto> {
|
||||||
|
readonly data: readonly AuthenticationResponseDto[];
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { ResponseBase } from '@mobicoop/ddd-library';
|
||||||
|
|
||||||
|
export class AuthenticationResponseDto extends ResponseBase {}
|
|
@ -0,0 +1,40 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package authentication;
|
||||||
|
|
||||||
|
service AuthenticationService {
|
||||||
|
rpc Validate(AuthenticationByUsernamePassword) returns (Id);
|
||||||
|
rpc Create(Authentication) returns (Id);
|
||||||
|
rpc AddUsername(Username) returns (Id);
|
||||||
|
rpc UpdatePassword(Password) returns (Id);
|
||||||
|
rpc UpdateUsername(Username) returns (Id);
|
||||||
|
rpc DeleteUsername(Username) returns (Id);
|
||||||
|
rpc Delete(Id) returns (Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
message AuthenticationByUsernamePassword {
|
||||||
|
string username = 1;
|
||||||
|
string password = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Authentication {
|
||||||
|
repeated Username usernames = 1;
|
||||||
|
string password = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Password {
|
||||||
|
string id = 1;
|
||||||
|
string password = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Username {
|
||||||
|
string id = 1;
|
||||||
|
string name = 2;
|
||||||
|
string type = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Id {
|
||||||
|
string id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Empty {}
|
|
@ -0,0 +1,43 @@
|
||||||
|
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 { CreateAuthenticationRequestDto } from './dtos/create-authentication.request.dto';
|
||||||
|
import { CreateAuthenticationCommand } from '@modules/newauthentication/core/application/commands/create-authentication/create-authentication.command';
|
||||||
|
import { AuthenticationAlreadyExistsException } from '@modules/newauthentication/core/domain/authentication.errors';
|
||||||
|
|
||||||
|
@UsePipes(
|
||||||
|
new RpcValidationPipe({
|
||||||
|
whitelist: true,
|
||||||
|
forbidUnknownValues: false,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
@Controller()
|
||||||
|
export class CreateAuthenticationGrpcController {
|
||||||
|
constructor(private readonly commandBus: CommandBus) {}
|
||||||
|
|
||||||
|
@GrpcMethod('AuthenticationService', 'Create')
|
||||||
|
async validate(data: CreateAuthenticationRequestDto): Promise<IdResponse> {
|
||||||
|
try {
|
||||||
|
const aggregateID: AggregateID = await this.commandBus.execute(
|
||||||
|
new CreateAuthenticationCommand(data),
|
||||||
|
);
|
||||||
|
return new IdResponse(aggregateID);
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error instanceof AuthenticationAlreadyExistsException)
|
||||||
|
throw new RpcException({
|
||||||
|
code: RpcExceptionCode.ALREADY_EXISTS,
|
||||||
|
message: error.message,
|
||||||
|
});
|
||||||
|
throw new RpcException({
|
||||||
|
code: RpcExceptionCode.UNKNOWN,
|
||||||
|
message: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { Type } from 'class-transformer';
|
||||||
|
import {
|
||||||
|
ArrayMinSize,
|
||||||
|
IsArray,
|
||||||
|
IsNotEmpty,
|
||||||
|
IsString,
|
||||||
|
ValidateNested,
|
||||||
|
} from 'class-validator';
|
||||||
|
import { UsernameDto } from './username.dto';
|
||||||
|
|
||||||
|
export class CreateAuthenticationRequestDto {
|
||||||
|
@Type(() => UsernameDto)
|
||||||
|
@IsArray()
|
||||||
|
@ArrayMinSize(1)
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
usernames: UsernameDto[];
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
password: string;
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
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;
|
||||||
|
}
|
|
@ -16,6 +16,10 @@
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
"strictBindCallApply": false,
|
"strictBindCallApply": false,
|
||||||
"forceConsistentCasingInFileNames": false,
|
"forceConsistentCasingInFileNames": false,
|
||||||
"noFallthroughCasesInSwitch": false
|
"noFallthroughCasesInSwitch": false,
|
||||||
|
"paths": {
|
||||||
|
"@modules/*": ["src/modules/*"],
|
||||||
|
"@src/*": ["src/*"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue