mirror of
https://gitlab.com/mobicoop/v3/service/user.git
synced 2026-01-11 22:02:39 +00:00
user update
This commit is contained in:
@@ -14,10 +14,12 @@ import { MessagerModule } from './modules/messager/messager.module';
|
||||
import { USER_REPOSITORY } from './modules/user/user.di-tokens';
|
||||
import { MESSAGE_PUBLISHER } from './modules/messager/messager.di-tokens';
|
||||
import { MessagePublisherPort } from '@mobicoop/ddd-library';
|
||||
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({ isGlobal: true }),
|
||||
EventEmitterModule.forRoot(),
|
||||
ConfigurationModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
inject: [ConfigService],
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { AggregateID, ConflictException } from '@mobicoop/ddd-library';
|
||||
import {
|
||||
AggregateID,
|
||||
ConflictException,
|
||||
UniqueConstraintException,
|
||||
} from '@mobicoop/ddd-library';
|
||||
import { CreateUserCommand } from './create-user.command';
|
||||
import { USER_REPOSITORY } from '@modules/user/user.di-tokens';
|
||||
import { UserRepositoryPort } from '../../ports/user.repository.port';
|
||||
import { UserEntity } from '../../../domain/user.entity';
|
||||
import { UserAlreadyExistsException } from '../../../domain/user.errors';
|
||||
import {
|
||||
EmailAlreadyExistsException,
|
||||
PhoneAlreadyExistsException,
|
||||
UserAlreadyExistsException,
|
||||
} from '../../../domain/user.errors';
|
||||
|
||||
@CommandHandler(CreateUserCommand)
|
||||
export class CreateUserService implements ICommandHandler {
|
||||
@@ -29,6 +37,18 @@ export class CreateUserService implements ICommandHandler {
|
||||
if (error instanceof ConflictException) {
|
||||
throw new UserAlreadyExistsException(error);
|
||||
}
|
||||
if (
|
||||
error instanceof UniqueConstraintException &&
|
||||
error.message.includes('email')
|
||||
) {
|
||||
throw new EmailAlreadyExistsException(error);
|
||||
}
|
||||
if (
|
||||
error instanceof UniqueConstraintException &&
|
||||
error.message.includes('phone')
|
||||
) {
|
||||
throw new PhoneAlreadyExistsException(error);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import {
|
||||
AggregateID,
|
||||
ConflictException,
|
||||
UniqueConstraintException,
|
||||
} from '@mobicoop/ddd-library';
|
||||
import { AggregateID, UniqueConstraintException } from '@mobicoop/ddd-library';
|
||||
import { USER_REPOSITORY } from '@modules/user/user.di-tokens';
|
||||
import { UserRepositoryPort } from '../../ports/user.repository.port';
|
||||
import { UserEntity } from '../../../domain/user.entity';
|
||||
@@ -12,7 +8,6 @@ import { UpdateUserCommand } from './update-user.command';
|
||||
import {
|
||||
EmailAlreadyExistsException,
|
||||
PhoneAlreadyExistsException,
|
||||
UserAlreadyExistsException,
|
||||
} from '@modules/user/core/domain/user.errors';
|
||||
|
||||
@CommandHandler(UpdateUserCommand)
|
||||
@@ -36,9 +31,6 @@ export class UpdateUserService implements ICommandHandler {
|
||||
await this.userRepository.update(user.id, user);
|
||||
return user.id;
|
||||
} catch (error: any) {
|
||||
if (error instanceof ConflictException) {
|
||||
throw new UserAlreadyExistsException(error);
|
||||
}
|
||||
if (
|
||||
error instanceof UniqueConstraintException &&
|
||||
error.message.includes('email')
|
||||
|
||||
@@ -25,10 +25,10 @@ export class UserEntity extends AggregateRoot<UserProps> {
|
||||
};
|
||||
|
||||
update(props: UpdateUserProps): void {
|
||||
this.props.firstName = props.firstName;
|
||||
this.props.lastName = props.lastName;
|
||||
this.props.email = props.email;
|
||||
this.props.phone = props.phone;
|
||||
this.props.firstName = props.firstName ?? this.props.firstName;
|
||||
this.props.lastName = props.lastName ?? this.props.lastName;
|
||||
this.props.email = props.email ?? this.props.email;
|
||||
this.props.phone = props.phone ?? this.props.phone;
|
||||
this.addEvent(
|
||||
new UserUpdatedDomainEvent({
|
||||
aggregateId: this._id,
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
// All properties that a User has
|
||||
export interface UserProps {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
email?: string;
|
||||
phone?: string;
|
||||
}
|
||||
|
||||
// Properties that are needed for a User creation
|
||||
export interface CreateUserProps {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
email?: string;
|
||||
phone?: string;
|
||||
}
|
||||
|
||||
export interface UpdateUserProps {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
email?: string;
|
||||
phone?: string;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,11 @@ import { RpcExceptionCode } from '@mobicoop/ddd-library';
|
||||
import { RpcValidationPipe } from '@mobicoop/ddd-library';
|
||||
import { CreateUserRequestDto } from './dtos/create-user.request.dto';
|
||||
import { CreateUserCommand } from '@modules/user/core/application/commands/create-user/create-user.command';
|
||||
import { UserAlreadyExistsException } from '@modules/user/core/domain/user.errors';
|
||||
import {
|
||||
EmailAlreadyExistsException,
|
||||
PhoneAlreadyExistsException,
|
||||
UserAlreadyExistsException,
|
||||
} from '@modules/user/core/domain/user.errors';
|
||||
|
||||
@UsePipes(
|
||||
new RpcValidationPipe({
|
||||
@@ -27,7 +31,11 @@ export class CreateUserGrpcController {
|
||||
);
|
||||
return new IdResponse(aggregateID);
|
||||
} catch (error: any) {
|
||||
if (error instanceof UserAlreadyExistsException)
|
||||
if (
|
||||
error instanceof UserAlreadyExistsException ||
|
||||
error instanceof EmailAlreadyExistsException ||
|
||||
error instanceof PhoneAlreadyExistsException
|
||||
)
|
||||
throw new RpcException({
|
||||
code: RpcExceptionCode.ALREADY_EXISTS,
|
||||
message: error.message,
|
||||
@@ -3,19 +3,19 @@ syntax = "proto3";
|
||||
package user;
|
||||
|
||||
service UserService {
|
||||
rpc FindOneByUuid(UserByUuid) returns (User);
|
||||
rpc FindOneById(UserById) returns (User);
|
||||
rpc FindAll(UserFilter) returns (Users);
|
||||
rpc Create(User) returns (User);
|
||||
rpc Update(User) returns (User);
|
||||
rpc Delete(UserByUuid) returns (Empty);
|
||||
rpc Create(User) returns (UserById);
|
||||
rpc Update(User) returns (UserById);
|
||||
rpc Delete(UserById) returns (Empty);
|
||||
}
|
||||
|
||||
message UserByUuid {
|
||||
string uuid = 1;
|
||||
message UserById {
|
||||
string id = 1;
|
||||
}
|
||||
|
||||
message User {
|
||||
string uuid = 1;
|
||||
string id = 1;
|
||||
string firstName = 2;
|
||||
string lastName = 3;
|
||||
string email = 4;
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,12 +1,16 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AggregateID } from '@mobicoop/ddd-library';
|
||||
import { AggregateID, UniqueConstraintException } from '@mobicoop/ddd-library';
|
||||
import { ConflictException } from '@mobicoop/ddd-library';
|
||||
import { CreateUserRequestDto } from '@modules/user/interface/dtos/grpc-controllers/dtos/create-user.request.dto';
|
||||
import { CreateUserRequestDto } from '@modules/user/interface/grpc-controllers/dtos/create-user.request.dto';
|
||||
import { CreateUserService } from '@modules/user/core/application/commands/create-user/create-user.service';
|
||||
import { USER_REPOSITORY } from '@modules/user/user.di-tokens';
|
||||
import { UserEntity } from '@modules/user/core/domain/user.entity';
|
||||
import { CreateUserCommand } from '@modules/user/core/application/commands/create-user/create-user.command';
|
||||
import { UserAlreadyExistsException } from '@modules/user/core/domain/user.errors';
|
||||
import {
|
||||
EmailAlreadyExistsException,
|
||||
PhoneAlreadyExistsException,
|
||||
UserAlreadyExistsException,
|
||||
} from '@modules/user/core/domain/user.errors';
|
||||
|
||||
const createUserRequest: CreateUserRequestDto = {
|
||||
firstName: 'John',
|
||||
@@ -23,7 +27,13 @@ const mockUserRepository = {
|
||||
throw new Error();
|
||||
})
|
||||
.mockImplementationOnce(() => {
|
||||
throw new ConflictException('already exists');
|
||||
throw new ConflictException('User already exists');
|
||||
})
|
||||
.mockImplementationOnce(() => {
|
||||
throw new UniqueConstraintException('email already exists');
|
||||
})
|
||||
.mockImplementationOnce(() => {
|
||||
throw new UniqueConstraintException('phone already exists');
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -75,5 +85,21 @@ describe('create-user.service', () => {
|
||||
createUserService.execute(createUserCommand),
|
||||
).rejects.toBeInstanceOf(UserAlreadyExistsException);
|
||||
});
|
||||
it('should throw an exception if Email already exists', async () => {
|
||||
UserEntity.create = jest.fn().mockReturnValue({
|
||||
id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
|
||||
});
|
||||
await expect(
|
||||
createUserService.execute(createUserCommand),
|
||||
).rejects.toBeInstanceOf(EmailAlreadyExistsException);
|
||||
});
|
||||
it('should throw an exception if Phone already exists', async () => {
|
||||
UserEntity.create = jest.fn().mockReturnValue({
|
||||
id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
|
||||
});
|
||||
await expect(
|
||||
createUserService.execute(createUserCommand),
|
||||
).rejects.toBeInstanceOf(PhoneAlreadyExistsException);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
126
src/modules/user/tests/unit/core/update-user.service.spec.ts
Normal file
126
src/modules/user/tests/unit/core/update-user.service.spec.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import {
|
||||
AggregateID,
|
||||
NotFoundException,
|
||||
UniqueConstraintException,
|
||||
} from '@mobicoop/ddd-library';
|
||||
import { USER_REPOSITORY } from '@modules/user/user.di-tokens';
|
||||
import { UserEntity } from '@modules/user/core/domain/user.entity';
|
||||
import {
|
||||
EmailAlreadyExistsException,
|
||||
PhoneAlreadyExistsException,
|
||||
} from '@modules/user/core/domain/user.errors';
|
||||
import { UpdateUserRequestDto } from '@modules/user/interface/grpc-controllers/dtos/update-user.request.dto';
|
||||
import { UpdateUserService } from '@modules/user/core/application/commands/update-user/update-user.service';
|
||||
import { UpdateUserCommand } from '@modules/user/core/application/commands/update-user/update-user.command';
|
||||
|
||||
const updateFirstNameUserRequest: UpdateUserRequestDto = {
|
||||
id: 'c97b1783-76cf-4840-b298-b90b13c58894',
|
||||
firstName: 'Johnny',
|
||||
};
|
||||
|
||||
const updateEmailUserRequest: UpdateUserRequestDto = {
|
||||
id: 'c97b1783-76cf-4840-b298-b90b13c58894',
|
||||
email: 'john.doe@already.exists.email.com',
|
||||
};
|
||||
|
||||
const updatePhoneUserRequest: UpdateUserRequestDto = {
|
||||
id: 'c97b1783-76cf-4840-b298-b90b13c58894',
|
||||
phone: '+33611223344',
|
||||
};
|
||||
|
||||
const now = new Date();
|
||||
const userToUpdate: UserEntity = new UserEntity({
|
||||
id: 'c97b1783-76cf-4840-b298-b90b13c58894',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
props: {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
email: 'john.doe@email.com',
|
||||
phone: '+33611223344',
|
||||
},
|
||||
});
|
||||
|
||||
const mockUserRepository = {
|
||||
findOneById: jest
|
||||
.fn()
|
||||
.mockImplementationOnce(() => {
|
||||
throw new NotFoundException('Record not found');
|
||||
})
|
||||
.mockImplementation(() => userToUpdate),
|
||||
update: jest
|
||||
.fn()
|
||||
.mockImplementationOnce(() => 'c97b1783-76cf-4840-b298-b90b13c58894')
|
||||
.mockImplementationOnce(() => {
|
||||
throw new UniqueConstraintException('email already exists');
|
||||
})
|
||||
.mockImplementationOnce(() => {
|
||||
throw new UniqueConstraintException('phone already exists');
|
||||
})
|
||||
.mockImplementationOnce(() => {
|
||||
throw new Error();
|
||||
}),
|
||||
};
|
||||
|
||||
describe('update-user.service', () => {
|
||||
let updateUserService: UpdateUserService;
|
||||
|
||||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
{
|
||||
provide: USER_REPOSITORY,
|
||||
useValue: mockUserRepository,
|
||||
},
|
||||
UpdateUserService,
|
||||
],
|
||||
}).compile();
|
||||
|
||||
updateUserService = module.get<UpdateUserService>(UpdateUserService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(updateUserService).toBeDefined();
|
||||
});
|
||||
|
||||
describe('execution', () => {
|
||||
it('should throw an exception if use is not found', async () => {
|
||||
const updateUserCommand = new UpdateUserCommand(
|
||||
updateFirstNameUserRequest,
|
||||
);
|
||||
await expect(
|
||||
updateUserService.execute(updateUserCommand),
|
||||
).rejects.toBeInstanceOf(NotFoundException);
|
||||
});
|
||||
it('should update a user firstName', async () => {
|
||||
jest.spyOn(userToUpdate, 'update');
|
||||
const updateUserCommand = new UpdateUserCommand(
|
||||
updateFirstNameUserRequest,
|
||||
);
|
||||
const result: AggregateID = await updateUserService.execute(
|
||||
updateUserCommand,
|
||||
);
|
||||
expect(result).toBe('c97b1783-76cf-4840-b298-b90b13c58894');
|
||||
expect(userToUpdate.update).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('should throw an exception if Email already exists', async () => {
|
||||
const updateUserCommand = new UpdateUserCommand(updateEmailUserRequest);
|
||||
await expect(
|
||||
updateUserService.execute(updateUserCommand),
|
||||
).rejects.toBeInstanceOf(EmailAlreadyExistsException);
|
||||
});
|
||||
it('should throw an exception if Phone already exists', async () => {
|
||||
const updateUserCommand = new UpdateUserCommand(updatePhoneUserRequest);
|
||||
await expect(
|
||||
updateUserService.execute(updateUserCommand),
|
||||
).rejects.toBeInstanceOf(PhoneAlreadyExistsException);
|
||||
});
|
||||
it('should throw an error if something bad happens', async () => {
|
||||
const updateUserCommand = new UpdateUserCommand(updatePhoneUserRequest);
|
||||
await expect(
|
||||
updateUserService.execute(updateUserCommand),
|
||||
).rejects.toBeInstanceOf(Error);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3,9 +3,13 @@ import { RpcExceptionCode } from '@mobicoop/ddd-library';
|
||||
import { CommandBus } from '@nestjs/cqrs';
|
||||
import { RpcException } from '@nestjs/microservices';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { UserAlreadyExistsException } from '@modules/user/core/domain/user.errors';
|
||||
import { CreateUserGrpcController } from '@modules/user/interface/dtos/grpc-controllers/create-user.grpc.controller';
|
||||
import { CreateUserRequestDto } from '@modules/user/interface/dtos/grpc-controllers/dtos/create-user.request.dto';
|
||||
import {
|
||||
EmailAlreadyExistsException,
|
||||
PhoneAlreadyExistsException,
|
||||
UserAlreadyExistsException,
|
||||
} from '@modules/user/core/domain/user.errors';
|
||||
import { CreateUserGrpcController } from '@modules/user/interface/grpc-controllers/create-user.grpc.controller';
|
||||
import { CreateUserRequestDto } from '@modules/user/interface/grpc-controllers/dtos/create-user.request.dto';
|
||||
|
||||
const createUserRequest: CreateUserRequestDto = {
|
||||
firstName: 'John',
|
||||
@@ -21,6 +25,12 @@ const mockCommandBus = {
|
||||
.mockImplementationOnce(() => {
|
||||
throw new UserAlreadyExistsException();
|
||||
})
|
||||
.mockImplementationOnce(() => {
|
||||
throw new EmailAlreadyExistsException();
|
||||
})
|
||||
.mockImplementationOnce(() => {
|
||||
throw new PhoneAlreadyExistsException();
|
||||
})
|
||||
.mockImplementationOnce(() => {
|
||||
throw new Error();
|
||||
}),
|
||||
@@ -75,6 +85,30 @@ describe('Create User Grpc Controller', () => {
|
||||
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should throw a dedicated RpcException if email already exists', async () => {
|
||||
jest.spyOn(mockCommandBus, 'execute');
|
||||
expect.assertions(3);
|
||||
try {
|
||||
await createUserGrpcController.create(createUserRequest);
|
||||
} catch (e: any) {
|
||||
expect(e).toBeInstanceOf(RpcException);
|
||||
expect(e.error.code).toBe(RpcExceptionCode.ALREADY_EXISTS);
|
||||
}
|
||||
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should throw a dedicated RpcException if phone already exists', async () => {
|
||||
jest.spyOn(mockCommandBus, 'execute');
|
||||
expect.assertions(3);
|
||||
try {
|
||||
await createUserGrpcController.create(createUserRequest);
|
||||
} 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);
|
||||
|
||||
@@ -4,8 +4,8 @@ import {
|
||||
EmailAlreadyExistsException,
|
||||
PhoneAlreadyExistsException,
|
||||
} from '@modules/user/core/domain/user.errors';
|
||||
import { UpdateUserRequestDto } from '@modules/user/interface/dtos/grpc-controllers/dtos/update-user.request.dto';
|
||||
import { UpdateUserGrpcController } from '@modules/user/interface/dtos/grpc-controllers/update-user.grpc.controller';
|
||||
import { UpdateUserRequestDto } from '@modules/user/interface/grpc-controllers/dtos/update-user.request.dto';
|
||||
import { UpdateUserGrpcController } from '@modules/user/interface/grpc-controllers/update-user.grpc.controller';
|
||||
|
||||
import { CommandBus } from '@nestjs/cqrs';
|
||||
import { RpcException } from '@nestjs/microservices';
|
||||
|
||||
@@ -1,60 +1,67 @@
|
||||
import { RedisClientOptions } from '@liaoliaots/nestjs-redis';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { Module, Provider } from '@nestjs/common';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { CqrsModule } from '@nestjs/cqrs';
|
||||
import { redisStore } from 'cache-manager-ioredis-yet';
|
||||
import { DatabaseModule } from '../database/database.module';
|
||||
import { UserController } from './adapters/primaries/user.controller';
|
||||
import { UsersRepository } from './adapters/secondaries/users.repository';
|
||||
import { CreateUserUseCase } from './domain/usecases/create-user.usecase';
|
||||
import { DeleteUserUseCase } from './domain/usecases/delete-user.usecase';
|
||||
import { FindAllUsersUseCase } from './domain/usecases/find-all-users.usecase';
|
||||
import { FindUserByUuidUseCase } from './domain/usecases/find-user-by-uuid.usecase';
|
||||
import { UpdateUserUseCase } from './domain/usecases/update-user.usecase';
|
||||
import { UserProfile } from './mappers/user.profile';
|
||||
import { CacheModule } from '@nestjs/cache-manager';
|
||||
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
|
||||
import {
|
||||
MESSAGE_BROKER_PUBLISHER,
|
||||
MESSAGE_PUBLISHER,
|
||||
} from '../../app.constants';
|
||||
import { MessagePublisher } from './adapters/secondaries/message-publisher';
|
||||
import { CreateUserGrpcController } from './interface/grpc-controllers/create-user.grpc.controller';
|
||||
import { UpdateUserGrpcController } from './interface/grpc-controllers/update-user.grpc.controller';
|
||||
import { CreateUserService } from './core/application/commands/create-user/create-user.service';
|
||||
import { UpdateUserService } from './core/application/commands/update-user/update-user.service';
|
||||
import { USER_MESSAGE_PUBLISHER, USER_REPOSITORY } from './user.di-tokens';
|
||||
import { UserRepository } from './infrastructure/user.repository';
|
||||
import { UserMapper } from './user.mapper';
|
||||
import { PrismaService } from './infrastructure/prisma.service';
|
||||
|
||||
const imports = [
|
||||
CqrsModule,
|
||||
CacheModule.registerAsync<RedisClientOptions>({
|
||||
imports: [ConfigModule],
|
||||
useFactory: async (configService: ConfigService) => ({
|
||||
store: await redisStore({
|
||||
host: configService.get<string>('REDIS_HOST'),
|
||||
port: configService.get<number>('REDIS_PORT'),
|
||||
password: configService.get<string>('REDIS_PASSWORD'),
|
||||
ttl: configService.get('CACHE_TTL'),
|
||||
}),
|
||||
}),
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
];
|
||||
|
||||
const grpcControllers = [CreateUserGrpcController, UpdateUserGrpcController];
|
||||
|
||||
const commandHandlers: Provider[] = [CreateUserService, UpdateUserService];
|
||||
|
||||
const mappers: Provider[] = [UserMapper];
|
||||
|
||||
const repositories: Provider[] = [
|
||||
{
|
||||
provide: USER_REPOSITORY,
|
||||
useClass: UserRepository,
|
||||
},
|
||||
];
|
||||
|
||||
const messagePublishers: Provider[] = [
|
||||
{
|
||||
provide: USER_MESSAGE_PUBLISHER,
|
||||
useExisting: MessageBrokerPublisher,
|
||||
},
|
||||
];
|
||||
|
||||
const orms: Provider[] = [PrismaService];
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
DatabaseModule,
|
||||
CqrsModule,
|
||||
CacheModule.registerAsync<RedisClientOptions>({
|
||||
imports: [ConfigModule],
|
||||
useFactory: async (configService: ConfigService) => ({
|
||||
store: await redisStore({
|
||||
host: configService.get<string>('REDIS_HOST'),
|
||||
port: configService.get<number>('REDIS_PORT'),
|
||||
password: configService.get<string>('REDIS_PASSWORD'),
|
||||
ttl: configService.get('CACHE_TTL'),
|
||||
}),
|
||||
}),
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
],
|
||||
controllers: [UserController],
|
||||
imports,
|
||||
controllers: [...grpcControllers],
|
||||
providers: [
|
||||
UserProfile,
|
||||
UsersRepository,
|
||||
FindAllUsersUseCase,
|
||||
FindUserByUuidUseCase,
|
||||
CreateUserUseCase,
|
||||
UpdateUserUseCase,
|
||||
DeleteUserUseCase,
|
||||
{
|
||||
provide: MESSAGE_BROKER_PUBLISHER,
|
||||
useClass: MessageBrokerPublisher,
|
||||
},
|
||||
{
|
||||
provide: MESSAGE_PUBLISHER,
|
||||
useClass: MessagePublisher,
|
||||
},
|
||||
...commandHandlers,
|
||||
...mappers,
|
||||
...repositories,
|
||||
...messagePublishers,
|
||||
...orms,
|
||||
],
|
||||
exports: [],
|
||||
exports: [PrismaService, UserMapper, USER_REPOSITORY],
|
||||
})
|
||||
export class UserModule {}
|
||||
|
||||
Reference in New Issue
Block a user