user updated message handler
This commit is contained in:
parent
487ae9c38e
commit
85746fde48
|
@ -24,6 +24,16 @@ import {
|
||||||
uri: configService.get<string>('MESSAGE_BROKER_URI'),
|
uri: configService.get<string>('MESSAGE_BROKER_URI'),
|
||||||
exchange: configService.get<string>('MESSAGE_BROKER_EXCHANGE'),
|
exchange: configService.get<string>('MESSAGE_BROKER_EXCHANGE'),
|
||||||
name: 'auth',
|
name: 'auth',
|
||||||
|
handlers: {
|
||||||
|
userUpdated: {
|
||||||
|
routingKey: 'user.updated',
|
||||||
|
queue: 'auth-user-updated',
|
||||||
|
},
|
||||||
|
userDeleted: {
|
||||||
|
routingKey: 'user.deleted',
|
||||||
|
queue: 'auth-user-deleted',
|
||||||
|
},
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
// AutomapperModule.forRoot({ strategyInitializer: classes() }),
|
// AutomapperModule.forRoot({ strategyInitializer: classes() }),
|
||||||
|
|
|
@ -39,8 +39,6 @@ export class AuthenticationMapper
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
createdAt: copy.createdAt,
|
|
||||||
updatedAt: copy.updatedAt,
|
|
||||||
};
|
};
|
||||||
return record;
|
return record;
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,6 +25,7 @@ import { UpdatePasswordGrpcController } from './interface/grpc-controllers/updat
|
||||||
import { UpdatePasswordService } from './core/application/commands/update-password/update-password.service';
|
import { UpdatePasswordService } from './core/application/commands/update-password/update-password.service';
|
||||||
import { ValidateAuthenticationGrpcController } from './interface/grpc-controllers/validate-authentication.grpc.controller';
|
import { ValidateAuthenticationGrpcController } from './interface/grpc-controllers/validate-authentication.grpc.controller';
|
||||||
import { ValidateAuthenticationQueryHandler } from './core/application/queries/validate-authentication/validate-authentication.query-handler';
|
import { ValidateAuthenticationQueryHandler } from './core/application/queries/validate-authentication/validate-authentication.query-handler';
|
||||||
|
import { UserUpdatedMessageHandler } from './interface/message-handlers/user-updated.message-handler';
|
||||||
|
|
||||||
const grpcControllers = [
|
const grpcControllers = [
|
||||||
CreateAuthenticationGrpcController,
|
CreateAuthenticationGrpcController,
|
||||||
|
@ -36,6 +37,8 @@ const grpcControllers = [
|
||||||
ValidateAuthenticationGrpcController,
|
ValidateAuthenticationGrpcController,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const messageHandlers = [UserUpdatedMessageHandler];
|
||||||
|
|
||||||
const commandHandlers: Provider[] = [
|
const commandHandlers: Provider[] = [
|
||||||
CreateAuthenticationService,
|
CreateAuthenticationService,
|
||||||
DeleteAuthenticationService,
|
DeleteAuthenticationService,
|
||||||
|
@ -73,6 +76,7 @@ const orms: Provider[] = [PrismaService];
|
||||||
imports: [CqrsModule],
|
imports: [CqrsModule],
|
||||||
controllers: [...grpcControllers],
|
controllers: [...grpcControllers],
|
||||||
providers: [
|
providers: [
|
||||||
|
...messageHandlers,
|
||||||
...commandHandlers,
|
...commandHandlers,
|
||||||
...queryHandlers,
|
...queryHandlers,
|
||||||
...mappers,
|
...mappers,
|
||||||
|
|
|
@ -11,15 +11,15 @@ 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';
|
||||||
|
|
||||||
export type AuthenticationBaseModel = {
|
type AuthenticationBaseModel = {
|
||||||
uuid: string;
|
uuid: string;
|
||||||
password: string;
|
password: string;
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AuthenticationReadModel = AuthenticationBaseModel & {
|
export type AuthenticationReadModel = AuthenticationBaseModel & {
|
||||||
usernames: UsernameModel[];
|
usernames: UsernameModel[];
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AuthenticationWriteModel = AuthenticationBaseModel & {
|
export type AuthenticationWriteModel = AuthenticationBaseModel & {
|
||||||
|
|
|
@ -12,20 +12,29 @@ import { UsernameRepositoryPort } from '../core/application/ports/username.repos
|
||||||
import { UsernameMapper } from '../username.mapper';
|
import { UsernameMapper } from '../username.mapper';
|
||||||
import { Type } from '../core/domain/username.types';
|
import { Type } from '../core/domain/username.types';
|
||||||
|
|
||||||
export type UsernameModel = {
|
type UsernameBaseModel = {
|
||||||
username: string;
|
username: string;
|
||||||
authUuid: string;
|
authUuid: string;
|
||||||
type: string;
|
type: string;
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type UsernameReadModel = UsernameBaseModel & {
|
||||||
|
createdAt?: Date;
|
||||||
|
updatedAt?: Date;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UsernameWriteModel = UsernameBaseModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repository is used for retrieving/saving domain entities
|
* Repository is used for retrieving/saving domain entities
|
||||||
* */
|
* */
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UsernameRepository
|
export class UsernameRepository
|
||||||
extends PrismaRepositoryBase<UsernameEntity, UsernameModel, UsernameModel>
|
extends PrismaRepositoryBase<
|
||||||
|
UsernameEntity,
|
||||||
|
UsernameReadModel,
|
||||||
|
UsernameWriteModel
|
||||||
|
>
|
||||||
implements UsernameRepositoryPort
|
implements UsernameRepositoryPort
|
||||||
{
|
{
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -42,7 +51,7 @@ export class UsernameRepository
|
||||||
eventEmitter,
|
eventEmitter,
|
||||||
new LoggerBase({
|
new LoggerBase({
|
||||||
logger: new Logger(UsernameRepository.name),
|
logger: new Logger(UsernameRepository.name),
|
||||||
domain: 'auth',
|
domain: 'auth.username',
|
||||||
messagePublisher,
|
messagePublisher,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { CommandBus } from '@nestjs/cqrs';
|
||||||
|
import { RabbitSubscribe } from '@mobicoop/message-broker-module';
|
||||||
|
import { Type } from '@modules/authentication/core/domain/username.types';
|
||||||
|
import { UpdateUsernameCommand } from '@modules/authentication/core/application/commands/update-username/update-username.command';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UserUpdatedMessageHandler {
|
||||||
|
constructor(private readonly commandBus: CommandBus) {}
|
||||||
|
|
||||||
|
@RabbitSubscribe({
|
||||||
|
name: 'userUpdated',
|
||||||
|
})
|
||||||
|
public async userUpdated(message: string) {
|
||||||
|
const updatedUser = JSON.parse(message);
|
||||||
|
try {
|
||||||
|
if (!updatedUser.hasOwnProperty('userId')) throw new Error();
|
||||||
|
if (updatedUser.hasOwnProperty('email') && updatedUser.email) {
|
||||||
|
await this.commandBus.execute(
|
||||||
|
new UpdateUsernameCommand({
|
||||||
|
userId: updatedUser.userId,
|
||||||
|
username: {
|
||||||
|
name: updatedUser.email,
|
||||||
|
type: Type.EMAIL,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (updatedUser.hasOwnProperty('phone') && updatedUser.phone) {
|
||||||
|
await this.commandBus.execute(
|
||||||
|
new UpdateUsernameCommand({
|
||||||
|
userId: updatedUser.userId,
|
||||||
|
username: {
|
||||||
|
name: updatedUser.phone,
|
||||||
|
type: Type.PHONE,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e: any) {}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ import { PrismaService } from '@modules/authentication/infrastructure/prisma.ser
|
||||||
import { EventEmitter2, EventEmitterModule } from '@nestjs/event-emitter';
|
import { EventEmitter2, EventEmitterModule } from '@nestjs/event-emitter';
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import {
|
import {
|
||||||
UsernameModel,
|
UsernameReadModel,
|
||||||
UsernameRepository,
|
UsernameRepository,
|
||||||
} from '@modules/authentication/infrastructure/username.repository';
|
} from '@modules/authentication/infrastructure/username.repository';
|
||||||
import { Type } from '@modules/authentication/core/domain/username.types';
|
import { Type } from '@modules/authentication/core/domain/username.types';
|
||||||
|
@ -13,7 +13,7 @@ const mockPrismaService = {
|
||||||
username: {
|
username: {
|
||||||
findFirst: jest.fn().mockImplementation(async () => {
|
findFirst: jest.fn().mockImplementation(async () => {
|
||||||
const now = new Date('2023-06-21 06:00:00');
|
const now = new Date('2023-06-21 06:00:00');
|
||||||
const record: UsernameModel = {
|
const record: UsernameReadModel = {
|
||||||
authUuid: '330bd6de-1eb8-450b-8674-0e3c9209f048',
|
authUuid: '330bd6de-1eb8-450b-8674-0e3c9209f048',
|
||||||
type: Type.EMAIL,
|
type: Type.EMAIL,
|
||||||
username: 'john.doe@email.com',
|
username: 'john.doe@email.com',
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
import { UserUpdatedMessageHandler } from '@modules/authentication/interface/message-handlers/user-updated.message-handler';
|
||||||
|
import { CommandBus } from '@nestjs/cqrs';
|
||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
|
||||||
|
const userEmailUpdatedMessage =
|
||||||
|
'{"userId":"2436d413-b7c7-429e-9792-b78edc17b3ca","email":"new-john.doe@email.com"}';
|
||||||
|
|
||||||
|
const userPhoneUpdatedMessage =
|
||||||
|
'{"userId":"2436d413-b7c7-429e-9792-b78edc17b3ca","phone":"+33611224455"}';
|
||||||
|
|
||||||
|
const userBirthDateUpdatedMessage =
|
||||||
|
'{"userId":"2436d413-b7c7-429e-9792-b78edc17b3ca","birthDate":"1976-10-23"}';
|
||||||
|
|
||||||
|
const userIdNotProvidedUpdatedMessage =
|
||||||
|
'{"user":"2436d413-b7c7-429e-9792-b78edc17b300","email":"new-john.doe@email.com"}';
|
||||||
|
|
||||||
|
const mockCommandBus = {
|
||||||
|
execute: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementationOnce(() => 'new-john.doe@email.com')
|
||||||
|
.mockImplementationOnce(() => '+33611224455'),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('User Updated Message Handler', () => {
|
||||||
|
let userUpdatedMessageHandler: UserUpdatedMessageHandler;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: CommandBus,
|
||||||
|
useValue: mockCommandBus,
|
||||||
|
},
|
||||||
|
UserUpdatedMessageHandler,
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
userUpdatedMessageHandler = module.get<UserUpdatedMessageHandler>(
|
||||||
|
UserUpdatedMessageHandler,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(userUpdatedMessageHandler).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update an email username', async () => {
|
||||||
|
jest.spyOn(mockCommandBus, 'execute');
|
||||||
|
await userUpdatedMessageHandler.userUpdated(userEmailUpdatedMessage);
|
||||||
|
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update a phone username', async () => {
|
||||||
|
jest.spyOn(mockCommandBus, 'execute');
|
||||||
|
await userUpdatedMessageHandler.userUpdated(userPhoneUpdatedMessage);
|
||||||
|
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not update a username if message does not contain email nor phone', async () => {
|
||||||
|
jest.spyOn(mockCommandBus, 'execute');
|
||||||
|
await userUpdatedMessageHandler.userUpdated(userBirthDateUpdatedMessage);
|
||||||
|
expect(mockCommandBus.execute).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not update a username if userId is unknown', async () => {
|
||||||
|
jest.spyOn(mockCommandBus, 'execute');
|
||||||
|
await userUpdatedMessageHandler.userUpdated(
|
||||||
|
userIdNotProvidedUpdatedMessage,
|
||||||
|
);
|
||||||
|
expect(mockCommandBus.execute).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// it('should throw a dedicated RpcException if username already exists', async () => {
|
||||||
|
// jest.spyOn(mockCommandBus, 'execute');
|
||||||
|
// expect.assertions(3);
|
||||||
|
// try {
|
||||||
|
// await updateUsernameGrpcController.updateUsername(updateUsernameRequest);
|
||||||
|
// } 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 updateUsernameGrpcController.updateUsername(updateUsernameRequest);
|
||||||
|
// } catch (e: any) {
|
||||||
|
// expect(e).toBeInstanceOf(RpcException);
|
||||||
|
// expect(e.error.code).toBe(RpcExceptionCode.UNKNOWN);
|
||||||
|
// }
|
||||||
|
// expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
||||||
|
// });
|
||||||
|
});
|
|
@ -1,6 +1,9 @@
|
||||||
import { UsernameEntity } from '@modules/authentication/core/domain/username.entity';
|
import { UsernameEntity } from '@modules/authentication/core/domain/username.entity';
|
||||||
import { Type } from '@modules/authentication/core/domain/username.types';
|
import { Type } from '@modules/authentication/core/domain/username.types';
|
||||||
import { UsernameModel } from '@modules/authentication/infrastructure/username.repository';
|
import {
|
||||||
|
UsernameReadModel,
|
||||||
|
UsernameWriteModel,
|
||||||
|
} from '@modules/authentication/infrastructure/username.repository';
|
||||||
import { UsernameResponseDto } from '@modules/authentication/interface/dtos/username.response.dto';
|
import { UsernameResponseDto } from '@modules/authentication/interface/dtos/username.response.dto';
|
||||||
import { UsernameMapper } from '@modules/authentication/username.mapper';
|
import { UsernameMapper } from '@modules/authentication/username.mapper';
|
||||||
import { Test } from '@nestjs/testing';
|
import { Test } from '@nestjs/testing';
|
||||||
|
@ -16,7 +19,7 @@ const usernameEntity: UsernameEntity = new UsernameEntity({
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
});
|
});
|
||||||
const usernameModel: UsernameModel = {
|
const usernameReadModel: UsernameReadModel = {
|
||||||
authUuid: '7ca2490b-d04d-4ac5-8d6c-5c416fab922e',
|
authUuid: '7ca2490b-d04d-4ac5-8d6c-5c416fab922e',
|
||||||
username: 'john.doe@email.com',
|
username: 'john.doe@email.com',
|
||||||
type: Type.EMAIL,
|
type: Type.EMAIL,
|
||||||
|
@ -39,13 +42,14 @@ describe('Username Mapper', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should map domain entity to persistence data', async () => {
|
it('should map domain entity to persistence data', async () => {
|
||||||
const mapped: UsernameModel = usernameMapper.toPersistence(usernameEntity);
|
const mapped: UsernameWriteModel =
|
||||||
|
usernameMapper.toPersistence(usernameEntity);
|
||||||
expect(mapped.username).toBe('john.doe@email.com');
|
expect(mapped.username).toBe('john.doe@email.com');
|
||||||
expect(mapped.type).toBe(Type.EMAIL);
|
expect(mapped.type).toBe(Type.EMAIL);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should map persisted data to domain entity', async () => {
|
it('should map persisted data to domain entity', async () => {
|
||||||
const mapped: UsernameEntity = usernameMapper.toDomain(usernameModel);
|
const mapped: UsernameEntity = usernameMapper.toDomain(usernameReadModel);
|
||||||
expect(mapped.getProps().name).toBe('john.doe@email.com');
|
expect(mapped.getProps().name).toBe('john.doe@email.com');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,10 @@ import { Mapper } from '@mobicoop/ddd-library';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Type } from './core/domain/username.types';
|
import { Type } from './core/domain/username.types';
|
||||||
import { UsernameEntity } from './core/domain/username.entity';
|
import { UsernameEntity } from './core/domain/username.entity';
|
||||||
import { UsernameModel } from './infrastructure/username.repository';
|
import {
|
||||||
|
UsernameReadModel,
|
||||||
|
UsernameWriteModel,
|
||||||
|
} from './infrastructure/username.repository';
|
||||||
import { UsernameResponseDto } from './interface/dtos/username.response.dto';
|
import { UsernameResponseDto } from './interface/dtos/username.response.dto';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,21 +18,24 @@ import { UsernameResponseDto } from './interface/dtos/username.response.dto';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UsernameMapper
|
export class UsernameMapper
|
||||||
implements
|
implements
|
||||||
Mapper<UsernameEntity, UsernameModel, UsernameModel, UsernameResponseDto>
|
Mapper<
|
||||||
|
UsernameEntity,
|
||||||
|
UsernameReadModel,
|
||||||
|
UsernameWriteModel,
|
||||||
|
UsernameResponseDto
|
||||||
|
>
|
||||||
{
|
{
|
||||||
toPersistence = (entity: UsernameEntity): UsernameModel => {
|
toPersistence = (entity: UsernameEntity): UsernameWriteModel => {
|
||||||
const copy = entity.getProps();
|
const copy = entity.getProps();
|
||||||
const record: UsernameModel = {
|
const record: UsernameWriteModel = {
|
||||||
authUuid: copy.userId,
|
authUuid: copy.userId,
|
||||||
username: copy.name,
|
username: copy.name,
|
||||||
type: copy.type,
|
type: copy.type,
|
||||||
createdAt: copy.createdAt,
|
|
||||||
updatedAt: copy.updatedAt,
|
|
||||||
};
|
};
|
||||||
return record;
|
return record;
|
||||||
};
|
};
|
||||||
|
|
||||||
toDomain = (record: UsernameModel): UsernameEntity => {
|
toDomain = (record: UsernameReadModel): UsernameEntity => {
|
||||||
const entity = new UsernameEntity({
|
const entity = new UsernameEntity({
|
||||||
id: record.username,
|
id: record.username,
|
||||||
createdAt: new Date(record.createdAt),
|
createdAt: new Date(record.createdAt),
|
||||||
|
|
Loading…
Reference in New Issue