integration events
This commit is contained in:
parent
3ac7460c83
commit
e8c40a6386
|
@ -1,121 +0,0 @@
|
||||||
import { Mapper } from '@automapper/core';
|
|
||||||
import { InjectMapper } from '@automapper/nestjs';
|
|
||||||
import { Controller, UseInterceptors, UsePipes } from '@nestjs/common';
|
|
||||||
import { CommandBus, QueryBus } from '@nestjs/cqrs';
|
|
||||||
import { GrpcMethod, RpcException } from '@nestjs/microservices';
|
|
||||||
import { DatabaseException } from '../../../database/exceptions/database.exception';
|
|
||||||
import { CreateUserCommand } from '../../commands/create-user.command';
|
|
||||||
import { DeleteUserCommand } from '../../commands/delete-user.command';
|
|
||||||
import { UpdateUserCommand } from '../../commands/update-user.command';
|
|
||||||
import { CreateUserRequest } from '../../domain/dtos/create-user.request';
|
|
||||||
import { FindAllUsersRequest } from '../../domain/dtos/find-all-users.request';
|
|
||||||
import { FindUserByUuidRequest } from '../../domain/dtos/find-user-by-uuid.request';
|
|
||||||
import { UpdateUserRequest } from '../../domain/dtos/update-user.request';
|
|
||||||
import { User } from '../../domain/entities/user';
|
|
||||||
import { FindAllUsersQuery } from '../../queries/find-all-users.query';
|
|
||||||
import { FindUserByUuidQuery } from '../../queries/find-user-by-uuid.query';
|
|
||||||
import { UserPresenter } from './user.presenter';
|
|
||||||
import { ICollection } from '../../../database/interfaces/collection.interface';
|
|
||||||
import { RpcValidationPipe } from '../../../../utils/pipes/rpc.validation-pipe';
|
|
||||||
import { CacheInterceptor, CacheKey } from '@nestjs/cache-manager';
|
|
||||||
|
|
||||||
@UsePipes(
|
|
||||||
new RpcValidationPipe({
|
|
||||||
whitelist: true,
|
|
||||||
forbidUnknownValues: false,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
@Controller()
|
|
||||||
export class UserController {
|
|
||||||
constructor(
|
|
||||||
private readonly commandBus: CommandBus,
|
|
||||||
private readonly queryBus: QueryBus,
|
|
||||||
@InjectMapper() private readonly mapper: Mapper,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
@GrpcMethod('UsersService', 'FindAll')
|
|
||||||
@UseInterceptors(CacheInterceptor)
|
|
||||||
@CacheKey('UsersServiceFindAll')
|
|
||||||
async findAll(data: FindAllUsersRequest): Promise<ICollection<User>> {
|
|
||||||
const userCollection = await this.queryBus.execute(
|
|
||||||
new FindAllUsersQuery(data),
|
|
||||||
);
|
|
||||||
return Promise.resolve({
|
|
||||||
data: userCollection.data.map((user: User) =>
|
|
||||||
this.mapper.map(user, User, UserPresenter),
|
|
||||||
),
|
|
||||||
total: userCollection.total,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@GrpcMethod('UsersService', 'FindOneByUuid')
|
|
||||||
@UseInterceptors(CacheInterceptor)
|
|
||||||
@CacheKey('UsersServiceFindOneByUuid')
|
|
||||||
async findOneByUuid(data: FindUserByUuidRequest): Promise<UserPresenter> {
|
|
||||||
try {
|
|
||||||
const user = await this.queryBus.execute(new FindUserByUuidQuery(data));
|
|
||||||
return this.mapper.map(user, User, UserPresenter);
|
|
||||||
} catch (error) {
|
|
||||||
throw new RpcException({
|
|
||||||
code: 5,
|
|
||||||
message: 'User not found',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GrpcMethod('UsersService', 'Create')
|
|
||||||
async createUser(data: CreateUserRequest): Promise<UserPresenter> {
|
|
||||||
try {
|
|
||||||
const user = await this.commandBus.execute(new CreateUserCommand(data));
|
|
||||||
return this.mapper.map(user, User, UserPresenter);
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof DatabaseException) {
|
|
||||||
if (e.message.includes('Already exists')) {
|
|
||||||
throw new RpcException({
|
|
||||||
code: 6,
|
|
||||||
message: 'User already exists',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new RpcException({});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GrpcMethod('UsersService', 'Update')
|
|
||||||
async updateUser(data: UpdateUserRequest): Promise<UserPresenter> {
|
|
||||||
try {
|
|
||||||
const user = await this.commandBus.execute(new UpdateUserCommand(data));
|
|
||||||
|
|
||||||
return this.mapper.map(user, User, UserPresenter);
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof DatabaseException) {
|
|
||||||
if (e.message.includes('not found')) {
|
|
||||||
throw new RpcException({
|
|
||||||
code: 5,
|
|
||||||
message: 'User not found',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new RpcException({});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GrpcMethod('UsersService', 'Delete')
|
|
||||||
async deleteUser(data: FindUserByUuidRequest): Promise<void> {
|
|
||||||
try {
|
|
||||||
await this.commandBus.execute(new DeleteUserCommand(data.uuid));
|
|
||||||
|
|
||||||
return Promise.resolve();
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof DatabaseException) {
|
|
||||||
if (e.message.includes('not found')) {
|
|
||||||
throw new RpcException({
|
|
||||||
code: 5,
|
|
||||||
message: 'User not found',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new RpcException({});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
import { AutoMap } from '@automapper/classes';
|
|
||||||
|
|
||||||
export class UserPresenter {
|
|
||||||
@AutoMap()
|
|
||||||
uuid: string;
|
|
||||||
|
|
||||||
@AutoMap()
|
|
||||||
firstName: string;
|
|
||||||
|
|
||||||
@AutoMap()
|
|
||||||
lastName: string;
|
|
||||||
|
|
||||||
@AutoMap()
|
|
||||||
email: string;
|
|
||||||
|
|
||||||
@AutoMap()
|
|
||||||
phone: string;
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package user;
|
|
||||||
|
|
||||||
service UsersService {
|
|
||||||
rpc FindOneByUuid(UserByUuid) returns (User);
|
|
||||||
rpc FindAll(UserFilter) returns (Users);
|
|
||||||
rpc Create(User) returns (User);
|
|
||||||
rpc Update(User) returns (User);
|
|
||||||
rpc Delete(UserByUuid) returns (Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
message UserByUuid {
|
|
||||||
string uuid = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message User {
|
|
||||||
string uuid = 1;
|
|
||||||
string firstName = 2;
|
|
||||||
string lastName = 3;
|
|
||||||
string email = 4;
|
|
||||||
string phone = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message UserFilter {
|
|
||||||
optional int32 page = 1;
|
|
||||||
optional int32 perPage = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Users {
|
|
||||||
repeated User data = 1;
|
|
||||||
int32 total = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Empty {}
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import { IPublishMessage } from '../../../../interfaces/message-publisher';
|
|
||||||
import { MESSAGE_BROKER_PUBLISHER } from '../../../../app.constants';
|
|
||||||
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class MessagePublisher implements IPublishMessage {
|
|
||||||
constructor(
|
|
||||||
@Inject(MESSAGE_BROKER_PUBLISHER)
|
|
||||||
private readonly messageBrokerPublisher: MessageBrokerPublisher,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
publish = (routingKey: string, message: string): void => {
|
|
||||||
this.messageBrokerPublisher.publish(routingKey, message);
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { UserRepository } from '../../../database/domain/user-repository';
|
|
||||||
import { User } from '../../domain/entities/user';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class UsersRepository extends UserRepository<User> {
|
|
||||||
protected model = 'user';
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { CreateUserRequest } from '../domain/dtos/create-user.request';
|
|
||||||
|
|
||||||
export class CreateUserCommand {
|
|
||||||
readonly createUserRequest: CreateUserRequest;
|
|
||||||
|
|
||||||
constructor(request: CreateUserRequest) {
|
|
||||||
this.createUserRequest = request;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
export class DeleteUserCommand {
|
|
||||||
readonly uuid: string;
|
|
||||||
|
|
||||||
constructor(uuid: string) {
|
|
||||||
this.uuid = uuid;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { UpdateUserRequest } from '../domain/dtos/update-user.request';
|
|
||||||
|
|
||||||
export class UpdateUserCommand {
|
|
||||||
readonly updateUserRequest: UpdateUserRequest;
|
|
||||||
|
|
||||||
constructor(request: UpdateUserRequest) {
|
|
||||||
this.updateUserRequest = request;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
import { AutoMap } from '@automapper/classes';
|
|
||||||
import { IsEmail, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
|
|
||||||
|
|
||||||
export class CreateUserRequest {
|
|
||||||
@IsString()
|
|
||||||
@IsOptional()
|
|
||||||
@AutoMap()
|
|
||||||
uuid?: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@IsOptional()
|
|
||||||
@AutoMap()
|
|
||||||
firstName?: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@IsOptional()
|
|
||||||
@AutoMap()
|
|
||||||
lastName?: string;
|
|
||||||
|
|
||||||
@IsEmail()
|
|
||||||
@IsOptional()
|
|
||||||
@AutoMap()
|
|
||||||
email?: string;
|
|
||||||
|
|
||||||
@IsPhoneNumber()
|
|
||||||
@IsOptional()
|
|
||||||
@AutoMap()
|
|
||||||
phone?: string;
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
import { IsInt, IsOptional } from 'class-validator';
|
|
||||||
|
|
||||||
export class FindAllUsersRequest {
|
|
||||||
@IsInt()
|
|
||||||
@IsOptional()
|
|
||||||
page?: number;
|
|
||||||
|
|
||||||
@IsInt()
|
|
||||||
@IsOptional()
|
|
||||||
perPage?: number;
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
import { IsNotEmpty, IsString } from 'class-validator';
|
|
||||||
|
|
||||||
export class FindUserByUuidRequest {
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
uuid: string;
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
import { AutoMap } from '@automapper/classes';
|
|
||||||
import {
|
|
||||||
IsEmail,
|
|
||||||
IsNotEmpty,
|
|
||||||
IsOptional,
|
|
||||||
IsPhoneNumber,
|
|
||||||
IsString,
|
|
||||||
} from 'class-validator';
|
|
||||||
|
|
||||||
export class UpdateUserRequest {
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
@AutoMap()
|
|
||||||
uuid: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@IsOptional()
|
|
||||||
@AutoMap()
|
|
||||||
firstName?: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@IsOptional()
|
|
||||||
@AutoMap()
|
|
||||||
lastName?: string;
|
|
||||||
|
|
||||||
@IsEmail()
|
|
||||||
@IsOptional()
|
|
||||||
@AutoMap()
|
|
||||||
email?: string;
|
|
||||||
|
|
||||||
@IsPhoneNumber()
|
|
||||||
@IsOptional()
|
|
||||||
@AutoMap()
|
|
||||||
phone?: string;
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
import { AutoMap } from '@automapper/classes';
|
|
||||||
|
|
||||||
export class User {
|
|
||||||
@AutoMap()
|
|
||||||
uuid: string;
|
|
||||||
|
|
||||||
@AutoMap()
|
|
||||||
firstName?: string;
|
|
||||||
|
|
||||||
@AutoMap()
|
|
||||||
lastName?: string;
|
|
||||||
|
|
||||||
@AutoMap()
|
|
||||||
email?: string;
|
|
||||||
|
|
||||||
@AutoMap()
|
|
||||||
phone?: string;
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
import { Mapper } from '@automapper/core';
|
|
||||||
import { InjectMapper } from '@automapper/nestjs';
|
|
||||||
import { CommandHandler } from '@nestjs/cqrs';
|
|
||||||
import { UsersRepository } from '../../adapters/secondaries/users.repository';
|
|
||||||
import { CreateUserCommand } from '../../commands/create-user.command';
|
|
||||||
import { CreateUserRequest } from '../dtos/create-user.request';
|
|
||||||
import { User } from '../entities/user';
|
|
||||||
import { Inject } from '@nestjs/common';
|
|
||||||
import { MESSAGE_PUBLISHER } from '../../../../app.constants';
|
|
||||||
import { IPublishMessage } from '../../../../interfaces/message-publisher';
|
|
||||||
|
|
||||||
@CommandHandler(CreateUserCommand)
|
|
||||||
export class CreateUserUseCase {
|
|
||||||
constructor(
|
|
||||||
private readonly repository: UsersRepository,
|
|
||||||
@Inject(MESSAGE_PUBLISHER)
|
|
||||||
private readonly messagePublisher: IPublishMessage,
|
|
||||||
@InjectMapper() private readonly mapper: Mapper,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
execute = async (command: CreateUserCommand): Promise<User> => {
|
|
||||||
const entity = this.mapper.map(
|
|
||||||
command.createUserRequest,
|
|
||||||
CreateUserRequest,
|
|
||||||
User,
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const user = await this.repository.create(entity);
|
|
||||||
this.messagePublisher.publish('user.create', JSON.stringify(user));
|
|
||||||
this.messagePublisher.publish(
|
|
||||||
'logging.user.create.info',
|
|
||||||
JSON.stringify(user),
|
|
||||||
);
|
|
||||||
return user;
|
|
||||||
} catch (error) {
|
|
||||||
let key = 'logging.user.create.crit';
|
|
||||||
if (error.message.includes('Already exists')) {
|
|
||||||
key = 'logging.user.create.warning';
|
|
||||||
}
|
|
||||||
this.messagePublisher.publish(
|
|
||||||
key,
|
|
||||||
JSON.stringify({
|
|
||||||
command,
|
|
||||||
error,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
import { CommandHandler } from '@nestjs/cqrs';
|
|
||||||
import { UsersRepository } from '../../adapters/secondaries/users.repository';
|
|
||||||
import { DeleteUserCommand } from '../../commands/delete-user.command';
|
|
||||||
import { User } from '../entities/user';
|
|
||||||
import { IPublishMessage } from '../../../../interfaces/message-publisher';
|
|
||||||
import { Inject } from '@nestjs/common';
|
|
||||||
import { MESSAGE_PUBLISHER } from '../../../../app.constants';
|
|
||||||
|
|
||||||
@CommandHandler(DeleteUserCommand)
|
|
||||||
export class DeleteUserUseCase {
|
|
||||||
constructor(
|
|
||||||
private readonly repository: UsersRepository,
|
|
||||||
@Inject(MESSAGE_PUBLISHER)
|
|
||||||
private readonly messagePublisher: IPublishMessage,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
execute = async (command: DeleteUserCommand): Promise<User> => {
|
|
||||||
try {
|
|
||||||
const user = await this.repository.delete(command.uuid);
|
|
||||||
this.messagePublisher.publish(
|
|
||||||
'user.delete',
|
|
||||||
JSON.stringify({ uuid: user.uuid }),
|
|
||||||
);
|
|
||||||
this.messagePublisher.publish(
|
|
||||||
'logging.user.delete.info',
|
|
||||||
JSON.stringify({ uuid: user.uuid }),
|
|
||||||
);
|
|
||||||
return user;
|
|
||||||
} catch (error) {
|
|
||||||
this.messagePublisher.publish(
|
|
||||||
'logging.user.delete.crit',
|
|
||||||
JSON.stringify({
|
|
||||||
command,
|
|
||||||
error,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
import { QueryHandler } from '@nestjs/cqrs';
|
|
||||||
import { ICollection } from 'src/modules/database/interfaces/collection.interface';
|
|
||||||
import { UsersRepository } from '../../adapters/secondaries/users.repository';
|
|
||||||
import { FindAllUsersQuery } from '../../queries/find-all-users.query';
|
|
||||||
import { User } from '../entities/user';
|
|
||||||
|
|
||||||
@QueryHandler(FindAllUsersQuery)
|
|
||||||
export class FindAllUsersUseCase {
|
|
||||||
constructor(private readonly repository: UsersRepository) {}
|
|
||||||
|
|
||||||
execute = async (
|
|
||||||
findAllUsersQuery: FindAllUsersQuery,
|
|
||||||
): Promise<ICollection<User>> =>
|
|
||||||
this.repository.findAll(findAllUsersQuery.page, findAllUsersQuery.perPage);
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
import { Inject, NotFoundException } from '@nestjs/common';
|
|
||||||
import { QueryHandler } from '@nestjs/cqrs';
|
|
||||||
import { UsersRepository } from '../../adapters/secondaries/users.repository';
|
|
||||||
import { FindUserByUuidQuery } from '../../queries/find-user-by-uuid.query';
|
|
||||||
import { User } from '../entities/user';
|
|
||||||
import { MESSAGE_PUBLISHER } from '../../../../app.constants';
|
|
||||||
import { IPublishMessage } from '../../../../interfaces/message-publisher';
|
|
||||||
|
|
||||||
@QueryHandler(FindUserByUuidQuery)
|
|
||||||
export class FindUserByUuidUseCase {
|
|
||||||
constructor(
|
|
||||||
private readonly repository: UsersRepository,
|
|
||||||
@Inject(MESSAGE_PUBLISHER)
|
|
||||||
private readonly messagePublisher: IPublishMessage,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
execute = async (findUserByUuid: FindUserByUuidQuery): Promise<User> => {
|
|
||||||
try {
|
|
||||||
const user = await this.repository.findOneByUuid(findUserByUuid.uuid);
|
|
||||||
if (!user) throw new NotFoundException();
|
|
||||||
return user;
|
|
||||||
} catch (error) {
|
|
||||||
this.messagePublisher.publish(
|
|
||||||
'logging.user.read.warning',
|
|
||||||
JSON.stringify({
|
|
||||||
query: findUserByUuid,
|
|
||||||
error,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
import { Mapper } from '@automapper/core';
|
|
||||||
import { InjectMapper } from '@automapper/nestjs';
|
|
||||||
import { CommandHandler } from '@nestjs/cqrs';
|
|
||||||
import { UsersRepository } from '../../adapters/secondaries/users.repository';
|
|
||||||
import { UpdateUserCommand } from '../../commands/update-user.command';
|
|
||||||
import { UpdateUserRequest } from '../dtos/update-user.request';
|
|
||||||
import { User } from '../entities/user';
|
|
||||||
import { MESSAGE_PUBLISHER } from '../../../../app.constants';
|
|
||||||
import { Inject } from '@nestjs/common';
|
|
||||||
import { IPublishMessage } from '../../../../interfaces/message-publisher';
|
|
||||||
|
|
||||||
@CommandHandler(UpdateUserCommand)
|
|
||||||
export class UpdateUserUseCase {
|
|
||||||
constructor(
|
|
||||||
private readonly repository: UsersRepository,
|
|
||||||
@Inject(MESSAGE_PUBLISHER)
|
|
||||||
private readonly messagePublisher: IPublishMessage,
|
|
||||||
@InjectMapper() private readonly mapper: Mapper,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
execute = async (command: UpdateUserCommand): Promise<User> => {
|
|
||||||
const entity = this.mapper.map(
|
|
||||||
command.updateUserRequest,
|
|
||||||
UpdateUserRequest,
|
|
||||||
User,
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const user = await this.repository.update(
|
|
||||||
command.updateUserRequest.uuid,
|
|
||||||
entity,
|
|
||||||
);
|
|
||||||
this.messagePublisher.publish(
|
|
||||||
'user.update',
|
|
||||||
JSON.stringify(command.updateUserRequest),
|
|
||||||
);
|
|
||||||
this.messagePublisher.publish(
|
|
||||||
'logging.user.update.info',
|
|
||||||
JSON.stringify(command.updateUserRequest),
|
|
||||||
);
|
|
||||||
return user;
|
|
||||||
} catch (error) {
|
|
||||||
this.messagePublisher.publish(
|
|
||||||
'logging.user.update.crit',
|
|
||||||
JSON.stringify({
|
|
||||||
command,
|
|
||||||
error,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
import { createMap, forMember, ignore, Mapper } from '@automapper/core';
|
|
||||||
import { AutomapperProfile, InjectMapper } from '@automapper/nestjs';
|
|
||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { UserPresenter } from '../adapters/primaries/user.presenter';
|
|
||||||
import { CreateUserRequest } from '../domain/dtos/create-user.request';
|
|
||||||
import { UpdateUserRequest } from '../domain/dtos/update-user.request';
|
|
||||||
import { User } from '../domain/entities/user';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class UserProfile extends AutomapperProfile {
|
|
||||||
constructor(@InjectMapper() mapper: Mapper) {
|
|
||||||
super(mapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
override get profile() {
|
|
||||||
return (mapper) => {
|
|
||||||
createMap(mapper, User, UserPresenter);
|
|
||||||
|
|
||||||
createMap(mapper, CreateUserRequest, User);
|
|
||||||
|
|
||||||
createMap(
|
|
||||||
mapper,
|
|
||||||
UpdateUserRequest,
|
|
||||||
User,
|
|
||||||
forMember((dest) => dest.uuid, ignore()),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
import { FindAllUsersRequest } from '../domain/dtos/find-all-users.request';
|
|
||||||
|
|
||||||
export class FindAllUsersQuery {
|
|
||||||
page: number;
|
|
||||||
perPage: number;
|
|
||||||
|
|
||||||
constructor(findAllUsersRequest?: FindAllUsersRequest) {
|
|
||||||
this.page = findAllUsersRequest?.page ?? 1;
|
|
||||||
this.perPage = findAllUsersRequest?.perPage ?? 10;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { FindUserByUuidRequest } from '../domain/dtos/find-user-by-uuid.request';
|
|
||||||
|
|
||||||
export class FindUserByUuidQuery {
|
|
||||||
readonly uuid: string;
|
|
||||||
|
|
||||||
constructor(findUserByUuidRequest: FindUserByUuidRequest) {
|
|
||||||
this.uuid = findUserByUuidRequest.uuid;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { UsersRepository } from '../../adapters/secondaries/users.repository';
|
|
||||||
import { FindAllUsersRequest } from '../../domain/dtos/find-all-users.request';
|
|
||||||
import { FindAllUsersUseCase } from '../../domain/usecases/find-all-users.usecase';
|
|
||||||
import { FindAllUsersQuery } from '../../queries/find-all-users.query';
|
|
||||||
|
|
||||||
const findAllUsersRequest: FindAllUsersRequest = new FindAllUsersRequest();
|
|
||||||
findAllUsersRequest.page = 1;
|
|
||||||
findAllUsersRequest.perPage = 10;
|
|
||||||
|
|
||||||
const findAllUsersQuery: FindAllUsersQuery = new FindAllUsersQuery(
|
|
||||||
findAllUsersRequest,
|
|
||||||
);
|
|
||||||
|
|
||||||
const mockUsers = [
|
|
||||||
{
|
|
||||||
uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
|
|
||||||
firstName: 'John',
|
|
||||||
lastName: 'Doe',
|
|
||||||
email: 'john.doe@email.com',
|
|
||||||
phone: '0601020304',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uuid: 'bb281075-1b98-4456-89d6-c643d3044a92',
|
|
||||||
firstName: 'Jane',
|
|
||||||
lastName: 'Doe',
|
|
||||||
email: 'jane.doe@email.com',
|
|
||||||
phone: '0602030405',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uuid: 'bb281075-1b98-4456-89d6-c643d3044a93',
|
|
||||||
firstName: 'Jimmy',
|
|
||||||
lastName: 'Doe',
|
|
||||||
email: 'jimmy.doe@email.com',
|
|
||||||
phone: '0603040506',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const mockUsersRepository = {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
findAll: jest.fn().mockImplementation((query?: FindAllUsersQuery) => {
|
|
||||||
return Promise.resolve(mockUsers);
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('FindAllUsersUseCase', () => {
|
|
||||||
let findAllUsersUseCase: FindAllUsersUseCase;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
providers: [
|
|
||||||
{
|
|
||||||
provide: UsersRepository,
|
|
||||||
useValue: mockUsersRepository,
|
|
||||||
},
|
|
||||||
FindAllUsersUseCase,
|
|
||||||
],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
findAllUsersUseCase = module.get<FindAllUsersUseCase>(FindAllUsersUseCase);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(findAllUsersUseCase).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('execute', () => {
|
|
||||||
it('should return an array filled with users', async () => {
|
|
||||||
const users = await findAllUsersUseCase.execute(findAllUsersQuery);
|
|
||||||
|
|
||||||
expect(users).toBe(mockUsers);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,82 +0,0 @@
|
||||||
import { NotFoundException } from '@nestjs/common';
|
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { UsersRepository } from '../../adapters/secondaries/users.repository';
|
|
||||||
import { FindUserByUuidRequest } from '../../domain/dtos/find-user-by-uuid.request';
|
|
||||||
import { FindUserByUuidUseCase } from '../../domain/usecases/find-user-by-uuid.usecase';
|
|
||||||
import { FindUserByUuidQuery } from '../../queries/find-user-by-uuid.query';
|
|
||||||
import { MESSAGE_PUBLISHER } from '../../../../app.constants';
|
|
||||||
|
|
||||||
const mockUser = {
|
|
||||||
uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
|
|
||||||
firstName: 'John',
|
|
||||||
lastName: 'Doe',
|
|
||||||
email: 'john.doe@email.com',
|
|
||||||
phone: '0601020304',
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockUserRepository = {
|
|
||||||
findOneByUuid: jest
|
|
||||||
.fn()
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
.mockImplementationOnce((query?: FindUserByUuidQuery) => {
|
|
||||||
return Promise.resolve(mockUser);
|
|
||||||
})
|
|
||||||
.mockImplementation(() => {
|
|
||||||
return Promise.resolve(undefined);
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockMessagePublisher = {
|
|
||||||
publish: jest.fn().mockImplementation(),
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('FindUserByUuidUseCase', () => {
|
|
||||||
let findUserByUuidUseCase: FindUserByUuidUseCase;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
imports: [],
|
|
||||||
providers: [
|
|
||||||
{
|
|
||||||
provide: UsersRepository,
|
|
||||||
useValue: mockUserRepository,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: MESSAGE_PUBLISHER,
|
|
||||||
useValue: mockMessagePublisher,
|
|
||||||
},
|
|
||||||
FindUserByUuidUseCase,
|
|
||||||
],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
findUserByUuidUseCase = module.get<FindUserByUuidUseCase>(
|
|
||||||
FindUserByUuidUseCase,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(findUserByUuidUseCase).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('execute', () => {
|
|
||||||
it('should return a user', async () => {
|
|
||||||
const findUserByUuidRequest: FindUserByUuidRequest =
|
|
||||||
new FindUserByUuidRequest();
|
|
||||||
findUserByUuidRequest.uuid = 'bb281075-1b98-4456-89d6-c643d3044a91';
|
|
||||||
const user = await findUserByUuidUseCase.execute(
|
|
||||||
new FindUserByUuidQuery(findUserByUuidRequest),
|
|
||||||
);
|
|
||||||
expect(user).toBe(mockUser);
|
|
||||||
});
|
|
||||||
it('should throw an error if user does not exist', async () => {
|
|
||||||
const findUserByUuidRequest: FindUserByUuidRequest =
|
|
||||||
new FindUserByUuidRequest();
|
|
||||||
findUserByUuidRequest.uuid = 'bb281075-1b98-4456-89d6-c643d3044a90';
|
|
||||||
await expect(
|
|
||||||
findUserByUuidUseCase.execute(
|
|
||||||
new FindUserByUuidQuery(findUserByUuidRequest),
|
|
||||||
),
|
|
||||||
).rejects.toBeInstanceOf(NotFoundException);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
File diff suppressed because it is too large
Load Diff
|
@ -34,7 +34,7 @@
|
||||||
"@grpc/proto-loader": "^0.7.4",
|
"@grpc/proto-loader": "^0.7.4",
|
||||||
"@liaoliaots/nestjs-redis": "^9.0.5",
|
"@liaoliaots/nestjs-redis": "^9.0.5",
|
||||||
"@mobicoop/configuration-module": "^1.2.0",
|
"@mobicoop/configuration-module": "^1.2.0",
|
||||||
"@mobicoop/ddd-library": "^0.3.0",
|
"@mobicoop/ddd-library": "file:../../packages/dddlibrary",
|
||||||
"@mobicoop/health-module": "^2.0.0",
|
"@mobicoop/health-module": "^2.0.0",
|
||||||
"@mobicoop/message-broker-module": "^1.2.0",
|
"@mobicoop/message-broker-module": "^1.2.0",
|
||||||
"@nestjs/cache-manager": "^1.0.0",
|
"@nestjs/cache-manager": "^1.0.0",
|
||||||
|
@ -87,6 +87,7 @@
|
||||||
"modulePathIgnorePatterns": [
|
"modulePathIgnorePatterns": [
|
||||||
".module.ts",
|
".module.ts",
|
||||||
".dto.ts",
|
".dto.ts",
|
||||||
|
".constants.ts",
|
||||||
".di-tokens.ts",
|
".di-tokens.ts",
|
||||||
".response.ts",
|
".response.ts",
|
||||||
".port.ts",
|
".port.ts",
|
||||||
|
@ -104,6 +105,7 @@
|
||||||
"coveragePathIgnorePatterns": [
|
"coveragePathIgnorePatterns": [
|
||||||
".module.ts",
|
".module.ts",
|
||||||
".dto.ts",
|
".dto.ts",
|
||||||
|
".constants.ts",
|
||||||
".di-tokens.ts",
|
".di-tokens.ts",
|
||||||
".response.ts",
|
".response.ts",
|
||||||
".port.ts",
|
".port.ts",
|
||||||
|
|
|
@ -2,7 +2,9 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { OnEvent } from '@nestjs/event-emitter';
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
import { MessagePublisherPort } from '@mobicoop/ddd-library';
|
import { MessagePublisherPort } from '@mobicoop/ddd-library';
|
||||||
import { USER_MESSAGE_PUBLISHER } from '@modules/user/user.di-tokens';
|
import { USER_MESSAGE_PUBLISHER } from '@modules/user/user.di-tokens';
|
||||||
import { UserCreatedDomainEvent } from '../../domain/events/user-created.domain-events';
|
import { UserCreatedDomainEvent } from '../../domain/events/user-created.domain-event';
|
||||||
|
import { UserCreatedIntegrationEvent } from '../events/user-created.integration-event';
|
||||||
|
import { USER_CREATED_ROUTING_KEY } from '@modules/user/user.constants';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PublishMessageWhenUserIsCreatedDomainEventHandler {
|
export class PublishMessageWhenUserIsCreatedDomainEventHandler {
|
||||||
|
@ -13,6 +15,17 @@ export class PublishMessageWhenUserIsCreatedDomainEventHandler {
|
||||||
|
|
||||||
@OnEvent(UserCreatedDomainEvent.name, { async: true, promisify: true })
|
@OnEvent(UserCreatedDomainEvent.name, { async: true, promisify: true })
|
||||||
async handle(event: UserCreatedDomainEvent): Promise<any> {
|
async handle(event: UserCreatedDomainEvent): Promise<any> {
|
||||||
this.messagePublisher.publish('user.created', JSON.stringify(event));
|
const userCreatedIntegrationEvent = new UserCreatedIntegrationEvent({
|
||||||
|
id: event.aggregateId,
|
||||||
|
firstName: event.firstName,
|
||||||
|
lastName: event.lastName,
|
||||||
|
email: event.email,
|
||||||
|
phone: event.phone,
|
||||||
|
metadata: event.metadata,
|
||||||
|
});
|
||||||
|
this.messagePublisher.publish(
|
||||||
|
USER_CREATED_ROUTING_KEY,
|
||||||
|
JSON.stringify(userCreatedIntegrationEvent),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { OnEvent } from '@nestjs/event-emitter';
|
||||||
import { MessagePublisherPort } from '@mobicoop/ddd-library';
|
import { MessagePublisherPort } from '@mobicoop/ddd-library';
|
||||||
import { USER_MESSAGE_PUBLISHER } from '@modules/user/user.di-tokens';
|
import { USER_MESSAGE_PUBLISHER } from '@modules/user/user.di-tokens';
|
||||||
import { UserDeletedDomainEvent } from '../../domain/events/user-deleted.domain-event';
|
import { UserDeletedDomainEvent } from '../../domain/events/user-deleted.domain-event';
|
||||||
|
import { UserDeletedIntegrationEvent } from '../events/user-deleted.integration-event';
|
||||||
|
import { USER_DELETED_ROUTING_KEY } from '@modules/user/user.constants';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PublishMessageWhenUserIsDeletedDomainEventHandler {
|
export class PublishMessageWhenUserIsDeletedDomainEventHandler {
|
||||||
|
@ -13,6 +15,13 @@ export class PublishMessageWhenUserIsDeletedDomainEventHandler {
|
||||||
|
|
||||||
@OnEvent(UserDeletedDomainEvent.name, { async: true, promisify: true })
|
@OnEvent(UserDeletedDomainEvent.name, { async: true, promisify: true })
|
||||||
async handle(event: UserDeletedDomainEvent): Promise<any> {
|
async handle(event: UserDeletedDomainEvent): Promise<any> {
|
||||||
this.messagePublisher.publish('user.deleted', JSON.stringify(event));
|
const userDeletedIntegrationEvent = new UserDeletedIntegrationEvent({
|
||||||
|
id: event.aggregateId,
|
||||||
|
metadata: event.metadata,
|
||||||
|
});
|
||||||
|
this.messagePublisher.publish(
|
||||||
|
USER_DELETED_ROUTING_KEY,
|
||||||
|
JSON.stringify(userDeletedIntegrationEvent),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,9 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { OnEvent } from '@nestjs/event-emitter';
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
import { MessagePublisherPort } from '@mobicoop/ddd-library';
|
import { MessagePublisherPort } from '@mobicoop/ddd-library';
|
||||||
import { USER_MESSAGE_PUBLISHER } from '@modules/user/user.di-tokens';
|
import { USER_MESSAGE_PUBLISHER } from '@modules/user/user.di-tokens';
|
||||||
import { UserUpdatedDomainEvent } from '../../domain/events/user-updated.domain-events';
|
import { UserUpdatedDomainEvent } from '../../domain/events/user-updated.domain-event';
|
||||||
|
import { UserUpdatedIntegrationEvent } from '../events/user-updated.integration-event';
|
||||||
|
import { USER_UPDATED_ROUTING_KEY } from '@modules/user/user.constants';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PublishMessageWhenUserIsUpdatedDomainEventHandler {
|
export class PublishMessageWhenUserIsUpdatedDomainEventHandler {
|
||||||
|
@ -13,6 +15,17 @@ export class PublishMessageWhenUserIsUpdatedDomainEventHandler {
|
||||||
|
|
||||||
@OnEvent(UserUpdatedDomainEvent.name, { async: true, promisify: true })
|
@OnEvent(UserUpdatedDomainEvent.name, { async: true, promisify: true })
|
||||||
async handle(event: UserUpdatedDomainEvent): Promise<any> {
|
async handle(event: UserUpdatedDomainEvent): Promise<any> {
|
||||||
this.messagePublisher.publish('user.updated', JSON.stringify(event));
|
const userUpdatedIntegrationEvent = new UserUpdatedIntegrationEvent({
|
||||||
|
id: event.aggregateId,
|
||||||
|
firstName: event.firstName,
|
||||||
|
lastName: event.lastName,
|
||||||
|
email: event.email,
|
||||||
|
phone: event.phone,
|
||||||
|
metadata: event.metadata,
|
||||||
|
});
|
||||||
|
this.messagePublisher.publish(
|
||||||
|
USER_UPDATED_ROUTING_KEY,
|
||||||
|
JSON.stringify(userUpdatedIntegrationEvent),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { IntegrationEvent, IntegrationEventProps } from '@mobicoop/ddd-library';
|
||||||
|
|
||||||
|
export class UserCreatedIntegrationEvent extends IntegrationEvent {
|
||||||
|
readonly firstName: string;
|
||||||
|
readonly lastName: string;
|
||||||
|
readonly email: string;
|
||||||
|
readonly phone: string;
|
||||||
|
|
||||||
|
constructor(props: IntegrationEventProps<UserCreatedIntegrationEvent>) {
|
||||||
|
super(props);
|
||||||
|
this.firstName = props.firstName;
|
||||||
|
this.lastName = props.lastName;
|
||||||
|
this.email = props.email;
|
||||||
|
this.phone = props.phone;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { IntegrationEvent, IntegrationEventProps } from '@mobicoop/ddd-library';
|
||||||
|
|
||||||
|
export class UserDeletedIntegrationEvent extends IntegrationEvent {
|
||||||
|
constructor(props: IntegrationEventProps<UserDeletedIntegrationEvent>) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { IntegrationEvent, IntegrationEventProps } from '@mobicoop/ddd-library';
|
||||||
|
|
||||||
|
export class UserUpdatedIntegrationEvent extends IntegrationEvent {
|
||||||
|
readonly firstName: string;
|
||||||
|
readonly lastName: string;
|
||||||
|
readonly email: string;
|
||||||
|
readonly phone: string;
|
||||||
|
|
||||||
|
constructor(props: IntegrationEventProps<UserUpdatedIntegrationEvent>) {
|
||||||
|
super(props);
|
||||||
|
this.firstName = props.firstName;
|
||||||
|
this.lastName = props.lastName;
|
||||||
|
this.email = props.email;
|
||||||
|
this.phone = props.phone;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
|
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
import { CreateUserProps, UpdateUserProps, UserProps } from './user.types';
|
import { CreateUserProps, UpdateUserProps, UserProps } from './user.types';
|
||||||
import { UserCreatedDomainEvent } from './events/user-created.domain-events';
|
import { UserCreatedDomainEvent } from './events/user-created.domain-event';
|
||||||
import { UserUpdatedDomainEvent } from './events/user-updated.domain-events';
|
import { UserUpdatedDomainEvent } from './events/user-updated.domain-event';
|
||||||
import { UserDeletedDomainEvent } from './events/user-deleted.domain-event';
|
import { UserDeletedDomainEvent } from './events/user-deleted.domain-event';
|
||||||
|
|
||||||
export class UserEntity extends AggregateRoot<UserProps> {
|
export class UserEntity extends AggregateRoot<UserProps> {
|
||||||
|
|
|
@ -11,22 +11,27 @@ import { UserRepositoryPort } from '../core/application/ports/user.repository.po
|
||||||
import { UserMapper } from '../user.mapper';
|
import { UserMapper } from '../user.mapper';
|
||||||
import { USER_MESSAGE_PUBLISHER } from '../user.di-tokens';
|
import { USER_MESSAGE_PUBLISHER } from '../user.di-tokens';
|
||||||
|
|
||||||
export type UserModel = {
|
export type UserBaseModel = {
|
||||||
uuid: string;
|
uuid: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
email: string;
|
email: string;
|
||||||
phone: string;
|
phone: string;
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type UserReadModel = UserBaseModel & {
|
||||||
|
createdAt?: Date;
|
||||||
|
updatedAt?: Date;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UserWriteModel = UserBaseModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repository is used for retrieving/saving domain entities
|
* Repository is used for retrieving/saving domain entities
|
||||||
* */
|
* */
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserRepository
|
export class UserRepository
|
||||||
extends PrismaRepositoryBase<UserEntity, UserModel, UserModel>
|
extends PrismaRepositoryBase<UserEntity, UserReadModel, UserWriteModel>
|
||||||
implements UserRepositoryPort
|
implements UserRepositoryPort
|
||||||
{
|
{
|
||||||
constructor(
|
constructor(
|
||||||
|
|
|
@ -25,11 +25,12 @@ import {
|
||||||
export class UpdateUserGrpcController {
|
export class UpdateUserGrpcController {
|
||||||
constructor(private readonly commandBus: CommandBus) {}
|
constructor(private readonly commandBus: CommandBus) {}
|
||||||
|
|
||||||
@GrpcMethod('UserService', 'UpdateUser')
|
@GrpcMethod('UserService', 'Update')
|
||||||
async updateUser(data: UpdateUserRequestDto): Promise<IdResponse> {
|
async updateUser(data: UpdateUserRequestDto): Promise<IdResponse> {
|
||||||
try {
|
try {
|
||||||
const aggregateID: AggregateID = await this.commandBus.execute(
|
const aggregateID: AggregateID = await this.commandBus.execute(
|
||||||
new UpdateUserCommand({
|
new UpdateUserCommand({
|
||||||
|
id: data.id,
|
||||||
firstName: data.firstName,
|
firstName: data.firstName,
|
||||||
lastName: data.lastName,
|
lastName: data.lastName,
|
||||||
email: data.email,
|
email: data.email,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { PublishMessageWhenUserIsCreatedDomainEventHandler } from '@modules/user/core/application/event-handlers/publish-message-when-user-is-created.domain-event-handler';
|
import { PublishMessageWhenUserIsCreatedDomainEventHandler } from '@modules/user/core/application/event-handlers/publish-message-when-user-is-created.domain-event-handler';
|
||||||
import { USER_MESSAGE_PUBLISHER } from '@modules/user/user.di-tokens';
|
import { USER_MESSAGE_PUBLISHER } from '@modules/user/user.di-tokens';
|
||||||
import { UserCreatedDomainEvent } from '@modules/user/core/domain/events/user-created.domain-events';
|
import { UserCreatedDomainEvent } from '@modules/user/core/domain/events/user-created.domain-event';
|
||||||
|
|
||||||
const mockMessagePublisher = {
|
const mockMessagePublisher = {
|
||||||
publish: jest.fn().mockImplementation(),
|
publish: jest.fn().mockImplementation(),
|
||||||
|
@ -48,7 +48,7 @@ describe('Publish message when user is created domain event handler', () => {
|
||||||
expect(mockMessagePublisher.publish).toHaveBeenCalledTimes(1);
|
expect(mockMessagePublisher.publish).toHaveBeenCalledTimes(1);
|
||||||
expect(mockMessagePublisher.publish).toHaveBeenCalledWith(
|
expect(mockMessagePublisher.publish).toHaveBeenCalledWith(
|
||||||
'user.created',
|
'user.created',
|
||||||
'{"id":"some-domain-event-id","aggregateId":"some-aggregate-id","firstName":"John","lastName":"Doe","email":"john.doe@email.com","phone":"+33611223344","metadata":{"timestamp":1687928400000,"correlationId":"some-correlation-id"}}',
|
'{"id":"some-aggregate-id","metadata":{"correlationId":"some-correlation-id","timestamp":1687928400000},"firstName":"John","lastName":"Doe","email":"john.doe@email.com","phone":"+33611223344"}',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { USER_MESSAGE_PUBLISHER } from '@modules/user/user.di-tokens';
|
import { USER_MESSAGE_PUBLISHER } from '@modules/user/user.di-tokens';
|
||||||
import { UserCreatedDomainEvent } from '@modules/user/core/domain/events/user-created.domain-events';
|
import { UserCreatedDomainEvent } from '@modules/user/core/domain/events/user-created.domain-event';
|
||||||
import { PublishMessageWhenUserIsDeletedDomainEventHandler } from '@modules/user/core/application/event-handlers/publish-message-when-user-is-deleted.domain-event-handler';
|
import { PublishMessageWhenUserIsDeletedDomainEventHandler } from '@modules/user/core/application/event-handlers/publish-message-when-user-is-deleted.domain-event-handler';
|
||||||
|
|
||||||
const mockMessagePublisher = {
|
const mockMessagePublisher = {
|
||||||
|
@ -48,7 +48,7 @@ describe('Publish message when user is deleted domain event handler', () => {
|
||||||
expect(mockMessagePublisher.publish).toHaveBeenCalledTimes(1);
|
expect(mockMessagePublisher.publish).toHaveBeenCalledTimes(1);
|
||||||
expect(mockMessagePublisher.publish).toHaveBeenCalledWith(
|
expect(mockMessagePublisher.publish).toHaveBeenCalledWith(
|
||||||
'user.deleted',
|
'user.deleted',
|
||||||
'{"id":"some-domain-event-id","aggregateId":"some-aggregate-id","firstName":"John","lastName":"Doe","email":"john.doe@email.com","phone":"+33611223344","metadata":{"timestamp":1687928400000,"correlationId":"some-correlation-id"}}',
|
'{"id":"some-aggregate-id","metadata":{"correlationId":"some-correlation-id","timestamp":1687928400000}}',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { USER_MESSAGE_PUBLISHER } from '@modules/user/user.di-tokens';
|
import { USER_MESSAGE_PUBLISHER } from '@modules/user/user.di-tokens';
|
||||||
import { PublishMessageWhenUserIsUpdatedDomainEventHandler } from '@modules/user/core/application/event-handlers/publish-message-when-user-is-updated.domain-event-handler';
|
import { PublishMessageWhenUserIsUpdatedDomainEventHandler } from '@modules/user/core/application/event-handlers/publish-message-when-user-is-updated.domain-event-handler';
|
||||||
import { UserUpdatedDomainEvent } from '@modules/user/core/domain/events/user-updated.domain-events';
|
import { UserUpdatedDomainEvent } from '@modules/user/core/domain/events/user-updated.domain-event';
|
||||||
|
|
||||||
const mockMessagePublisher = {
|
const mockMessagePublisher = {
|
||||||
publish: jest.fn().mockImplementation(),
|
publish: jest.fn().mockImplementation(),
|
||||||
|
@ -48,7 +48,7 @@ describe('Publish message when user is updated domain event handler', () => {
|
||||||
expect(mockMessagePublisher.publish).toHaveBeenCalledTimes(1);
|
expect(mockMessagePublisher.publish).toHaveBeenCalledTimes(1);
|
||||||
expect(mockMessagePublisher.publish).toHaveBeenCalledWith(
|
expect(mockMessagePublisher.publish).toHaveBeenCalledWith(
|
||||||
'user.updated',
|
'user.updated',
|
||||||
'{"id":"some-domain-event-id","aggregateId":"some-aggregate-id","firstName":"Jane","lastName":"Doe","email":"jane.doe@email.com","phone":"+33611223344","metadata":{"timestamp":1687928400000,"correlationId":"some-correlation-id"}}',
|
'{"id":"some-aggregate-id","metadata":{"correlationId":"some-correlation-id","timestamp":1687928400000},"firstName":"Jane","lastName":"Doe","email":"jane.doe@email.com","phone":"+33611223344"}',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { UserCreatedDomainEvent } from '@modules/user/core/domain/events/user-created.domain-events';
|
import { UserCreatedDomainEvent } from '@modules/user/core/domain/events/user-created.domain-event';
|
||||||
import { UserDeletedDomainEvent } from '@modules/user/core/domain/events/user-deleted.domain-event';
|
import { UserDeletedDomainEvent } from '@modules/user/core/domain/events/user-deleted.domain-event';
|
||||||
import { UserUpdatedDomainEvent } from '@modules/user/core/domain/events/user-updated.domain-events';
|
import { UserUpdatedDomainEvent } from '@modules/user/core/domain/events/user-updated.domain-event';
|
||||||
import { UserEntity } from '@modules/user/core/domain/user.entity';
|
import { UserEntity } from '@modules/user/core/domain/user.entity';
|
||||||
import {
|
import {
|
||||||
CreateUserProps,
|
CreateUserProps,
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import { UserEntity } from '@modules/user/core/domain/user.entity';
|
import { UserEntity } from '@modules/user/core/domain/user.entity';
|
||||||
import { UserModel } from '@modules/user/infrastructure/user.repository';
|
import {
|
||||||
|
UserReadModel,
|
||||||
|
UserWriteModel,
|
||||||
|
} from '@modules/user/infrastructure/user.repository';
|
||||||
import { UserResponseDto } from '@modules/user/interface/dtos/user.response.dto';
|
import { UserResponseDto } from '@modules/user/interface/dtos/user.response.dto';
|
||||||
import { UserMapper } from '@modules/user/user.mapper';
|
import { UserMapper } from '@modules/user/user.mapper';
|
||||||
import { Test } from '@nestjs/testing';
|
import { Test } from '@nestjs/testing';
|
||||||
|
@ -16,7 +19,7 @@ const userEntity: UserEntity = new UserEntity({
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
});
|
});
|
||||||
const userModel: UserModel = {
|
const userReadModel: UserReadModel = {
|
||||||
uuid: 'c160cf8c-f057-4962-841f-3ad68346df44',
|
uuid: 'c160cf8c-f057-4962-841f-3ad68346df44',
|
||||||
firstName: 'John',
|
firstName: 'John',
|
||||||
lastName: 'Doe',
|
lastName: 'Doe',
|
||||||
|
@ -41,12 +44,12 @@ describe('User Mapper', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should map domain entity to persistence data', async () => {
|
it('should map domain entity to persistence data', async () => {
|
||||||
const mapped: UserModel = userMapper.toPersistence(userEntity);
|
const mapped: UserWriteModel = userMapper.toPersistence(userEntity);
|
||||||
expect(mapped.lastName).toBe('Doe');
|
expect(mapped.lastName).toBe('Doe');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should map persisted data to domain entity', async () => {
|
it('should map persisted data to domain entity', async () => {
|
||||||
const mapped: UserEntity = userMapper.toDomain(userModel);
|
const mapped: UserEntity = userMapper.toDomain(userReadModel);
|
||||||
expect(mapped.getProps().firstName).toBe('John');
|
expect(mapped.getProps().firstName).toBe('John');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
export const USER_CREATED_ROUTING_KEY = 'user.created';
|
||||||
|
export const USER_UPDATED_ROUTING_KEY = 'user.updated';
|
||||||
|
export const USER_DELETED_ROUTING_KEY = 'user.deleted';
|
|
@ -1,7 +1,10 @@
|
||||||
import { Mapper } from '@mobicoop/ddd-library';
|
import { Mapper } from '@mobicoop/ddd-library';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { UserEntity } from './core/domain/user.entity';
|
import { UserEntity } from './core/domain/user.entity';
|
||||||
import { UserModel } from './infrastructure/user.repository';
|
import {
|
||||||
|
UserReadModel,
|
||||||
|
UserWriteModel,
|
||||||
|
} from './infrastructure/user.repository';
|
||||||
import { UserResponseDto } from './interface/dtos/user.response.dto';
|
import { UserResponseDto } from './interface/dtos/user.response.dto';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -13,23 +16,21 @@ import { UserResponseDto } from './interface/dtos/user.response.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserMapper
|
export class UserMapper
|
||||||
implements Mapper<UserEntity, UserModel, UserModel, UserResponseDto>
|
implements Mapper<UserEntity, UserReadModel, UserWriteModel, UserResponseDto>
|
||||||
{
|
{
|
||||||
toPersistence = (entity: UserEntity): UserModel => {
|
toPersistence = (entity: UserEntity): UserWriteModel => {
|
||||||
const copy = entity.getProps();
|
const copy = entity.getProps();
|
||||||
const record: UserModel = {
|
const record: UserWriteModel = {
|
||||||
uuid: copy.id,
|
uuid: copy.id,
|
||||||
firstName: copy.firstName,
|
firstName: copy.firstName,
|
||||||
lastName: copy.lastName,
|
lastName: copy.lastName,
|
||||||
email: copy.email,
|
email: copy.email,
|
||||||
phone: copy.phone,
|
phone: copy.phone,
|
||||||
createdAt: copy.createdAt,
|
|
||||||
updatedAt: copy.updatedAt,
|
|
||||||
};
|
};
|
||||||
return record;
|
return record;
|
||||||
};
|
};
|
||||||
|
|
||||||
toDomain = (record: UserModel): UserEntity => {
|
toDomain = (record: UserReadModel): UserEntity => {
|
||||||
const entity = new UserEntity({
|
const entity = new UserEntity({
|
||||||
id: record.uuid,
|
id: record.uuid,
|
||||||
createdAt: new Date(record.createdAt),
|
createdAt: new Date(record.createdAt),
|
||||||
|
@ -53,12 +54,4 @@ export class UserMapper
|
||||||
response.phone = props.phone;
|
response.phone = props.phone;
|
||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ^ Data returned to the user is whitelisted to avoid leaks.
|
|
||||||
If a new property is added, like password or a
|
|
||||||
credit card number, it won't be returned
|
|
||||||
unless you specifically allow this.
|
|
||||||
(avoid blacklisting, which will return everything
|
|
||||||
but blacklisted items, which can lead to a data leak).
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,13 @@ import { USER_MESSAGE_PUBLISHER, USER_REPOSITORY } from './user.di-tokens';
|
||||||
import { UserRepository } from './infrastructure/user.repository';
|
import { UserRepository } from './infrastructure/user.repository';
|
||||||
import { UserMapper } from './user.mapper';
|
import { UserMapper } from './user.mapper';
|
||||||
import { PrismaService } from './infrastructure/prisma.service';
|
import { PrismaService } from './infrastructure/prisma.service';
|
||||||
|
import { DeleteUserGrpcController } from './interface/grpc-controllers/delete-user.grpc.controller';
|
||||||
|
import { FindUserByIdGrpcController } from './interface/grpc-controllers/find-user-by-id.grpc.controller';
|
||||||
|
import { DeleteUserService } from './core/application/commands/delete-user/delete-user.service';
|
||||||
|
import { FindUserByIdQueryHandler } from './core/application/queries/find-user-by-id/find-user-by-id.query-handler';
|
||||||
|
import { PublishMessageWhenUserIsCreatedDomainEventHandler } from './core/application/event-handlers/publish-message-when-user-is-created.domain-event-handler';
|
||||||
|
import { PublishMessageWhenUserIsUpdatedDomainEventHandler } from './core/application/event-handlers/publish-message-when-user-is-updated.domain-event-handler';
|
||||||
|
import { PublishMessageWhenUserIsDeletedDomainEventHandler } from './core/application/event-handlers/publish-message-when-user-is-deleted.domain-event-handler';
|
||||||
|
|
||||||
const imports = [
|
const imports = [
|
||||||
CqrsModule,
|
CqrsModule,
|
||||||
|
@ -30,9 +37,26 @@ const imports = [
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
const grpcControllers = [CreateUserGrpcController, UpdateUserGrpcController];
|
const grpcControllers = [
|
||||||
|
CreateUserGrpcController,
|
||||||
|
UpdateUserGrpcController,
|
||||||
|
DeleteUserGrpcController,
|
||||||
|
FindUserByIdGrpcController,
|
||||||
|
];
|
||||||
|
|
||||||
const commandHandlers: Provider[] = [CreateUserService, UpdateUserService];
|
const eventHandlers: Provider[] = [
|
||||||
|
PublishMessageWhenUserIsCreatedDomainEventHandler,
|
||||||
|
PublishMessageWhenUserIsUpdatedDomainEventHandler,
|
||||||
|
PublishMessageWhenUserIsDeletedDomainEventHandler,
|
||||||
|
];
|
||||||
|
|
||||||
|
const commandHandlers: Provider[] = [
|
||||||
|
CreateUserService,
|
||||||
|
UpdateUserService,
|
||||||
|
DeleteUserService,
|
||||||
|
];
|
||||||
|
|
||||||
|
const queryHandlers: Provider[] = [FindUserByIdQueryHandler];
|
||||||
|
|
||||||
const mappers: Provider[] = [UserMapper];
|
const mappers: Provider[] = [UserMapper];
|
||||||
|
|
||||||
|
@ -56,7 +80,9 @@ const orms: Provider[] = [PrismaService];
|
||||||
imports,
|
imports,
|
||||||
controllers: [...grpcControllers],
|
controllers: [...grpcControllers],
|
||||||
providers: [
|
providers: [
|
||||||
|
...eventHandlers,
|
||||||
...commandHandlers,
|
...commandHandlers,
|
||||||
|
...queryHandlers,
|
||||||
...mappers,
|
...mappers,
|
||||||
...repositories,
|
...repositories,
|
||||||
...messagePublishers,
|
...messagePublishers,
|
||||||
|
|
Loading…
Reference in New Issue