.
This commit is contained in:
parent
38ead39cb3
commit
923bd92220
@ -36,7 +36,7 @@ export const isLoggedUser = compose([
|
||||
}),
|
||||
(req: Express.Request, res: Express.Response, next: Express.NextFunction) => {
|
||||
const user = <AuthUser>req.user;
|
||||
if (user.isUser) {
|
||||
if (!user.isUser) {
|
||||
return generateExpressErrorResponse(req, res, httpStatus.UNAUTHORIZED);
|
||||
}
|
||||
next();
|
||||
|
||||
@ -2,7 +2,7 @@ import { registerMiddleware } from "@/contexts/common/infrastructure/express";
|
||||
import Express from "express";
|
||||
import passport from "passport";
|
||||
import { createLoginController } from "./controllers";
|
||||
import { isLoggedUser } from "./passport";
|
||||
import { isAdminUser, isLoggedUser } from "./passport";
|
||||
|
||||
/*authRoutes.post(
|
||||
"/login",
|
||||
@ -41,6 +41,7 @@ export const AuthRouter = (appRouter: Express.Router) => {
|
||||
const authRoutes: Express.Router = Express.Router({ mergeParams: true });
|
||||
|
||||
appRouter.use(registerMiddleware("isLoggedUser", isLoggedUser));
|
||||
appRouter.use(registerMiddleware("isAdminUser", isAdminUser));
|
||||
|
||||
authRoutes.post(
|
||||
"/login",
|
||||
|
||||
366
server/src/contexts/users/application/CreateUser.useCase.ts
Normal file
366
server/src/contexts/users/application/CreateUser.useCase.ts
Normal file
@ -0,0 +1,366 @@
|
||||
import {
|
||||
IUseCase,
|
||||
IUseCaseError,
|
||||
UseCaseError,
|
||||
handleInvalidInputDataFailure,
|
||||
handleResourceAlreadyExitsFailure,
|
||||
handleUseCaseError,
|
||||
} from "@/contexts/common/application/useCases";
|
||||
import { IRepositoryManager } from "@/contexts/common/domain";
|
||||
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||
import {
|
||||
AddressTitle,
|
||||
City,
|
||||
Collection,
|
||||
Country,
|
||||
DomainError,
|
||||
ICreateUser_DTO,
|
||||
IDomainError,
|
||||
Note,
|
||||
PostalCode,
|
||||
Province,
|
||||
Result,
|
||||
ResultCollection,
|
||||
Street,
|
||||
UniqueID,
|
||||
UserEmail,
|
||||
UserJobTitle,
|
||||
UserName,
|
||||
UserPhone,
|
||||
UserTIN,
|
||||
ensureUserIdIsValid,
|
||||
ensureUserTINIsValid,
|
||||
} 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";
|
||||
|
||||
export type CreateUserResponseOrError =
|
||||
| Result<never, IUseCaseError> // Misc errors (value objects)
|
||||
| Result<User, never>; // Success!
|
||||
|
||||
export class CreateUserUseCase
|
||||
implements IUseCase<ICreateUser_DTO, Promise<CreateUserResponseOrError>>
|
||||
{
|
||||
private _adapter: ISequelizeAdapter;
|
||||
private _repositoryManager: IRepositoryManager;
|
||||
|
||||
constructor(props: {
|
||||
adapter: ISequelizeAdapter;
|
||||
repositoryManager: IRepositoryManager;
|
||||
}) {
|
||||
this._adapter = props.adapter;
|
||||
this._repositoryManager = props.repositoryManager;
|
||||
}
|
||||
|
||||
private getRepositoryByName<T>(name: string) {
|
||||
return this._repositoryManager.getRepository<T>(name);
|
||||
}
|
||||
|
||||
async execute(request: ICreateUser_DTO): Promise<CreateUserResponseOrError> {
|
||||
const userDTO = request;
|
||||
|
||||
// Validaciones de datos
|
||||
|
||||
const userIdOrError = ensureUserIdIsValid(userDTO.id);
|
||||
if (userIdOrError.isFailure) {
|
||||
return handleInvalidInputDataFailure(
|
||||
"User ID is not valid",
|
||||
userIdOrError.error,
|
||||
);
|
||||
}
|
||||
|
||||
const idExists = await this.findUserID(userIdOrError.object);
|
||||
if (idExists) {
|
||||
return handleResourceAlreadyExitsFailure(
|
||||
`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 tinExists =
|
||||
!tinOrError.object.isEmpty() &&
|
||||
(await this.findUserTIN(tinOrError.object));
|
||||
|
||||
if (tinExists) {
|
||||
return handleResourceAlreadyExitsFailure(
|
||||
`User with TIN ${tinOrError.object.toString()} exists`,
|
||||
userIdOrError.error,
|
||||
);
|
||||
}
|
||||
|
||||
// Crear user
|
||||
const userOrError = this.tryCreateUserInstance(
|
||||
userDTO,
|
||||
userIdOrError.object,
|
||||
);
|
||||
|
||||
if (userOrError.isFailure) {
|
||||
const { error: domainError } = userOrError;
|
||||
let errorCode = "";
|
||||
let message = "";
|
||||
|
||||
switch (domainError.code) {
|
||||
case User.ERROR_CUSTOMER_WITHOUT_NAME:
|
||||
return handleInvalidInputDataFailure(
|
||||
"El usuario debe ser una compañía o tener nombre y apellidos.",
|
||||
domainError,
|
||||
);
|
||||
break;
|
||||
|
||||
case DomainError.INVALID_INPUT_DATA:
|
||||
errorCode = UseCaseError.INVALID_INPUT_DATA;
|
||||
message = domainError.message;
|
||||
return handleInvalidInputDataFailure(
|
||||
"El usuario debe ser una compañía o tener nombre y apellidos.",
|
||||
domainError,
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
errorCode = UseCaseError.UNEXCEPTED_ERROR;
|
||||
message = domainError.message;
|
||||
return handleUseCaseError(errorCode, message, domainError);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return this.createUser(userOrError.object);
|
||||
}
|
||||
|
||||
private async createUser(user: User) {
|
||||
// Guardar el contacto
|
||||
const transaction = this._adapter.startTransaction();
|
||||
const userRepoBuilder = this.getRepositoryByName<IUserRepository>("User");
|
||||
let userRepo: IUserRepository;
|
||||
|
||||
try {
|
||||
await transaction.complete(async (t) => {
|
||||
userRepo = userRepoBuilder({ transaction: t });
|
||||
await userRepo.create(user);
|
||||
});
|
||||
|
||||
return Result.ok<User>(user);
|
||||
} catch (error: unknown) {
|
||||
const _error = error as IInfrastructureError;
|
||||
return Result.fail(
|
||||
handleUseCaseError(
|
||||
UseCaseError.REPOSITORY_ERROR,
|
||||
"Error al guardar el usuario",
|
||||
_error,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
userId: UniqueID,
|
||||
): Result<User, IDomainError> {
|
||||
const userTINOrError = ensureUserTINIsValid(userDTO.tin);
|
||||
if (userTINOrError.isFailure) {
|
||||
return Result.fail(userTINOrError.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);
|
||||
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,
|
||||
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);
|
||||
}
|
||||
}
|
||||
65
server/src/contexts/users/application/DeleteUser.useCase.ts
Normal file
65
server/src/contexts/users/application/DeleteUser.useCase.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import {
|
||||
IUseCase,
|
||||
IUseCaseError,
|
||||
IUseCaseRequest,
|
||||
UseCaseError,
|
||||
handleUseCaseError,
|
||||
} from "@/contexts/common/application/useCases";
|
||||
import { IRepositoryManager } from "@/contexts/common/domain";
|
||||
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||
import { Result, UniqueID } from "@shared/contexts";
|
||||
import { IUserRepository } from "../domain";
|
||||
|
||||
export interface IDeleteUserUseCaseRequest extends IUseCaseRequest {
|
||||
id: UniqueID;
|
||||
}
|
||||
|
||||
export type DeleteUserResponseOrError =
|
||||
| Result<never, IUseCaseError> // Misc errors (value objects)
|
||||
| Result<void, never>; // Success!
|
||||
|
||||
export class DeleteUserUseCase
|
||||
implements
|
||||
IUseCase<IDeleteUserUseCaseRequest, Promise<DeleteUserResponseOrError>>
|
||||
{
|
||||
private _adapter: ISequelizeAdapter;
|
||||
private _repositoryManager: IRepositoryManager;
|
||||
|
||||
constructor(props: {
|
||||
adapter: ISequelizeAdapter;
|
||||
repositoryManager: IRepositoryManager;
|
||||
}) {
|
||||
this._adapter = props.adapter;
|
||||
this._repositoryManager = props.repositoryManager;
|
||||
}
|
||||
|
||||
private getRepositoryByName<T>(name: string) {
|
||||
return this._repositoryManager.getRepository<T>(name);
|
||||
}
|
||||
|
||||
async execute(
|
||||
request: IDeleteUserUseCaseRequest,
|
||||
): Promise<DeleteUserResponseOrError> {
|
||||
const { id: userId } = request;
|
||||
|
||||
const transaction = this._adapter.startTransaction();
|
||||
const userRepoBuilder = this.getRepositoryByName<IUserRepository>("User");
|
||||
|
||||
try {
|
||||
await transaction.complete(async (t) => {
|
||||
const invoiceRepo = userRepoBuilder({ transaction: t });
|
||||
await invoiceRepo.removeById(userId);
|
||||
});
|
||||
|
||||
return Result.ok<void>();
|
||||
} catch (error: unknown) {
|
||||
//const _error = error as IInfrastructureError;
|
||||
return Result.fail(
|
||||
handleUseCaseError(
|
||||
UseCaseError.REPOSITORY_ERROR,
|
||||
"Error al eliminar el usuario",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
83
server/src/contexts/users/application/GetUser.useCase.ts
Normal file
83
server/src/contexts/users/application/GetUser.useCase.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import {
|
||||
IUseCase,
|
||||
IUseCaseError,
|
||||
IUseCaseRequest,
|
||||
UseCaseError,
|
||||
handleUseCaseError,
|
||||
} from "@/contexts/common/application/useCases";
|
||||
import { IRepositoryManager } from "@/contexts/common/domain";
|
||||
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||
import { Result, UniqueID } from "@shared/contexts";
|
||||
import { IUserRepository } from "../domain";
|
||||
|
||||
import { IInfrastructureError } from "@/contexts/common/infrastructure";
|
||||
import { User } from "../domain/entities/User";
|
||||
|
||||
export interface IGetUserUseCaseRequest extends IUseCaseRequest {
|
||||
id: UniqueID;
|
||||
}
|
||||
|
||||
export type GetUserResponseOrError =
|
||||
| Result<never, IUseCaseError> // Misc errors (value objects)
|
||||
| Result<User, never>; // Success!
|
||||
|
||||
export class GetUserUseCase
|
||||
implements IUseCase<IGetUserUseCaseRequest, Promise<GetUserResponseOrError>>
|
||||
{
|
||||
private _adapter: ISequelizeAdapter;
|
||||
private _repositoryManager: IRepositoryManager;
|
||||
|
||||
constructor(props: {
|
||||
adapter: ISequelizeAdapter;
|
||||
repositoryManager: IRepositoryManager;
|
||||
}) {
|
||||
this._adapter = props.adapter;
|
||||
this._repositoryManager = props.repositoryManager;
|
||||
}
|
||||
|
||||
private getRepositoryByName<T>(name: string) {
|
||||
return this._repositoryManager.getRepository<T>(name);
|
||||
}
|
||||
|
||||
async execute(
|
||||
request: IGetUserUseCaseRequest,
|
||||
): Promise<GetUserResponseOrError> {
|
||||
const { id } = request;
|
||||
|
||||
// Validación de datos
|
||||
// No hay en este caso
|
||||
|
||||
return await this.findUser(id);
|
||||
}
|
||||
|
||||
private async findUser(id: UniqueID) {
|
||||
const transaction = this._adapter.startTransaction();
|
||||
const userRepoBuilder = this.getRepositoryByName<IUserRepository>("User");
|
||||
|
||||
let user: User | null = null;
|
||||
|
||||
try {
|
||||
await transaction.complete(async (t) => {
|
||||
const userRepo = userRepoBuilder({ transaction: t });
|
||||
user = await userRepo.getById(id);
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return Result.fail(
|
||||
handleUseCaseError(UseCaseError.NOT_FOUND_ERROR, "User not found"),
|
||||
);
|
||||
}
|
||||
|
||||
return Result.ok<User>(user!);
|
||||
} catch (error: unknown) {
|
||||
const _error = error as IInfrastructureError;
|
||||
return Result.fail(
|
||||
handleUseCaseError(
|
||||
UseCaseError.REPOSITORY_ERROR,
|
||||
"Error al consultar el usuario",
|
||||
_error,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
337
server/src/contexts/users/application/UpdateUser.useCase.ts
Normal file
337
server/src/contexts/users/application/UpdateUser.useCase.ts
Normal file
@ -0,0 +1,337 @@
|
||||
import {
|
||||
IUseCase,
|
||||
IUseCaseError,
|
||||
IUseCaseRequest,
|
||||
UseCaseError,
|
||||
handleUseCaseError,
|
||||
} from "@/contexts/common/application/useCases";
|
||||
import { IRepositoryManager } from "@/contexts/common/domain";
|
||||
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||
import {
|
||||
AddressTitle,
|
||||
City,
|
||||
Collection,
|
||||
Country,
|
||||
DomainError,
|
||||
IDomainError,
|
||||
IUpdateUser_DTO,
|
||||
Note,
|
||||
PostalCode,
|
||||
Province,
|
||||
Result,
|
||||
ResultCollection,
|
||||
Street,
|
||||
UniqueID,
|
||||
UserEmail,
|
||||
UserJobTitle,
|
||||
UserName,
|
||||
UserPhone,
|
||||
UserTIN,
|
||||
} from "@shared/contexts";
|
||||
|
||||
import { IInfrastructureError } from "@/contexts/common/infrastructure";
|
||||
import {
|
||||
IBillingAddressUser_DTO,
|
||||
IShippingAddressUser_DTO,
|
||||
} from "@shared/contexts/users/application/dto/IUserAddressDTO";
|
||||
import {
|
||||
IUserRepository,
|
||||
User,
|
||||
UserAddressType,
|
||||
UserShippingAddress,
|
||||
} from "../domain";
|
||||
|
||||
export interface IUpdateUserUseCaseRequest extends IUseCaseRequest {
|
||||
id: UniqueID;
|
||||
userDTO: IUpdateUser_DTO;
|
||||
}
|
||||
|
||||
export type UpdateUserResponseOrError =
|
||||
| Result<never, IUseCaseError> // Misc errors (value objects)
|
||||
| Result<User, never>; // Success!
|
||||
|
||||
export class UpdateUserUseCase
|
||||
implements
|
||||
IUseCase<IUpdateUserUseCaseRequest, Promise<UpdateUserResponseOrError>>
|
||||
{
|
||||
private _adapter: ISequelizeAdapter;
|
||||
private _repositoryManager: IRepositoryManager;
|
||||
|
||||
constructor(props: {
|
||||
adapter: ISequelizeAdapter;
|
||||
repositoryManager: IRepositoryManager;
|
||||
}) {
|
||||
this._adapter = props.adapter;
|
||||
this._repositoryManager = props.repositoryManager;
|
||||
}
|
||||
|
||||
private getRepositoryByName<T>(name: string) {
|
||||
return this._repositoryManager.getRepository<T>(name);
|
||||
}
|
||||
|
||||
async execute(
|
||||
request: IUpdateUserUseCaseRequest,
|
||||
): Promise<UpdateUserResponseOrError> {
|
||||
const { id, userDTO } = request;
|
||||
|
||||
// Comprobar que existe el user
|
||||
const idExists = await this._findUserID(id);
|
||||
if (!idExists) {
|
||||
const message = `User with ID ${id.toString()} not found`;
|
||||
return Result.fail<IUseCaseError>(
|
||||
handleUseCaseError(UseCaseError.NOT_FOUND_ERROR, message, [
|
||||
{ path: "id" },
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
// Crear user
|
||||
const userOrError = this._tryCreateUserInstance(userDTO, id);
|
||||
|
||||
if (userOrError.isFailure) {
|
||||
const { error: domainError } = userOrError;
|
||||
let errorCode = "";
|
||||
let message = "";
|
||||
let payload = {};
|
||||
|
||||
switch (domainError.code) {
|
||||
// Errores manuales
|
||||
case User.ERROR_CUSTOMER_WITHOUT_NAME:
|
||||
errorCode = UseCaseError.INVALID_INPUT_DATA;
|
||||
message = "El usuario debe ser una compañía o tener nombre.";
|
||||
payload = [{ path: "first_name" }, { path: "company_name" }];
|
||||
break;
|
||||
|
||||
// Value object error
|
||||
case DomainError.INVALID_INPUT_DATA:
|
||||
errorCode = UseCaseError.INVALID_INPUT_DATA;
|
||||
message = domainError.message;
|
||||
payload = domainError.payload;
|
||||
break;
|
||||
|
||||
default:
|
||||
errorCode = UseCaseError.UNEXCEPTED_ERROR;
|
||||
message = domainError.message;
|
||||
payload = domainError.payload;
|
||||
break;
|
||||
}
|
||||
|
||||
return Result.fail<IUseCaseError>(
|
||||
handleUseCaseError(errorCode, message, payload),
|
||||
);
|
||||
}
|
||||
|
||||
return this._updateUser(userOrError.object);
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
private _tryCreateUserShippingAddresses(
|
||||
addressesDTO: IShippingAddressUser_DTO[] | undefined,
|
||||
) {
|
||||
const shippingAddressesOrError = new ResultCollection<
|
||||
UserShippingAddress,
|
||||
DomainError
|
||||
>();
|
||||
|
||||
if (addressesDTO) {
|
||||
addressesDTO.map((value) => {
|
||||
const result = this._tryCreateUserAddress(value, "shipping");
|
||||
shippingAddressesOrError.add(result);
|
||||
});
|
||||
}
|
||||
|
||||
if (shippingAddressesOrError.hasSomeFaultyResult()) {
|
||||
return Result.fail(shippingAddressesOrError.getFirstFaultyResult().error);
|
||||
}
|
||||
|
||||
return Result.ok(new Collection(shippingAddressesOrError.objects));
|
||||
}
|
||||
|
||||
private _tryCreateUserInstance(
|
||||
userDTO: IUpdateUser_DTO,
|
||||
userId: UniqueID,
|
||||
): Result<User, IDomainError> {
|
||||
const userTINOrError = UserTIN.create(userDTO.tin, {
|
||||
label: "tin",
|
||||
path: "tin",
|
||||
});
|
||||
if (userTINOrError.isFailure) {
|
||||
return Result.fail(userTINOrError.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);
|
||||
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,
|
||||
email: emailOrError.object,
|
||||
phone: phoneOrError.object,
|
||||
//taxId: taxIdOrError.object,
|
||||
notes: notesOrError.object,
|
||||
|
||||
billingAddress: billingAddressOrError.object,
|
||||
shippingAddresses: shippingAddressesOrError.object,
|
||||
},
|
||||
userId,
|
||||
);
|
||||
}
|
||||
|
||||
private async _findUserTIN(tin: UserTIN) {
|
||||
const userRepoBuilder = this.getRepositoryByName<IUserRepository>("User");
|
||||
return await userRepoBuilder().existsWithSameTIN(tin);
|
||||
}
|
||||
|
||||
private async _findUserID(id: UniqueID) {
|
||||
const userRepoBuilder = this.getRepositoryByName<IUserRepository>("User");
|
||||
return await userRepoBuilder().exists(id);
|
||||
}
|
||||
|
||||
private async _updateUser(user: User) {
|
||||
// Guardar el contacto
|
||||
const transaction = this._adapter.startTransaction();
|
||||
const userRepoBuilder = this.getRepositoryByName<IUserRepository>("User");
|
||||
|
||||
try {
|
||||
await transaction.complete(async (t) => {
|
||||
const userRepo = userRepoBuilder({ transaction: t });
|
||||
await userRepo.update(user);
|
||||
});
|
||||
|
||||
return Result.ok<User>(user);
|
||||
} catch (error: unknown) {
|
||||
const _error = error as IInfrastructureError;
|
||||
return Result.fail(
|
||||
handleUseCaseError(
|
||||
UseCaseError.REPOSITORY_ERROR,
|
||||
"Error al guardar el usuario",
|
||||
_error,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1 +1,5 @@
|
||||
export * from "./ListUsersUseCase";
|
||||
export * from "./DeleteUser.useCase";
|
||||
export * from "./GetUser.useCase";
|
||||
export * from "./ListUsers.useCase";
|
||||
//export * from "./CreateUser.useCase";
|
||||
//export * from "./UpdateUser.useCase";
|
||||
|
||||
@ -5,4 +5,8 @@ import { User } from "../entities";
|
||||
export interface IUserRepository extends IRepository<any> {
|
||||
getById(id: UniqueID): Promise<User | null>;
|
||||
findAll(queryCriteria?: IQueryCriteria): Promise<ICollection<User>>;
|
||||
|
||||
removeById(id: UniqueID): Promise<void>;
|
||||
|
||||
exists(id: UniqueID): Promise<boolean>;
|
||||
}
|
||||
|
||||
@ -67,6 +67,14 @@ export class UserRepository
|
||||
|
||||
return this.mapper.mapArrayAndCountToDomain(rows, count);
|
||||
}
|
||||
|
||||
public async removeById(id: UniqueID, force: boolean = false): Promise<void> {
|
||||
return this._removeById("User_Model", id);
|
||||
}
|
||||
|
||||
public async exists(id: UniqueID): Promise<boolean> {
|
||||
return this._exists("User_Model", "id", id.toString());
|
||||
}
|
||||
}
|
||||
|
||||
export const registerUserRepository = (context: IUserContext) => {
|
||||
|
||||
@ -0,0 +1,106 @@
|
||||
import {
|
||||
IUseCaseError,
|
||||
UseCaseError,
|
||||
} from "@/contexts/common/application/useCases";
|
||||
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 { IServerError } from "@/contexts/common/domain/errors";
|
||||
import {
|
||||
IInfrastructureError,
|
||||
InfrastructureError,
|
||||
handleInfrastructureError,
|
||||
} from "@/contexts/common/infrastructure";
|
||||
import { IUserContext } from "../../../User.context";
|
||||
import { IGetUserPresenter } from "./presenter";
|
||||
|
||||
export class GetUserController extends ExpressController {
|
||||
private useCase: GetUserUseCase;
|
||||
private presenter: IGetUserPresenter;
|
||||
private context: IUserContext;
|
||||
|
||||
constructor(
|
||||
props: {
|
||||
useCase: GetUserUseCase;
|
||||
presenter: IGetUserPresenter;
|
||||
},
|
||||
context: IUserContext,
|
||||
) {
|
||||
super();
|
||||
|
||||
const { useCase, presenter } = props;
|
||||
this.useCase = useCase;
|
||||
this.presenter = presenter;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
async executeImpl(): Promise<any> {
|
||||
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);
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await this.useCase.execute({
|
||||
id: userIdOrError.object,
|
||||
});
|
||||
|
||||
if (result.isFailure) {
|
||||
return this._handleExecuteError(result.error);
|
||||
}
|
||||
|
||||
const user = <User>result.object;
|
||||
|
||||
return this.ok<IGetUser_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.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
import { GetUserUseCase } from "@/contexts/users/application";
|
||||
import { IUserContext } from "../../../User.context";
|
||||
import { registerUserRepository } from "../../../User.repository";
|
||||
import { GetUserController } from "./GetUser.controller";
|
||||
import { GetUserPresenter } from "./presenter";
|
||||
|
||||
export const createGetUserController = (context: IUserContext) => {
|
||||
registerUserRepository(context);
|
||||
return new GetUserController(
|
||||
{
|
||||
useCase: new GetUserUseCase(context),
|
||||
presenter: GetUserPresenter,
|
||||
},
|
||||
context,
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,17 @@
|
||||
import { IGetUser_Response_DTO } from "@shared/contexts";
|
||||
import { User } from "../../../../../domain";
|
||||
import { IUserContext } from "../../../../User.context";
|
||||
|
||||
export interface IGetUserPresenter {
|
||||
map: (user: User, context: IUserContext) => IGetUser_Response_DTO;
|
||||
}
|
||||
|
||||
export const GetUserPresenter: IGetUserPresenter = {
|
||||
map: (user: User, context: IUserContext): IGetUser_Response_DTO => {
|
||||
return {
|
||||
id: user.id.toString(),
|
||||
name: user.name.toString(),
|
||||
email: user.email.toString(),
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export * from "./GetUser.presenter";
|
||||
@ -1 +1,2 @@
|
||||
export * from "./getUser";
|
||||
export * from "./listUsers";
|
||||
|
||||
@ -73,10 +73,10 @@ export class ListUsersController extends ExpressController {
|
||||
return this.clientError(result.error.message);
|
||||
}
|
||||
|
||||
const customers = <ICollection<User>>result.object;
|
||||
const users = <ICollection<User>>result.object;
|
||||
|
||||
return this.ok<IListResponse_DTO<IListUsers_Response_DTO>>(
|
||||
this.presenter.mapArray(customers, this.context, {
|
||||
this.presenter.mapArray(users, this.context, {
|
||||
page: queryCriteria.pagination.offset,
|
||||
limit: queryCriteria.pagination.limit,
|
||||
}),
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import { applyMiddleware } from "@/contexts/common/infrastructure/express";
|
||||
import Express from "express";
|
||||
import { createListUsersController } from "./controllers";
|
||||
import {
|
||||
createGetUserController,
|
||||
createListUsersController,
|
||||
} from "./controllers";
|
||||
|
||||
export const UserRouter = (appRouter: Express.Router) => {
|
||||
const userRoutes: Express.Router = Express.Router({ mergeParams: true });
|
||||
@ -12,12 +15,12 @@ export const UserRouter = (appRouter: Express.Router) => {
|
||||
createListUsersController(res.locals["context"]).execute(req, res, next),
|
||||
);
|
||||
|
||||
/*userRoutes.get(
|
||||
"/:id",
|
||||
userRoutes.get(
|
||||
"/:userId",
|
||||
applyMiddleware("isAdminUser"),
|
||||
(req: Express.Request, res: Express.Response, next: Express.NextFunction) =>
|
||||
createGettUserController(res.locals["context"]).execute(req, res, next),
|
||||
);*/
|
||||
createGetUserController(res.locals["context"]).execute(req, res, next),
|
||||
);
|
||||
|
||||
appRouter.use("/users", userRoutes);
|
||||
};
|
||||
|
||||
42
shared/lib/contexts/common/application/Common.service.ts
Normal file
42
shared/lib/contexts/common/application/Common.service.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { Email, Name, Phone, Result, UniqueID } from "..";
|
||||
import { UndefinedOr } from "../../../utilities";
|
||||
|
||||
export const ensureIdIsValid = (value: string) =>
|
||||
UniqueID.create(value, { generateOnEmpty: false });
|
||||
|
||||
export const ensureNameIsValid = (
|
||||
value: UndefinedOr<string>,
|
||||
label: string = "name",
|
||||
) => {
|
||||
const valueOrError = Name.create(value, {
|
||||
label,
|
||||
});
|
||||
|
||||
return valueOrError.isSuccess
|
||||
? Result.ok(valueOrError.object)
|
||||
: Result.fail(valueOrError.error);
|
||||
};
|
||||
|
||||
export const ensureEmailIsValid = (
|
||||
value: UndefinedOr<string>,
|
||||
label: string = "email",
|
||||
) => {
|
||||
const valueOrError = Email.create(value, {
|
||||
label,
|
||||
});
|
||||
|
||||
return valueOrError.isSuccess
|
||||
? Result.ok(valueOrError.object)
|
||||
: Result.fail(valueOrError.error);
|
||||
};
|
||||
|
||||
export const ensurePhoneIsValid = (
|
||||
value: UndefinedOr<string>,
|
||||
label: string = "phone",
|
||||
) => {
|
||||
const valueOrError = Phone.create(value, { label });
|
||||
|
||||
return valueOrError.isSuccess
|
||||
? Result.ok(valueOrError.object)
|
||||
: Result.fail(valueOrError.error);
|
||||
};
|
||||
@ -1 +1,2 @@
|
||||
export * from "./Common.service";
|
||||
export * from "./dto";
|
||||
|
||||
@ -17,7 +17,7 @@ export interface IUniqueIDOptions extends INullableValueObjectOptions {
|
||||
export class UniqueID extends NullableValueObject<string> {
|
||||
protected static validate(
|
||||
value: UndefinedOr<string>,
|
||||
options: IUniqueIDOptions
|
||||
options: IUniqueIDOptions,
|
||||
) {
|
||||
const ruleIsEmpty = RuleValidator.RULE_ALLOW_EMPTY.default("");
|
||||
|
||||
@ -43,6 +43,15 @@ export class UniqueID extends NullableValueObject<string> {
|
||||
...options,
|
||||
};
|
||||
|
||||
if (!value && !_options.generateOnEmpty) {
|
||||
return Result.fail(
|
||||
handleDomainError(
|
||||
DomainError.INVALID_INPUT_DATA,
|
||||
"ID is null or empty",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (value) {
|
||||
const validationResult = UniqueID.validate(value, _options);
|
||||
|
||||
@ -51,13 +60,13 @@ export class UniqueID extends NullableValueObject<string> {
|
||||
handleDomainError(
|
||||
DomainError.INVALID_INPUT_DATA,
|
||||
validationResult.error.message,
|
||||
_options
|
||||
)
|
||||
_options,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Result.ok<UniqueID>(
|
||||
new UniqueID(UniqueID.sanitize(validationResult.object))
|
||||
new UniqueID(UniqueID.sanitize(validationResult.object)),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
1
shared/lib/contexts/users/application/User.service.ts
Normal file
1
shared/lib/contexts/users/application/User.service.ts
Normal file
@ -0,0 +1 @@
|
||||
export {};
|
||||
@ -0,0 +1,5 @@
|
||||
import { UniqueID } from "../../../../common";
|
||||
|
||||
export interface IGetUserRequest_DTO {
|
||||
id: UniqueID;
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
export interface IGetUser_Response_DTO {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./IGetUser.dto";
|
||||
export * from "./IGetUser_Response.dto";
|
||||
@ -1 +1,2 @@
|
||||
export * from "./GetUser.dto";
|
||||
export * from "./IListUsers.dto";
|
||||
|
||||
@ -1 +1,2 @@
|
||||
export * from "./User.service";
|
||||
export * from "./dto";
|
||||
|
||||
@ -8,6 +8,11 @@
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"references": [{ "path": "./shared/tsconfig.json" }],
|
||||
"include": ["server/**/*.ts", "client/**/*.ts", "shared/**/*.ts"],
|
||||
"include": [
|
||||
"server/**/*.ts",
|
||||
"client/**/*.ts",
|
||||
"shared/**/*.ts",
|
||||
"server/src/contexts/users/application/CreateUser.useCase.ts"
|
||||
],
|
||||
"exclude": ["**/node_modules"]
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user