From 1685a44fc2b018a274fbe9b177f25432d4c5bd70 Mon Sep 17 00:00:00 2001 From: David Arranz Date: Sat, 18 May 2024 18:51:31 +0200 Subject: [PATCH] . --- .../application/FindUserByEmail.useCase.ts | 5 +- .../contexts/auth/application/authServices.ts | 4 +- .../express/ExpressController.ts | 10 +- .../express/ExpressErrorResponse.ts | 33 +- .../users/application/CreateUser.useCase.ts | 295 ++++-------------- .../src/contexts/users/application/index.ts | 4 +- .../users/application/userServices.ts | 47 +++ .../repository/UserRepository.interface.ts | 6 +- .../users/infrastructure/User.repository.ts | 6 +- .../createUser/CreateUser.controller.ts | 120 +++++++ .../express/controllers/createUser/index.ts | 17 + .../presenter/CreateUser.presenter.ts | 17 + .../controllers/createUser/presenter/index.ts | 1 + .../deleteUser/DeleteUser.controller.ts | 91 ++++++ .../express/controllers/deleteUser/index.ts | 14 + .../controllers/getUser/GetUser.controller.ts | 4 +- .../getUser/presenter/GetUser.presenter.ts | 6 +- .../express/controllers/index.ts | 3 + .../updateUser/UpdateUser.controller.ts | 137 ++++++++ .../express/controllers/updateUser/index.ts | 16 + .../presenter/UpdateUser.presenter.ts | 17 + .../controllers/updateUser/presenter/index.ts | 1 + .../users/infrastructure/express/routes.ts | 24 ++ .../contexts/auth/application/User.service.ts | 11 +- .../CreateUser.dto/ICreateUser_Request.dto.ts | 29 ++ .../ICreateUser_Response.dto.ts | 5 + .../application/dto/CreateUser.dto/index.ts | 2 + .../dto/GetUser.dto/IGetUser.dto.ts | 5 - .../dto/GetUser.dto/IGetUser_Response.dto.ts | 2 +- .../application/dto/GetUser.dto/index.ts | 1 - .../UpdateUser.dto/IUpdateUser_Request.dto.ts | 29 ++ .../IUpdateUser_Response.dto.ts | 5 + .../application/dto/UpdateUser.dto/index.ts | 2 + .../contexts/users/application/dto/index.ts | 2 + 34 files changed, 673 insertions(+), 298 deletions(-) create mode 100644 server/src/contexts/users/application/userServices.ts create mode 100644 server/src/contexts/users/infrastructure/express/controllers/createUser/CreateUser.controller.ts create mode 100644 server/src/contexts/users/infrastructure/express/controllers/createUser/index.ts create mode 100644 server/src/contexts/users/infrastructure/express/controllers/createUser/presenter/CreateUser.presenter.ts create mode 100644 server/src/contexts/users/infrastructure/express/controllers/createUser/presenter/index.ts create mode 100644 server/src/contexts/users/infrastructure/express/controllers/deleteUser/DeleteUser.controller.ts create mode 100644 server/src/contexts/users/infrastructure/express/controllers/deleteUser/index.ts create mode 100644 server/src/contexts/users/infrastructure/express/controllers/updateUser/UpdateUser.controller.ts create mode 100644 server/src/contexts/users/infrastructure/express/controllers/updateUser/index.ts create mode 100644 server/src/contexts/users/infrastructure/express/controllers/updateUser/presenter/UpdateUser.presenter.ts create mode 100644 server/src/contexts/users/infrastructure/express/controllers/updateUser/presenter/index.ts create mode 100644 shared/lib/contexts/users/application/dto/CreateUser.dto/ICreateUser_Request.dto.ts create mode 100644 shared/lib/contexts/users/application/dto/CreateUser.dto/ICreateUser_Response.dto.ts create mode 100644 shared/lib/contexts/users/application/dto/CreateUser.dto/index.ts delete mode 100644 shared/lib/contexts/users/application/dto/GetUser.dto/IGetUser.dto.ts create mode 100644 shared/lib/contexts/users/application/dto/UpdateUser.dto/IUpdateUser_Request.dto.ts create mode 100644 shared/lib/contexts/users/application/dto/UpdateUser.dto/IUpdateUser_Response.dto.ts create mode 100644 shared/lib/contexts/users/application/dto/UpdateUser.dto/index.ts diff --git a/server/src/contexts/auth/application/FindUserByEmail.useCase.ts b/server/src/contexts/auth/application/FindUserByEmail.useCase.ts index 5bc7e95..b60dcc6 100644 --- a/server/src/contexts/auth/application/FindUserByEmail.useCase.ts +++ b/server/src/contexts/auth/application/FindUserByEmail.useCase.ts @@ -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, diff --git a/server/src/contexts/auth/application/authServices.ts b/server/src/contexts/auth/application/authServices.ts index 18c9487..be25c8e 100644 --- a/server/src/contexts/auth/application/authServices.ts +++ b/server/src/contexts/auth/application/authServices.ts @@ -8,11 +8,9 @@ export const findUserByEmail = async ( adapter: IAdapter, repository: RepositoryBuilder, ): Promise => { - const user = await adapter + return await adapter .startTransaction() .complete(async (t) => repository({ transaction: t }).findUserByEmail(email), ); - - return user; }; diff --git a/server/src/contexts/common/infrastructure/express/ExpressController.ts b/server/src/contexts/common/infrastructure/express/ExpressController.ts index 7432681..f0a034a 100644 --- a/server/src/contexts/common/infrastructure/express/ExpressController.ts +++ b/server/src/contexts/common/infrastructure/express/ExpressController.ts @@ -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(dto?: T) { diff --git a/server/src/contexts/common/infrastructure/express/ExpressErrorResponse.ts b/server/src/contexts/common/infrastructure/express/ExpressErrorResponse.ts index e4984e3..686f562 100644 --- a/server/src/contexts/common/infrastructure/express/ExpressErrorResponse.ts +++ b/server/src/contexts/common/infrastructure/express/ExpressErrorResponse.ts @@ -14,25 +14,12 @@ export const generateExpressErrorResponse = ( message?: string, error?: Error | InfrastructureError, ): Express.Response => { - 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 = 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) { diff --git a/server/src/contexts/users/application/CreateUser.useCase.ts b/server/src/contexts/users/application/CreateUser.useCase.ts index 5731409..ca222b9 100644 --- a/server/src/contexts/users/application/CreateUser.useCase.ts +++ b/server/src/contexts/users/application/CreateUser.useCase.ts @@ -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 // Misc errors (value objects) | Result; // Success! export class CreateUserUseCase - implements IUseCase> + implements + IUseCase> { private _adapter: ISequelizeAdapter; private _repositoryManager: IRepositoryManager; @@ -66,43 +42,65 @@ export class CreateUserUseCase return this._repositoryManager.getRepository(name); } - async execute(request: ICreateUser_DTO): Promise { + async execute( + request: ICreateUser_Request_DTO, + ): Promise { const userDTO = request; + const userRepository = this.getRepositoryByName("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("User"); @@ -170,197 +168,26 @@ export class CreateUserUseCase } } - private async findUserID(id: UniqueID) { - const userRepoBuilder = this.getRepositoryByName("User"); - return await userRepoBuilder().exists(id); - } - - private async findUserTIN(tin: UserTIN) { - const userRepoBuilder = this.getRepositoryByName("User"); - return await userRepoBuilder().existsWithSameTIN(tin); - } - private tryCreateUserInstance( - userDTO: ICreateUser_DTO, + userDTO: ICreateUser_Request_DTO, userId: UniqueID, ): Result { - 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); - } } diff --git a/server/src/contexts/users/application/index.ts b/server/src/contexts/users/application/index.ts index c44cdae..b9dfd91 100644 --- a/server/src/contexts/users/application/index.ts +++ b/server/src/contexts/users/application/index.ts @@ -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"; diff --git a/server/src/contexts/users/application/userServices.ts b/server/src/contexts/users/application/userServices.ts new file mode 100644 index 0000000..12940b3 --- /dev/null +++ b/server/src/contexts/users/application/userServices.ts @@ -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, +): Promise => { + return await adapter + .startTransaction() + .complete(async (t) => repository({ transaction: t }).existsUserWithId(id)); +}; + +export const findUserByID = async ( + id: UniqueID, + adapter: IAdapter, + repository: RepositoryBuilder, +): Promise => { + return await adapter + .startTransaction() + .complete(async (t) => repository({ transaction: t }).getById(id)); +}; + +export const existsUserByEmail = async ( + email: Email, + adapter: IAdapter, + repository: RepositoryBuilder, +): Promise => { + return await adapter + .startTransaction() + .complete(async (t) => + repository({ transaction: t }).existsUserWithEmail(email), + ); +}; + +export const findUserByEmail = async ( + email: Email, + adapter: IAdapter, + repository: RepositoryBuilder, +): Promise => { + return await adapter + .startTransaction() + .complete(async (t) => + repository({ transaction: t }).findUserByEmail(email), + ); +}; diff --git a/server/src/contexts/users/domain/repository/UserRepository.interface.ts b/server/src/contexts/users/domain/repository/UserRepository.interface.ts index 76309cb..f037038 100644 --- a/server/src/contexts/users/domain/repository/UserRepository.interface.ts +++ b/server/src/contexts/users/domain/repository/UserRepository.interface.ts @@ -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 { getById(id: UniqueID): Promise; + findUserByEmail(email: Email): Promise; findAll(queryCriteria?: IQueryCriteria): Promise>; removeById(id: UniqueID): Promise; - exists(id: UniqueID): Promise; + existsUserWithId(id: UniqueID): Promise; + existsUserWithEmail(email: Email): Promise; } diff --git a/server/src/contexts/users/infrastructure/User.repository.ts b/server/src/contexts/users/infrastructure/User.repository.ts index 45ff810..ee2a644 100644 --- a/server/src/contexts/users/infrastructure/User.repository.ts +++ b/server/src/contexts/users/infrastructure/User.repository.ts @@ -72,9 +72,13 @@ export class UserRepository return this._removeById("User_Model", id); } - public async exists(id: UniqueID): Promise { + public async existsUserWithId(id: UniqueID): Promise { return this._exists("User_Model", "id", id.toString()); } + + public async existsUserWithEmail(email: Email): Promise { + return this._exists("User_Model", "email", email.toString()); + } } export const registerUserRepository = (context: IUserContext) => { diff --git a/server/src/contexts/users/infrastructure/express/controllers/createUser/CreateUser.controller.ts b/server/src/contexts/users/infrastructure/express/controllers/createUser/CreateUser.controller.ts new file mode 100644 index 0000000..04f8f45 --- /dev/null +++ b/server/src/contexts/users/infrastructure/express/controllers/createUser/CreateUser.controller.ts @@ -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 { + 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 = result.object; + + return this.created( + 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); + } + } +} diff --git a/server/src/contexts/users/infrastructure/express/controllers/createUser/index.ts b/server/src/contexts/users/infrastructure/express/controllers/createUser/index.ts new file mode 100644 index 0000000..c88b41f --- /dev/null +++ b/server/src/contexts/users/infrastructure/express/controllers/createUser/index.ts @@ -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, + ); +}; diff --git a/server/src/contexts/users/infrastructure/express/controllers/createUser/presenter/CreateUser.presenter.ts b/server/src/contexts/users/infrastructure/express/controllers/createUser/presenter/CreateUser.presenter.ts new file mode 100644 index 0000000..9d1d124 --- /dev/null +++ b/server/src/contexts/users/infrastructure/express/controllers/createUser/presenter/CreateUser.presenter.ts @@ -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(), + }; + }, +}; diff --git a/server/src/contexts/users/infrastructure/express/controllers/createUser/presenter/index.ts b/server/src/contexts/users/infrastructure/express/controllers/createUser/presenter/index.ts new file mode 100644 index 0000000..3cf7762 --- /dev/null +++ b/server/src/contexts/users/infrastructure/express/controllers/createUser/presenter/index.ts @@ -0,0 +1 @@ +export * from "./CreateUser.presenter"; diff --git a/server/src/contexts/users/infrastructure/express/controllers/deleteUser/DeleteUser.controller.ts b/server/src/contexts/users/infrastructure/express/controllers/deleteUser/DeleteUser.controller.ts new file mode 100644 index 0000000..9ed7149 --- /dev/null +++ b/server/src/contexts/users/infrastructure/express/controllers/deleteUser/DeleteUser.controller.ts @@ -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 { + 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); + } + } +} diff --git a/server/src/contexts/users/infrastructure/express/controllers/deleteUser/index.ts b/server/src/contexts/users/infrastructure/express/controllers/deleteUser/index.ts new file mode 100644 index 0000000..ff7f954 --- /dev/null +++ b/server/src/contexts/users/infrastructure/express/controllers/deleteUser/index.ts @@ -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, + ); +}; diff --git a/server/src/contexts/users/infrastructure/express/controllers/getUser/GetUser.controller.ts b/server/src/contexts/users/infrastructure/express/controllers/getUser/GetUser.controller.ts index 743b320..98ff505 100644 --- a/server/src/contexts/users/infrastructure/express/controllers/getUser/GetUser.controller.ts +++ b/server/src/contexts/users/infrastructure/express/controllers/getUser/GetUser.controller.ts @@ -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 = result.object; - return this.ok( + return this.ok( this.presenter.map(user, this.context), ); } catch (e: unknown) { diff --git a/server/src/contexts/users/infrastructure/express/controllers/getUser/presenter/GetUser.presenter.ts b/server/src/contexts/users/infrastructure/express/controllers/getUser/presenter/GetUser.presenter.ts index 07ad501..7e9ca14 100644 --- a/server/src/contexts/users/infrastructure/express/controllers/getUser/presenter/GetUser.presenter.ts +++ b/server/src/contexts/users/infrastructure/express/controllers/getUser/presenter/GetUser.presenter.ts @@ -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(), diff --git a/server/src/contexts/users/infrastructure/express/controllers/index.ts b/server/src/contexts/users/infrastructure/express/controllers/index.ts index 501404e..5045b03 100644 --- a/server/src/contexts/users/infrastructure/express/controllers/index.ts +++ b/server/src/contexts/users/infrastructure/express/controllers/index.ts @@ -1,2 +1,5 @@ +export * from "./createUser"; +export * from "./deleteUser"; export * from "./getUser"; export * from "./listUsers"; +export * from "./updateUser"; diff --git a/server/src/contexts/users/infrastructure/express/controllers/updateUser/UpdateUser.controller.ts b/server/src/contexts/users/infrastructure/express/controllers/updateUser/UpdateUser.controller.ts new file mode 100644 index 0000000..c3af028 --- /dev/null +++ b/server/src/contexts/users/infrastructure/express/controllers/updateUser/UpdateUser.controller.ts @@ -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 { + 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 = result.object; + + return this.ok( + 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); + } + } +} diff --git a/server/src/contexts/users/infrastructure/express/controllers/updateUser/index.ts b/server/src/contexts/users/infrastructure/express/controllers/updateUser/index.ts new file mode 100644 index 0000000..4982921 --- /dev/null +++ b/server/src/contexts/users/infrastructure/express/controllers/updateUser/index.ts @@ -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, + ); +}; diff --git a/server/src/contexts/users/infrastructure/express/controllers/updateUser/presenter/UpdateUser.presenter.ts b/server/src/contexts/users/infrastructure/express/controllers/updateUser/presenter/UpdateUser.presenter.ts new file mode 100644 index 0000000..7637720 --- /dev/null +++ b/server/src/contexts/users/infrastructure/express/controllers/updateUser/presenter/UpdateUser.presenter.ts @@ -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(), + }; + }, +}; diff --git a/server/src/contexts/users/infrastructure/express/controllers/updateUser/presenter/index.ts b/server/src/contexts/users/infrastructure/express/controllers/updateUser/presenter/index.ts new file mode 100644 index 0000000..4869d6e --- /dev/null +++ b/server/src/contexts/users/infrastructure/express/controllers/updateUser/presenter/index.ts @@ -0,0 +1 @@ +export * from "./UpdateUser.presenter"; diff --git a/server/src/contexts/users/infrastructure/express/routes.ts b/server/src/contexts/users/infrastructure/express/routes.ts index 96a4975..4636dbc 100644 --- a/server/src/contexts/users/infrastructure/express/routes.ts +++ b/server/src/contexts/users/infrastructure/express/routes.ts @@ -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); }; diff --git a/shared/lib/contexts/auth/application/User.service.ts b/shared/lib/contexts/auth/application/User.service.ts index 96863f2..cb0ff5c 100644 --- a/shared/lib/contexts/auth/application/User.service.ts +++ b/shared/lib/contexts/auth/application/User.service.ts @@ -1,10 +1 @@ -import { UndefinedOr } from "../../../utilities"; -import { Email, Result } from "../../common"; - -export const ensureUserEmailIsValid = (value: UndefinedOr) => { - const valueOrError = Email.create(value); - - return valueOrError.isSuccess - ? Result.ok(valueOrError.object) - : Result.fail(valueOrError.error); -}; +export {}; diff --git a/shared/lib/contexts/users/application/dto/CreateUser.dto/ICreateUser_Request.dto.ts b/shared/lib/contexts/users/application/dto/CreateUser.dto/ICreateUser_Request.dto.ts new file mode 100644 index 0000000..2d92dae --- /dev/null +++ b/shared/lib/contexts/users/application/dto/CreateUser.dto/ICreateUser_Request.dto.ts @@ -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 { + const schema = Joi.object({ + id: Joi.string(), + name: Joi.string(), + email: Joi.string(), + }).unknown(true); + + const result = RuleValidator.validate( + schema, + userDTO, + ); + + if (result.isFailure) { + return Result.fail(result.error); + } + + return Result.ok(true); +} diff --git a/shared/lib/contexts/users/application/dto/CreateUser.dto/ICreateUser_Response.dto.ts b/shared/lib/contexts/users/application/dto/CreateUser.dto/ICreateUser_Response.dto.ts new file mode 100644 index 0000000..4a5f813 --- /dev/null +++ b/shared/lib/contexts/users/application/dto/CreateUser.dto/ICreateUser_Response.dto.ts @@ -0,0 +1,5 @@ +export interface ICreateUser_Response_DTO { + id: string; + name: string; + email: string; +} diff --git a/shared/lib/contexts/users/application/dto/CreateUser.dto/index.ts b/shared/lib/contexts/users/application/dto/CreateUser.dto/index.ts new file mode 100644 index 0000000..8c13e38 --- /dev/null +++ b/shared/lib/contexts/users/application/dto/CreateUser.dto/index.ts @@ -0,0 +1,2 @@ +export * from "./ICreateUser_Request.dto"; +export * from "./ICreateUser_Response.dto"; diff --git a/shared/lib/contexts/users/application/dto/GetUser.dto/IGetUser.dto.ts b/shared/lib/contexts/users/application/dto/GetUser.dto/IGetUser.dto.ts deleted file mode 100644 index b50356b..0000000 --- a/shared/lib/contexts/users/application/dto/GetUser.dto/IGetUser.dto.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { UniqueID } from "../../../../common"; - -export interface IGetUserRequest_DTO { - id: UniqueID; -} diff --git a/shared/lib/contexts/users/application/dto/GetUser.dto/IGetUser_Response.dto.ts b/shared/lib/contexts/users/application/dto/GetUser.dto/IGetUser_Response.dto.ts index 8274fa5..c6f8c1b 100644 --- a/shared/lib/contexts/users/application/dto/GetUser.dto/IGetUser_Response.dto.ts +++ b/shared/lib/contexts/users/application/dto/GetUser.dto/IGetUser_Response.dto.ts @@ -1,4 +1,4 @@ -export interface IGetUser_Response_DTO { +export interface IGetUserResponse_DTO { id: string; name: string; email: string; diff --git a/shared/lib/contexts/users/application/dto/GetUser.dto/index.ts b/shared/lib/contexts/users/application/dto/GetUser.dto/index.ts index 46a52b7..1ba62ba 100644 --- a/shared/lib/contexts/users/application/dto/GetUser.dto/index.ts +++ b/shared/lib/contexts/users/application/dto/GetUser.dto/index.ts @@ -1,2 +1 @@ -export * from "./IGetUser.dto"; export * from "./IGetUser_Response.dto"; diff --git a/shared/lib/contexts/users/application/dto/UpdateUser.dto/IUpdateUser_Request.dto.ts b/shared/lib/contexts/users/application/dto/UpdateUser.dto/IUpdateUser_Request.dto.ts new file mode 100644 index 0000000..9d25999 --- /dev/null +++ b/shared/lib/contexts/users/application/dto/UpdateUser.dto/IUpdateUser_Request.dto.ts @@ -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 { + const schema = Joi.object({ + id: Joi.string(), + name: Joi.string(), + email: Joi.string(), + }).unknown(true); + + const result = RuleValidator.validate( + schema, + userDTO, + ); + + if (result.isFailure) { + return Result.fail(result.error); + } + + return Result.ok(true); +} diff --git a/shared/lib/contexts/users/application/dto/UpdateUser.dto/IUpdateUser_Response.dto.ts b/shared/lib/contexts/users/application/dto/UpdateUser.dto/IUpdateUser_Response.dto.ts new file mode 100644 index 0000000..e4e2498 --- /dev/null +++ b/shared/lib/contexts/users/application/dto/UpdateUser.dto/IUpdateUser_Response.dto.ts @@ -0,0 +1,5 @@ +export interface IUpdateUser_Response_DTO { + id: string; + name: string; + email: string; +} diff --git a/shared/lib/contexts/users/application/dto/UpdateUser.dto/index.ts b/shared/lib/contexts/users/application/dto/UpdateUser.dto/index.ts new file mode 100644 index 0000000..5ecb999 --- /dev/null +++ b/shared/lib/contexts/users/application/dto/UpdateUser.dto/index.ts @@ -0,0 +1,2 @@ +export * from "./IUpdateUser_Request.dto"; +export * from "./IUpdateUser_Response.dto"; diff --git a/shared/lib/contexts/users/application/dto/index.ts b/shared/lib/contexts/users/application/dto/index.ts index be7970b..b0fdde4 100644 --- a/shared/lib/contexts/users/application/dto/index.ts +++ b/shared/lib/contexts/users/application/dto/index.ts @@ -1,2 +1,4 @@ +export * from "./CreateUser.dto"; export * from "./GetUser.dto"; export * from "./IListUsers.dto"; +export * from "./UpdateUser.dto";