This commit is contained in:
David Arranz 2024-05-16 21:40:07 +02:00
parent 38ead39cb3
commit 923bd92220
27 changed files with 1099 additions and 15 deletions

View File

@ -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();

View File

@ -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",

View 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);
}
}

View 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",
),
);
}
}
}

View 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,
),
);
}
}
}

View 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,
),
);
}
}
}

View File

@ -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";

View File

@ -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>;
}

View File

@ -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) => {

View File

@ -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);
}
}
}

View File

@ -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,
);
};

View File

@ -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(),
};
},
};

View File

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

View File

@ -1 +1,2 @@
export * from "./getUser";
export * from "./listUsers";

View File

@ -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,
}),

View File

@ -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);
};

View 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);
};

View File

@ -1 +1,2 @@
export * from "./Common.service";
export * from "./dto";

View File

@ -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)),
);
}

View File

@ -0,0 +1 @@
export {};

View File

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

View File

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

View File

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

View File

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

View File

@ -1 +1,2 @@
export * from "./User.service";
export * from "./dto";

View File

@ -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"]
}