This commit is contained in:
David Arranz 2024-05-18 18:51:31 +02:00
parent 923bd92220
commit 1685a44fc2
34 changed files with 673 additions and 298 deletions

View File

@ -8,7 +8,7 @@ import {
import { IRepositoryManager } from "@/contexts/common/domain";
import { IInfrastructureError } from "@/contexts/common/infrastructure";
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
import { Result, ensureUserEmailIsValid } from "@shared/contexts";
import { Result, ensureEmailIsValid } from "@shared/contexts";
import { AuthUser } from "../domain";
import { findUserByEmail } from "./authServices";
@ -46,7 +46,7 @@ export class FindUserByEmailUseCase
// Validaciones de datos
const emailOrError = ensureUserEmailIsValid(email);
const emailOrError = ensureEmailIsValid(email);
if (emailOrError.isFailure) {
return Result.fail(
handleUseCaseError(
@ -57,7 +57,6 @@ export class FindUserByEmailUseCase
);
}
// Crear auth
try {
const user = await findUserByEmail(
emailOrError.object,

View File

@ -8,11 +8,9 @@ export const findUserByEmail = async (
adapter: IAdapter,
repository: RepositoryBuilder<IAuthRepository>,
): Promise<AuthUser | null> => {
const user = await adapter
return await adapter
.startTransaction()
.complete(async (t) =>
repository({ transaction: t }).findUserByEmail(email),
);
return user;
};

View File

@ -46,15 +46,9 @@ export abstract class ExpressController implements IController {
}
public fail(error: IServerError) {
console.group("ExpressController FAIL RESPONSE ====================");
console.log(error);
console.trace("Show me");
console.groupEnd();
console.error("ExpressController FAIL RESPONSE:", error);
return this._errorResponse(
httpStatus.INTERNAL_SERVER_ERROR,
error ? error.toString() : "Fail",
);
return this._errorResponse(httpStatus.INTERNAL_SERVER_ERROR, error.message);
}
public created<T>(dto?: T) {

View File

@ -14,25 +14,12 @@ export const generateExpressErrorResponse = (
message?: string,
error?: Error | InfrastructureError,
): Express.Response<IError_Response_DTO> => {
const context = {};
if (Object.keys(res.locals).length) {
if ("user" in res.locals) {
context["user"] = res.locals.user;
}
}
if (Object.keys(req.params).length) {
context["params"] = req.params;
}
if (Object.keys(req.query).length) {
context["query"] = req.query;
}
if (Object.keys(req.body).length) {
context["body"] = req.body;
}
const context = {
user: res.locals.user || undefined,
params: req.params || undefined,
query: req.query || undefined,
body: req.body || undefined,
};
const extension = new ProblemDocumentExtension({
context,
@ -54,11 +41,11 @@ export const generateExpressErrorResponse = (
function generateExpressError(
error: Error | InfrastructureError,
): IErrorExtra_Response_DTO {
const useCaseError = <UseCaseError>error;
const useCaseError = error as UseCaseError;
const payload = !Array.isArray(useCaseError.payload)
? Array(useCaseError.payload)
: useCaseError.payload;
const payload = Array.isArray(useCaseError.payload)
? useCaseError.payload
: [useCaseError.payload];
const errors = payload.map((item) => {
if (item.path) {

View File

@ -2,54 +2,30 @@ import {
IUseCase,
IUseCaseError,
UseCaseError,
handleInvalidInputDataFailure,
handleResourceAlreadyExitsFailure,
handleUseCaseError,
} from "@/contexts/common/application/useCases";
} from "@/contexts/common/application";
import { IRepositoryManager } from "@/contexts/common/domain";
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
import {
AddressTitle,
City,
Collection,
Country,
DomainError,
ICreateUser_DTO,
Email,
ICreateUser_Request_DTO,
IDomainError,
Note,
PostalCode,
Province,
Name,
Result,
ResultCollection,
Street,
UniqueID,
UserEmail,
UserJobTitle,
UserName,
UserPhone,
UserTIN,
ensureUserIdIsValid,
ensureUserTINIsValid,
ensureIdIsValid,
ensureUserEmailIsValid,
} from "@shared/contexts";
import { IInfrastructureError } from "@/contexts/common/infrastructure";
import {
IBillingAddressUser_DTO,
IShippingAddressUser_DTO,
} from "@shared/contexts/users/application/dto/IUserAddressDTO";
import {
IUserRepository,
User,
UserBillingAddress as UserShippingAddress,
} from "../domain";
import { UserAddressType } from "../domain/entities/UserAddress";
import { IUserRepository, User } from "../domain";
import { existsUserByEmail, existsUserByID } from "./userServices";
export type CreateUserResponseOrError =
| Result<never, IUseCaseError> // Misc errors (value objects)
| Result<User, never>; // Success!
export class CreateUserUseCase
implements IUseCase<ICreateUser_DTO, Promise<CreateUserResponseOrError>>
implements
IUseCase<ICreateUser_Request_DTO, Promise<CreateUserResponseOrError>>
{
private _adapter: ISequelizeAdapter;
private _repositoryManager: IRepositoryManager;
@ -66,43 +42,65 @@ export class CreateUserUseCase
return this._repositoryManager.getRepository<T>(name);
}
async execute(request: ICreateUser_DTO): Promise<CreateUserResponseOrError> {
async execute(
request: ICreateUser_Request_DTO,
): Promise<CreateUserResponseOrError> {
const userDTO = request;
const userRepository = this.getRepositoryByName<IUserRepository>("User");
// Validaciones de datos
const userIdOrError = ensureUserIdIsValid(userDTO.id);
const userIdOrError = ensureIdIsValid(userDTO.id);
if (userIdOrError.isFailure) {
return handleInvalidInputDataFailure(
"User ID is not valid",
userIdOrError.error,
return Result.fail(
handleUseCaseError(
UseCaseError.INVALID_INPUT_DATA,
"User ID is not valid",
userIdOrError.error,
),
);
}
const idExists = await this.findUserID(userIdOrError.object);
const idExists = await existsUserByID(
userIdOrError.object,
this._adapter,
userRepository,
);
if (idExists) {
return handleResourceAlreadyExitsFailure(
`Another user with ID ${userDTO.id} exists`,
userIdOrError.error,
return Result.fail(
handleUseCaseError(
UseCaseError.RESOURCE_ALREADY_EXITS,
`Another user with ID ${userDTO.id} exists`,
userIdOrError.error,
),
);
}
const tinOrError = ensureUserTINIsValid(userDTO.tin);
if (tinOrError.isFailure) {
return handleInvalidInputDataFailure(
`User TIN is not valid: ${tinOrError.error.message}`,
userIdOrError.error,
const emailOrError = ensureUserEmailIsValid(userDTO.email);
if (emailOrError.isFailure) {
return Result.fail(
handleUseCaseError(
UseCaseError.INVALID_INPUT_DATA,
"Email or password is not valid",
emailOrError.error,
),
);
}
const tinExists =
!tinOrError.object.isEmpty() &&
(await this.findUserTIN(tinOrError.object));
const emailExists = await existsUserByEmail(
emailOrError.object,
this._adapter,
userRepository,
);
if (tinExists) {
return handleResourceAlreadyExitsFailure(
`User with TIN ${tinOrError.object.toString()} exists`,
userIdOrError.error,
if (emailExists) {
return Result.fail(
handleUseCaseError(
UseCaseError.RESOURCE_ALREADY_EXITS,
`Another user with email ${userDTO.email} exists`,
userIdOrError.error,
),
);
}
@ -142,10 +140,10 @@ export class CreateUserUseCase
}
}
return this.createUser(userOrError.object);
return this.saveUser(userOrError.object);
}
private async createUser(user: User) {
private async saveUser(user: User) {
// Guardar el contacto
const transaction = this._adapter.startTransaction();
const userRepoBuilder = this.getRepositoryByName<IUserRepository>("User");
@ -170,197 +168,26 @@ export class CreateUserUseCase
}
}
private async findUserID(id: UniqueID) {
const userRepoBuilder = this.getRepositoryByName<IUserRepository>("User");
return await userRepoBuilder().exists(id);
}
private async findUserTIN(tin: UserTIN) {
const userRepoBuilder = this.getRepositoryByName<IUserRepository>("User");
return await userRepoBuilder().existsWithSameTIN(tin);
}
private tryCreateUserInstance(
userDTO: ICreateUser_DTO,
userDTO: ICreateUser_Request_DTO,
userId: UniqueID,
): Result<User, IDomainError> {
const userTINOrError = ensureUserTINIsValid(userDTO.tin);
if (userTINOrError.isFailure) {
return Result.fail(userTINOrError.error);
const nameOrError = Name.create(userDTO.name);
if (nameOrError.isFailure) {
return Result.fail(nameOrError.error);
}
const companyNameOrError = UserName.create(userDTO.company_name);
if (companyNameOrError.isFailure) {
return Result.fail(companyNameOrError.error);
}
const firstNameOrError = UserName.create(userDTO.first_name);
if (firstNameOrError.isFailure) {
return Result.fail(firstNameOrError.error);
}
const lastNameOrError = UserName.create(userDTO.last_name);
if (lastNameOrError.isFailure) {
return Result.fail(lastNameOrError.error);
}
const jobTitleOrError = UserJobTitle.create(userDTO.job_title);
if (jobTitleOrError.isFailure) {
return Result.fail(jobTitleOrError.error);
}
const emailOrError = UserEmail.create(userDTO.email);
const emailOrError = Email.create(userDTO.email);
if (emailOrError.isFailure) {
return Result.fail(emailOrError.error);
}
const phoneOrError = UserPhone.create(userDTO.phone);
if (phoneOrError.isFailure) {
return Result.fail(phoneOrError.error);
}
/*const taxIdOrError = UniqueID.create(userDTO.tax_id);
if (taxIdOrError.isFailure) {
return Result.fail(taxIdOrError.error);
}*/
const notesOrError = Note.create(userDTO.notes);
if (notesOrError.isFailure) {
return Result.fail(notesOrError.error);
}
// Billing address
const billingAddressOrError = this.tryCreateUserAddress(
userDTO.billing_address,
"billing",
);
if (billingAddressOrError.isFailure) {
return Result.fail(billingAddressOrError.error);
}
// Shipping address
const shippingAddressesOrError = this.tryCreateUserShippingAddresses(
userDTO.shipping_addresses,
);
if (shippingAddressesOrError.isFailure) {
return Result.fail(shippingAddressesOrError.error);
}
return User.create(
{
tin: userTINOrError.object,
companyName: companyNameOrError.object,
firstName: firstNameOrError.object,
lastName: lastNameOrError.object,
jobTitle: jobTitleOrError.object,
name: nameOrError.object,
email: emailOrError.object,
phone: phoneOrError.object,
//taxId: taxIdOrError.object,
notes: notesOrError.object,
billingAddress: billingAddressOrError.object,
shippingAddresses: shippingAddressesOrError.object,
},
userId,
);
}
private tryCreateUserShippingAddresses(
addressesDTO: IShippingAddressUser_DTO[] | undefined,
) {
const shippingAddressesOrError = new ResultCollection<
UserShippingAddress,
DomainError
>();
if (addressesDTO) {
addressesDTO.map((value, index) => {
const result = this.tryCreateUserAddress(value, "shipping");
if (result.error) {
const { path } = result.error.payload;
const newPath = `shipping_addresses.${index}.${path}`;
result.error.payload.path = newPath;
}
shippingAddressesOrError.add(result);
});
}
if (shippingAddressesOrError.hasSomeFaultyResult()) {
return Result.fail(shippingAddressesOrError.getFirstFaultyResult().error);
}
return Result.ok(new Collection(shippingAddressesOrError.objects));
}
private tryCreateUserAddress(
addressDTO: IBillingAddressUser_DTO | IShippingAddressUser_DTO | undefined,
addressType: UserAddressType,
) {
const titleOrError = AddressTitle.create(addressDTO?.title);
if (titleOrError.isFailure) {
return Result.fail(titleOrError.error);
}
const streetOrError = Street.create(addressDTO?.street);
if (streetOrError.isFailure) {
return Result.fail(streetOrError.error);
}
const postalCodeOrError = PostalCode.create(addressDTO?.postal_code);
if (postalCodeOrError.isFailure) {
return Result.fail(postalCodeOrError.error);
}
const cityOrError = City.create(addressDTO?.city);
if (cityOrError.isFailure) {
return Result.fail(cityOrError.error);
}
const provinceOrError = Province.create(addressDTO?.province);
if (provinceOrError.isFailure) {
return Result.fail(provinceOrError.error);
}
const countryOrError = Country.create(addressDTO?.country);
if (countryOrError.isFailure) {
return Result.fail(countryOrError.error);
}
const emailOrError = UserEmail.create(addressDTO?.email, {
label: "email",
path: "email",
});
if (emailOrError.isFailure) {
return Result.fail(emailOrError.error);
}
const phoneOrError = UserPhone.create(addressDTO?.phone);
if (phoneOrError.isFailure) {
return Result.fail(phoneOrError.error);
}
const notesOrError = Note.create(addressDTO?.notes);
if (notesOrError.isFailure) {
return Result.fail(notesOrError.error);
}
const addressProps = {
title: titleOrError.object,
street: streetOrError.object,
city: cityOrError.object,
province: provinceOrError.object,
postalCode: postalCodeOrError.object,
country: countryOrError.object,
email: emailOrError.object,
phone: phoneOrError.object,
notes: notesOrError.object,
};
return addressType === "billing"
? UserShippingAddress.create(addressProps)
: UserShippingAddress.create(addressProps);
}
}

View File

@ -1,5 +1,5 @@
export * from "./CreateUser.useCase";
export * from "./DeleteUser.useCase";
export * from "./GetUser.useCase";
export * from "./ListUsers.useCase";
//export * from "./CreateUser.useCase";
//export * from "./UpdateUser.useCase";
export * from "./UpdateUser.useCase";

View File

@ -0,0 +1,47 @@
import { IAdapter, RepositoryBuilder } from "@/contexts/common/domain";
import { Email, UniqueID } from "@shared/contexts";
import { IUserRepository, User } from "../domain";
export const existsUserByID = async (
id: UniqueID,
adapter: IAdapter,
repository: RepositoryBuilder<IUserRepository>,
): Promise<boolean> => {
return await adapter
.startTransaction()
.complete(async (t) => repository({ transaction: t }).existsUserWithId(id));
};
export const findUserByID = async (
id: UniqueID,
adapter: IAdapter,
repository: RepositoryBuilder<IUserRepository>,
): Promise<User | null> => {
return await adapter
.startTransaction()
.complete(async (t) => repository({ transaction: t }).getById(id));
};
export const existsUserByEmail = async (
email: Email,
adapter: IAdapter,
repository: RepositoryBuilder<IUserRepository>,
): Promise<boolean> => {
return await adapter
.startTransaction()
.complete(async (t) =>
repository({ transaction: t }).existsUserWithEmail(email),
);
};
export const findUserByEmail = async (
email: Email,
adapter: IAdapter,
repository: RepositoryBuilder<IUserRepository>,
): Promise<User | null> => {
return await adapter
.startTransaction()
.complete(async (t) =>
repository({ transaction: t }).findUserByEmail(email),
);
};

View File

@ -1,12 +1,14 @@
import { IRepository } from "@/contexts/common/domain";
import { ICollection, IQueryCriteria, UniqueID } from "@shared/contexts";
import { Email, ICollection, IQueryCriteria, UniqueID } from "@shared/contexts";
import { User } from "../entities";
export interface IUserRepository extends IRepository<any> {
getById(id: UniqueID): Promise<User | null>;
findUserByEmail(email: Email): Promise<User | null>;
findAll(queryCriteria?: IQueryCriteria): Promise<ICollection<User>>;
removeById(id: UniqueID): Promise<void>;
exists(id: UniqueID): Promise<boolean>;
existsUserWithId(id: UniqueID): Promise<boolean>;
existsUserWithEmail(email: Email): Promise<boolean>;
}

View File

@ -72,9 +72,13 @@ export class UserRepository
return this._removeById("User_Model", id);
}
public async exists(id: UniqueID): Promise<boolean> {
public async existsUserWithId(id: UniqueID): Promise<boolean> {
return this._exists("User_Model", "id", id.toString());
}
public async existsUserWithEmail(email: Email): Promise<boolean> {
return this._exists("User_Model", "email", email.toString());
}
}
export const registerUserRepository = (context: IUserContext) => {

View File

@ -0,0 +1,120 @@
import { IUseCaseError, UseCaseError } from "@/contexts/common/application";
import { IServerError } from "@/contexts/common/domain/errors";
import {
IInfrastructureError,
InfrastructureError,
handleInfrastructureError,
} from "@/contexts/common/infrastructure";
import { ExpressController } from "@/contexts/common/infrastructure/express";
import {
CreateUserResponseOrError,
CreateUserUseCase,
} from "@/contexts/users/application";
import { User } from "@/contexts/users/domain";
import {
ICreateUser_Request_DTO,
ICreateUser_Response_DTO,
ensureCreateUser_Request_DTOIsValid,
} from "@shared/contexts";
import { IUserContext } from "../../../User.context";
import { ICreateUserPresenter } from "./presenter/CreateUser.presenter";
export class CreateUserController extends ExpressController {
private useCase: CreateUserUseCase;
private presenter: ICreateUserPresenter;
private context: IUserContext;
constructor(
props: {
useCase: CreateUserUseCase;
presenter: ICreateUserPresenter;
},
context: IUserContext,
) {
super();
const { useCase, presenter } = props;
this.useCase = useCase;
this.presenter = presenter;
this.context = context;
}
async executeImpl(): Promise<any> {
try {
const userDTO: ICreateUser_Request_DTO = this.req.body;
// Validaciones de DTO
const userDTOOrError = ensureCreateUser_Request_DTOIsValid(userDTO);
if (userDTOOrError.isFailure) {
const errorMessage = "User data not valid";
const infraError = handleInfrastructureError(
InfrastructureError.INVALID_INPUT_DATA,
errorMessage,
userDTOOrError.error,
);
return this.invalidInputError(errorMessage, infraError);
}
// Llamar al caso de uso
const result: CreateUserResponseOrError = await this.useCase.execute(
userDTO,
);
if (result.isFailure) {
return this._handleExecuteError(result.error);
}
const user = <User>result.object;
return this.created<ICreateUser_Response_DTO>(
this.presenter.map(user, this.context),
);
} catch (e: unknown) {
return this.fail(e as IServerError);
}
}
private _handleExecuteError(error: IUseCaseError) {
let errorMessage: string;
let infraError: IInfrastructureError;
switch (error.code) {
case UseCaseError.INVALID_INPUT_DATA:
errorMessage = "User data not valid";
infraError = handleInfrastructureError(
InfrastructureError.INVALID_INPUT_DATA,
errorMessage,
error,
);
return this.invalidInputError(errorMessage, infraError);
break;
case UseCaseError.RESOURCE_ALREADY_EXITS:
errorMessage = "User already exists";
infraError = handleInfrastructureError(
InfrastructureError.INVALID_INPUT_DATA,
errorMessage,
error,
);
return this.conflictError(error.message, error);
break;
case UseCaseError.UNEXCEPTED_ERROR:
errorMessage = error.message;
infraError = handleInfrastructureError(
InfrastructureError.UNEXCEPTED_ERROR,
errorMessage,
error,
);
return this.internalServerError(errorMessage, infraError);
break;
default:
errorMessage = error.message;
return this.clientError(errorMessage);
}
}
}

View File

@ -0,0 +1,17 @@
import { CreateUserUseCase } from "@/contexts/users/application";
import { IUserContext } from "../../../User.context";
import { registerUserRepository } from "../../../User.repository";
import { CreateUserController } from "./CreateUser.controller";
import { CreateUserPresenter } from "./presenter/CreateUser.presenter";
export const createCreateUserController = (context: IUserContext) => {
registerUserRepository(context);
return new CreateUserController(
{
useCase: new CreateUserUseCase(context),
presenter: CreateUserPresenter,
},
context,
);
};

View File

@ -0,0 +1,17 @@
import { User } from "@/contexts/users/domain";
import { IUserContext } from "@/contexts/users/infrastructure/User.context";
import { ICreateUser_Response_DTO } from "@shared/contexts";
export interface ICreateUserPresenter {
map: (user: User, context: IUserContext) => ICreateUser_Response_DTO;
}
export const CreateUserPresenter: ICreateUserPresenter = {
map: (user: User, context: IUserContext): ICreateUser_Response_DTO => {
return {
id: user.id.toString(),
name: user.name.toString(),
email: user.email.toString(),
};
},
};

View File

@ -0,0 +1 @@
export * from "./CreateUser.presenter";

View File

@ -0,0 +1,91 @@
import {
IUseCaseError,
UseCaseError,
} from "@/contexts/common/application/useCases";
import { IServerError } from "@/contexts/common/domain/errors";
import {
IInfrastructureError,
InfrastructureError,
handleInfrastructureError,
} from "@/contexts/common/infrastructure";
import { ExpressController } from "@/contexts/common/infrastructure/express";
import { DeleteUserUseCase } from "@/contexts/users/application";
import { ensureIdIsValid } from "@shared/contexts";
import { IUserContext } from "../../../User.context";
export class DeleteUserController extends ExpressController {
private useCase: DeleteUserUseCase;
private context: IUserContext;
constructor(props: { useCase: DeleteUserUseCase }, context: IUserContext) {
super();
const { useCase } = props;
this.useCase = useCase;
this.context = context;
}
async executeImpl(): Promise<any> {
try {
const { userId } = this.req.params;
// Validar ID
const userIdOrError = ensureIdIsValid(userId);
if (userIdOrError.isFailure) {
const errorMessage = "User ID is not valid";
const infraError = handleInfrastructureError(
InfrastructureError.INVALID_INPUT_DATA,
errorMessage,
userIdOrError.error,
);
return this.invalidInputError(errorMessage, infraError);
}
// Llamar al caso de uso
const result = await this.useCase.execute({
id: userIdOrError.object,
});
if (result.isFailure) {
return this._handleExecuteError(result.error);
}
return this.noContent();
} catch (e: unknown) {
return this.fail(e as IServerError);
}
}
private _handleExecuteError(error: IUseCaseError) {
let errorMessage: string;
let infraError: IInfrastructureError;
switch (error.code) {
case UseCaseError.NOT_FOUND_ERROR:
errorMessage = "User not found";
infraError = handleInfrastructureError(
InfrastructureError.RESOURCE_NOT_FOUND_ERROR,
errorMessage,
error,
);
return this.notFoundError(errorMessage, infraError);
break;
case UseCaseError.UNEXCEPTED_ERROR:
errorMessage = error.message;
infraError = handleInfrastructureError(
InfrastructureError.UNEXCEPTED_ERROR,
errorMessage,
error,
);
return this.internalServerError(errorMessage, infraError);
break;
default:
errorMessage = error.message;
return this.clientError(errorMessage);
}
}
}

View File

@ -0,0 +1,14 @@
import { DeleteUserUseCase } from "@/contexts/users/application";
import { IUserContext } from "../../../User.context";
import { registerUserRepository } from "../../../User.repository";
import { DeleteUserController } from "./DeleteUser.controller";
export const createDeleteUserController = (context: IUserContext) => {
registerUserRepository(context);
return new DeleteUserController(
{
useCase: new DeleteUserUseCase(context),
},
context,
);
};

View File

@ -5,7 +5,7 @@ import {
import { ExpressController } from "@/contexts/common/infrastructure/express";
import { GetUserUseCase } from "@/contexts/users/application";
import { User } from "@/contexts/users/domain/entities/User";
import { IGetUser_Response_DTO, ensureIdIsValid } from "@shared/contexts";
import { IGetUserResponse_DTO, ensureIdIsValid } from "@shared/contexts";
import { IServerError } from "@/contexts/common/domain/errors";
import {
@ -62,7 +62,7 @@ export class GetUserController extends ExpressController {
const user = <User>result.object;
return this.ok<IGetUser_Response_DTO>(
return this.ok<IGetUserResponse_DTO>(
this.presenter.map(user, this.context),
);
} catch (e: unknown) {

View File

@ -1,13 +1,13 @@
import { IGetUser_Response_DTO } from "@shared/contexts";
import { IGetUserResponse_DTO } from "@shared/contexts";
import { User } from "../../../../../domain";
import { IUserContext } from "../../../../User.context";
export interface IGetUserPresenter {
map: (user: User, context: IUserContext) => IGetUser_Response_DTO;
map: (user: User, context: IUserContext) => IGetUserResponse_DTO;
}
export const GetUserPresenter: IGetUserPresenter = {
map: (user: User, context: IUserContext): IGetUser_Response_DTO => {
map: (user: User, context: IUserContext): IGetUserResponse_DTO => {
return {
id: user.id.toString(),
name: user.name.toString(),

View File

@ -1,2 +1,5 @@
export * from "./createUser";
export * from "./deleteUser";
export * from "./getUser";
export * from "./listUsers";
export * from "./updateUser";

View File

@ -0,0 +1,137 @@
import { IUseCaseError, UseCaseError } from "@/contexts/common/application";
import { IServerError } from "@/contexts/common/domain/errors";
import {
IInfrastructureError,
InfrastructureError,
handleInfrastructureError,
} from "@/contexts/common/infrastructure";
import { ExpressController } from "@/contexts/common/infrastructure/express";
import {
UpdateUserResponseOrError,
UpdateUserUseCase,
} from "@/contexts/users/application";
import { User } from "@/contexts/users/domain";
import {
IUpdateUser_Request_DTO,
IUpdateUser_Response_DTO,
ensureIdIsValid,
ensureUpdateUser_Request_DTOIsValid,
} from "@shared/contexts";
import { IUserContext } from "../../../User.context";
import { IUpdateUserPresenter } from "./presenter/UpdateUser.presenter";
export class UpdateUserController extends ExpressController {
private useCase: UpdateUserUseCase;
private presenter: IUpdateUserPresenter;
private context: IUserContext;
constructor(
props: {
useCase: UpdateUserUseCase;
presenter: IUpdateUserPresenter;
},
context: IUserContext,
) {
super();
const { useCase, presenter } = props;
this.useCase = useCase;
this.presenter = presenter;
this.context = context;
}
async executeImpl(): Promise<any> {
try {
const { userId } = this.req.params;
const userDTO: IUpdateUser_Request_DTO = this.req.body;
// Validar ID
const userIdOrError = ensureIdIsValid(userId);
if (userIdOrError.isFailure) {
const errorMessage = "User ID is not valid";
const infraError = handleInfrastructureError(
InfrastructureError.INVALID_INPUT_DATA,
errorMessage,
userIdOrError.error,
);
return this.invalidInputError(errorMessage, infraError);
}
// Validar DTO de datos
const userDTOOrError = ensureUpdateUser_Request_DTOIsValid(userDTO);
if (userDTOOrError.isFailure) {
const errorMessage = "User data not valid";
const infraError = handleInfrastructureError(
InfrastructureError.INVALID_INPUT_DATA,
errorMessage,
userDTOOrError.error,
);
return this.invalidInputError(errorMessage, infraError);
}
// Llamar al caso de uso
const result: UpdateUserResponseOrError = await this.useCase.execute({
id: userIdOrError.object,
userDTO,
});
if (result.isFailure) {
return this.handleExecuteError(result.error);
}
const user = <User>result.object;
return this.ok<IUpdateUser_Response_DTO>(
this.presenter.map(user, this.context),
);
} catch (e: unknown) {
return this.fail(e as IServerError);
}
}
private handleExecuteError(error: IUseCaseError) {
let errorMessage: string;
let infraError: IInfrastructureError;
switch (error.code) {
case UseCaseError.NOT_FOUND_ERROR:
errorMessage = "User not found";
infraError = handleInfrastructureError(
InfrastructureError.RESOURCE_NOT_FOUND_ERROR,
errorMessage,
error,
);
return this.notFoundError(errorMessage, infraError);
break;
case UseCaseError.INVALID_INPUT_DATA:
errorMessage = "User data not valid";
infraError = handleInfrastructureError(
InfrastructureError.INVALID_INPUT_DATA,
"Datos del cliente a actulizar erróneos",
error,
);
return this.invalidInputError(errorMessage, infraError);
break;
case UseCaseError.UNEXCEPTED_ERROR:
errorMessage = error.message;
infraError = handleInfrastructureError(
InfrastructureError.UNEXCEPTED_ERROR,
errorMessage,
error,
);
return this.internalServerError(errorMessage, infraError);
break;
default:
errorMessage = error.message;
return this.clientError(errorMessage);
}
}
}

View File

@ -0,0 +1,16 @@
import { UpdateUserUseCase } from "@/contexts/users/application";
import { IUserContext } from "../../../User.context";
import { registerUserRepository } from "../../../User.repository";
import { UpdateUserController } from "./UpdateUser.controller";
import { UpdateUserPresenter } from "./presenter/UpdateUser.presenter";
export const createUpdateUserController = (context: IUserContext) => {
registerUserRepository(context);
return new UpdateUserController(
{
useCase: new UpdateUserUseCase(context),
presenter: UpdateUserPresenter,
},
context,
);
};

View File

@ -0,0 +1,17 @@
import { User } from "@/contexts/users/domain";
import { IUserContext } from "@/contexts/users/infrastructure/User.context";
import { IUpdateUser_Response_DTO } from "@shared/contexts";
export interface IUpdateUserPresenter {
map: (user: User, context: IUserContext) => IUpdateUser_Response_DTO;
}
export const UpdateUserPresenter: IUpdateUserPresenter = {
map: (user: User, context: IUserContext): IUpdateUser_Response_DTO => {
return {
id: user.id.toString(),
name: user.name.toString(),
email: user.email.toString(),
};
},
};

View File

@ -0,0 +1 @@
export * from "./UpdateUser.presenter";

View File

@ -1,8 +1,11 @@
import { applyMiddleware } from "@/contexts/common/infrastructure/express";
import Express from "express";
import {
createCreateUserController,
createDeleteUserController,
createGetUserController,
createListUsersController,
createUpdateUserController,
} from "./controllers";
export const UserRouter = (appRouter: Express.Router) => {
@ -22,5 +25,26 @@ export const UserRouter = (appRouter: Express.Router) => {
createGetUserController(res.locals["context"]).execute(req, res, next),
);
userRoutes.post(
"/",
applyMiddleware("isAdminUser"),
(req: Express.Request, res: Express.Response, next: Express.NextFunction) =>
createCreateUserController(res.locals["context"]).execute(req, res, next),
);
userRoutes.put(
"/:userId",
applyMiddleware("isAdminUser"),
(req: Express.Request, res: Express.Response, next: Express.NextFunction) =>
createUpdateUserController(res.locals["context"]).execute(req, res, next),
);
userRoutes.delete(
"/:userId",
applyMiddleware("isAdminUser"),
(req: Express.Request, res: Express.Response, next: Express.NextFunction) =>
createDeleteUserController(res.locals["context"]).execute(req, res, next),
);
appRouter.use("/users", userRoutes);
};

View File

@ -1,10 +1 @@
import { UndefinedOr } from "../../../utilities";
import { Email, Result } from "../../common";
export const ensureUserEmailIsValid = (value: UndefinedOr<string>) => {
const valueOrError = Email.create(value);
return valueOrError.isSuccess
? Result.ok(valueOrError.object)
: Result.fail(valueOrError.error);
};
export {};

View File

@ -0,0 +1,29 @@
import Joi from "joi";
import { Result, RuleValidator } from "../../../../common";
export interface ICreateUser_Request_DTO {
id: string;
name: string;
email: string;
}
export function ensureCreateUser_Request_DTOIsValid(
userDTO: ICreateUser_Request_DTO,
): Result<boolean, Error> {
const schema = Joi.object({
id: Joi.string(),
name: Joi.string(),
email: Joi.string(),
}).unknown(true);
const result = RuleValidator.validate<ICreateUser_Request_DTO>(
schema,
userDTO,
);
if (result.isFailure) {
return Result.fail(result.error);
}
return Result.ok(true);
}

View File

@ -0,0 +1,5 @@
export interface ICreateUser_Response_DTO {
id: string;
name: string;
email: string;
}

View File

@ -0,0 +1,2 @@
export * from "./ICreateUser_Request.dto";
export * from "./ICreateUser_Response.dto";

View File

@ -1,5 +0,0 @@
import { UniqueID } from "../../../../common";
export interface IGetUserRequest_DTO {
id: UniqueID;
}

View File

@ -1,4 +1,4 @@
export interface IGetUser_Response_DTO {
export interface IGetUserResponse_DTO {
id: string;
name: string;
email: string;

View File

@ -1,2 +1 @@
export * from "./IGetUser.dto";
export * from "./IGetUser_Response.dto";

View File

@ -0,0 +1,29 @@
import Joi from "joi";
import { Result, RuleValidator } from "../../../../common";
export interface IUpdateUser_Request_DTO {
id: string;
name: string;
email: string;
}
export function ensureUpdateUser_Request_DTOIsValid(
userDTO: IUpdateUser_Request_DTO,
): Result<boolean, Error> {
const schema = Joi.object({
id: Joi.string(),
name: Joi.string(),
email: Joi.string(),
}).unknown(true);
const result = RuleValidator.validate<IUpdateUser_Request_DTO>(
schema,
userDTO,
);
if (result.isFailure) {
return Result.fail(result.error);
}
return Result.ok(true);
}

View File

@ -0,0 +1,5 @@
export interface IUpdateUser_Response_DTO {
id: string;
name: string;
email: string;
}

View File

@ -0,0 +1,2 @@
export * from "./IUpdateUser_Request.dto";
export * from "./IUpdateUser_Response.dto";

View File

@ -1,2 +1,4 @@
export * from "./CreateUser.dto";
export * from "./GetUser.dto";
export * from "./IListUsers.dto";
export * from "./UpdateUser.dto";