This commit is contained in:
David Arranz 2024-05-20 00:04:23 +02:00
parent 1685a44fc2
commit 7cd155f331
34 changed files with 354 additions and 218 deletions

View File

@ -1,10 +1,11 @@
{
"semi": true,
"printWidth": 80,
"printWidth": 130,
"tabWidth": 4,
"useTabs": false,
"endOfLine": "auto",
"trailingComma": "all",
"semi": true,
"singleQuote": false,
"bracketSpacing": true
"trailingComma": "all",
"bracketSpacing": true,
"jsxBracketSameLine": true,
"arrowParens": "always"
}

View File

@ -1,5 +1,4 @@
{
//"typescript.surveys.enabled": false,
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit",
"source.fixAll.eslint": "explicit"

View File

@ -4,8 +4,8 @@ module.exports = {
"9d6c903873c341816995a8be0355c6f0d6d471fc6aedacf50790e9b1e49c45b3",
refresh_secret_key:
"3972dc40c69327b65352ed097419213b0b75561169dba562410b85660bb1f305",
token_expiration: "5m",
refresh_token_expiration: "7d",
token_expiration: "7d",
refresh_token_expiration: "30d",
},
database: {

View File

@ -3,7 +3,6 @@ import {
IUseCaseError,
IUseCaseRequest,
UseCaseError,
handleUseCaseError,
} from "@/contexts/common/application";
import { IRepositoryManager } from "@/contexts/common/domain";
import { IInfrastructureError } from "@/contexts/common/infrastructure";
@ -49,7 +48,7 @@ export class FindUserByEmailUseCase
const emailOrError = ensureEmailIsValid(email);
if (emailOrError.isFailure) {
return Result.fail(
handleUseCaseError(
UseCaseError.create(
UseCaseError.INVALID_INPUT_DATA,
"Email or password is not valid",
emailOrError.error,
@ -66,7 +65,7 @@ export class FindUserByEmailUseCase
if (user === null) {
return Result.fail(
handleUseCaseError(
UseCaseError.create(
UseCaseError.NOT_FOUND_ERROR,
`User with email ${email} not found`,
),
@ -76,7 +75,7 @@ export class FindUserByEmailUseCase
} catch (error: unknown) {
const _error = error as IInfrastructureError;
return Result.fail(
handleUseCaseError(
UseCaseError.create(
UseCaseError.REPOSITORY_ERROR,
"Error al buscar el usuario",
_error,

View File

@ -2,12 +2,11 @@ import {
IUseCase,
IUseCaseError,
UseCaseError,
handleUseCaseError,
} from "@/contexts/common/application";
import { IRepositoryManager } from "@/contexts/common/domain";
import { IInfrastructureError } from "@/contexts/common/infrastructure";
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
import { ILogin_DTO, Result, ensureUserEmailIsValid } from "@shared/contexts";
import { ILogin_DTO, Result, ensureEmailIsValid } from "@shared/contexts";
import { AuthUser } from "../domain";
import { findUserByEmail } from "./authServices";
@ -38,10 +37,10 @@ export class LoginUseCase
// Validaciones de datos
const emailOrError = ensureUserEmailIsValid(email);
const emailOrError = ensureEmailIsValid(email);
if (emailOrError.isFailure) {
return Result.fail(
handleUseCaseError(
UseCaseError.create(
UseCaseError.INVALID_INPUT_DATA,
"Email or password is not valid",
emailOrError.error,
@ -58,7 +57,7 @@ export class LoginUseCase
);
if (user === null || !user.verifyPassword(password)) {
return Result.fail(
handleUseCaseError(
UseCaseError.create(
UseCaseError.INVALID_INPUT_DATA,
"Email or password is not valid",
),
@ -68,7 +67,7 @@ export class LoginUseCase
} catch (error: unknown) {
const _error = error as IInfrastructureError;
return Result.fail(
handleUseCaseError(
UseCaseError.create(
UseCaseError.REPOSITORY_ERROR,
"Error al buscar el usuario",
_error,

View File

@ -1,10 +1,7 @@
import { config } from "@/config";
import { AuthUser } from "@/contexts/auth/domain";
import { IServerError } from "@/contexts/common/domain/errors";
import {
InfrastructureError,
handleInfrastructureError,
} from "@/contexts/common/infrastructure";
import { InfrastructureError } from "@/contexts/common/infrastructure";
import { ExpressController } from "@/contexts/common/infrastructure/express";
import { ILogin_Response_DTO } from "@shared/contexts";
import JWT from "jsonwebtoken";
@ -34,7 +31,7 @@ export class LoginController extends ExpressController {
if (!user) {
const errorMessage = "Unexpected missing user data";
const infraError = handleInfrastructureError(
const infraError = InfrastructureError.create(
InfrastructureError.UNEXCEPTED_ERROR,
errorMessage,
);

View File

@ -2,7 +2,7 @@ import {
ISequelizeMapper,
SequelizeMapper,
} from "@/contexts/common/infrastructure";
import { Email, Name, UniqueID } from "@shared/contexts";
import { Email, Name, Password, UniqueID } from "@shared/contexts";
import { AuthUser, IAuthUserProps } from "../../domain/entities";
import { IAuthContext } from "../Auth.context";
import {
@ -29,7 +29,11 @@ class UserMapper
const props: IAuthUserProps = {
name: this.mapsValue(source, "name", Name.create),
email: this.mapsValue(source, "email", Email.create),
hashed_password: source.password,
hashed_password: this.mapsValue(
source,
"password",
Password.createFromHashedText,
),
};
const id = this.mapsValue(source, "id", UniqueID.create);

View File

@ -2,7 +2,6 @@ import {
IUseCase,
IUseCaseError,
UseCaseError,
handleUseCaseError,
} from "@/contexts/common/application/useCases";
import { IRepositoryManager } from "@/contexts/common/domain";
import {
@ -67,7 +66,7 @@ export class ListArticlesUseCase
} catch (error: unknown) {
const _error = error as IInfrastructureError;
return Result.fail(
handleUseCaseError(
UseCaseError.create(
UseCaseError.REPOSITORY_ERROR,
"Error al listar el catálogo",
_error,

View File

@ -2,6 +2,10 @@ import { IServerError, ServerError } from "../../domain/errors";
export interface IUseCaseError extends IServerError {}
export type UseCaseErrorDetails = {
path?: string;
} & Record<string, any>;
export class UseCaseError extends ServerError implements IUseCaseError {
public static readonly INVALID_REQUEST_PARAM = "INVALID_REQUEST_PARAM";
public static readonly INVALID_INPUT_DATA = "INVALID_INPUT_DATA";
@ -13,16 +17,8 @@ export class UseCaseError extends ServerError implements IUseCaseError {
public static create(
code: string,
message: string,
details?: Record<string, any>,
details?: UseCaseErrorDetails,
): UseCaseError {
return new UseCaseError(code, message, details);
}
}
export function handleUseCaseError(
code: string,
message: string,
payload?: Record<string, any>,
): IUseCaseError {
return UseCaseError.create(code, message, payload);
}

View File

@ -18,42 +18,28 @@ export class InfrastructureError
public static create(
code: string,
message: string,
payload?: Record<string, any>,
error?: UseCaseError | ValidationError,
): InfrastructureError {
let payload = {};
if (error) {
if (error.name === "ValidationError") {
//Joi error => error.details
payload = (<ValidationError>error).details;
} else {
// UseCaseError
const _error = <UseCaseError>error;
const _payload = Array.isArray(_error.payload)
? _error.payload
: [_error.payload];
payload = _payload.map((item: Record<string, any>) => ({
path: item.path,
message: error.message,
}));
}
}
return new InfrastructureError(code, message, payload);
}
}
function _isJoiError(error: Error) {
return error.name === "ValidationError";
}
export function handleInfrastructureError(
code: string,
message: string,
error?: Error, // UseCaseError | ValidationError
): IInfrastructureError {
let payload = {};
if (error) {
if (_isJoiError(error)) {
//Joi => error.details
payload = (<ValidationError>error).details;
} else {
// UseCaseError
/*const useCaseError = <UseCaseError>error;
if (useCaseError.payload.path) {
const errorItem = {};
errorItem[`${useCaseError.payload.path}`] = useCaseError.message;
payload = {+
errors: [errorItem],
};
}*/
payload = (<UseCaseError>error).payload;
}
}
console.log(payload);
return InfrastructureError.create(code, message, payload);
}

View File

@ -51,7 +51,7 @@ function generateExpressError(
if (item.path) {
return item.path
? {
[String(item.path)]: useCaseError.message,
[String(item.path)]: item.message || useCaseError.message,
}
: {};
} else {

View File

@ -33,4 +33,8 @@ function applyMiddleware(middlewares: string | Array<string>) {
};
}
export { applyMiddleware, registerMiddleware };
function createMiddlewareMap() {
return new Map<string, Express.RequestHandler>();
}
export { applyMiddleware, createMiddlewareMap, registerMiddleware };

View File

@ -73,7 +73,7 @@ export class SequelizeAdapter implements ISequelizeAdapter {
return this._connection.sync(params);
}
public getModel(modelName: string) {
public getModel(modelName: string): any {
if (this.hasModel(modelName)) {
return this._models[modelName];
}

View File

@ -1,10 +1,11 @@
import { Model, Sequelize } from "sequelize";
import { Model, ModelStatic, Sequelize } from "sequelize";
interface ISequelizeModel extends Model {}
interface ISequelizeModel extends InstanceType<ModelStatic<Model>> {}
interface ISequelizeModels {
[prop: string]: ISequelizeModel;
}
interface ISequelizeModel extends Model {
associate?: (connection: Sequelize, models?: ISequelizeModels) => void;
hooks?: (connection: Sequelize) => void;

View File

@ -86,10 +86,6 @@ export abstract class SequelizeRepository<T> implements IRepository<T> {
queryCriteria,
});
if (!_model) {
throw new Error(`[SequelizeRepository] Model ${modelName} not found!`);
}
const args = {
...query,
distinct: true,
@ -110,7 +106,7 @@ export abstract class SequelizeRepository<T> implements IRepository<T> {
value: any,
params: any = {},
): Promise<boolean> {
const _model = this.adapter.getModel(modelName);
const _model = this.adapter.getModel(modelName) as ModelDefined<any, any>;
const where = {};
where[field] = value;

View File

@ -2,19 +2,21 @@ import {
IUseCase,
IUseCaseError,
UseCaseError,
handleUseCaseError,
} from "@/contexts/common/application";
import { IRepositoryManager } from "@/contexts/common/domain";
import { IInfrastructureError } from "@/contexts/common/infrastructure";
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
import {
DomainError,
Email,
ICreateUser_Request_DTO,
IDomainError,
Name,
Password,
Result,
UniqueID,
ensureEmailIsValid,
ensureIdIsValid,
ensureUserEmailIsValid,
} from "@shared/contexts";
import { IUserRepository, User } from "../domain";
import { existsUserByEmail, existsUserByID } from "./userServices";
@ -38,27 +40,20 @@ export class CreateUserUseCase
this._repositoryManager = props.repositoryManager;
}
private getRepositoryByName<T>(name: string) {
return this._repositoryManager.getRepository<T>(name);
}
async execute(request: ICreateUser_Request_DTO) {
const { id, email } = request;
async execute(
request: ICreateUser_Request_DTO,
): Promise<CreateUserResponseOrError> {
const userDTO = request;
const userRepository = this.getRepositoryByName<IUserRepository>("User");
const userRepository = this._getUserRepository();
// Validaciones de datos
const userIdOrError = ensureIdIsValid(userDTO.id);
const userIdOrError = ensureIdIsValid(id);
if (userIdOrError.isFailure) {
const message = userIdOrError.error.message; //`User ID ${userDTO.id} is not valid`;
return Result.fail(
handleUseCaseError(
UseCaseError.INVALID_INPUT_DATA,
"User ID is not valid",
userIdOrError.error,
),
UseCaseError.create(UseCaseError.INVALID_INPUT_DATA, message, [
{ path: "id" },
]),
);
}
@ -68,19 +63,18 @@ export class CreateUserUseCase
userRepository,
);
if (idExists) {
const message = `Another user with ID ${id} exists`;
return Result.fail(
handleUseCaseError(
UseCaseError.RESOURCE_ALREADY_EXITS,
`Another user with ID ${userDTO.id} exists`,
userIdOrError.error,
),
UseCaseError.create(UseCaseError.RESOURCE_ALREADY_EXITS, message, {
path: "id",
}),
);
}
const emailOrError = ensureUserEmailIsValid(userDTO.email);
const emailOrError = ensureEmailIsValid(email);
if (emailOrError.isFailure) {
return Result.fail(
handleUseCaseError(
UseCaseError.create(
UseCaseError.INVALID_INPUT_DATA,
"Email or password is not valid",
emailOrError.error,
@ -96,17 +90,17 @@ export class CreateUserUseCase
if (emailExists) {
return Result.fail(
handleUseCaseError(
UseCaseError.create(
UseCaseError.RESOURCE_ALREADY_EXITS,
`Another user with email ${userDTO.email} exists`,
userIdOrError.error,
`Another user with email ${email} exists`,
{ path: "email" },
),
);
}
// Crear user
const userOrError = this.tryCreateUserInstance(
userDTO,
const userOrError = this._tryCreateUserInstance(
request,
userIdOrError.object,
);
@ -116,42 +110,50 @@ export class CreateUserUseCase
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,
case User.ERROR_USER_WITHOUT_NAME:
return Result.fail(
UseCaseError.create(
UseCaseError.INVALID_INPUT_DATA,
"El usuario debe tener un nombre.",
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,
return Result.fail(
UseCaseError.create(
UseCaseError.INVALID_INPUT_DATA,
"El usuario tiene algún dato erróneo.",
domainError,
),
);
break;
default:
errorCode = UseCaseError.UNEXCEPTED_ERROR;
message = domainError.message;
return handleUseCaseError(errorCode, message, domainError);
return Result.fail(
UseCaseError.create(errorCode, message, domainError),
);
break;
}
}
return this.saveUser(userOrError.object);
return this._saveUser(userOrError.object);
}
private async saveUser(user: User) {
private async _saveUser(user: User) {
// Guardar el contacto
const transaction = this._adapter.startTransaction();
const userRepoBuilder = this.getRepositoryByName<IUserRepository>("User");
const userRepository = this._getUserRepository();
let userRepo: IUserRepository;
try {
await transaction.complete(async (t) => {
userRepo = userRepoBuilder({ transaction: t });
userRepo = userRepository({ transaction: t });
await userRepo.create(user);
});
@ -159,16 +161,12 @@ export class CreateUserUseCase
} catch (error: unknown) {
const _error = error as IInfrastructureError;
return Result.fail(
handleUseCaseError(
UseCaseError.REPOSITORY_ERROR,
"Error al guardar el usuario",
_error,
),
UseCaseError.create(UseCaseError.REPOSITORY_ERROR, _error.message),
);
}
}
private tryCreateUserInstance(
private _tryCreateUserInstance(
userDTO: ICreateUser_Request_DTO,
userId: UniqueID,
): Result<User, IDomainError> {
@ -182,12 +180,22 @@ export class CreateUserUseCase
return Result.fail(emailOrError.error);
}
const passwordOrError = Password.createFromPlainText(userDTO.password);
if (passwordOrError.isFailure) {
return Result.fail(passwordOrError.error);
}
return User.create(
{
name: nameOrError.object,
email: emailOrError.object,
password: passwordOrError.object,
},
userId,
);
}
private _getUserRepository() {
return this._repositoryManager.getRepository<IUserRepository>("User");
}
}

View File

@ -3,7 +3,6 @@ import {
IUseCaseError,
IUseCaseRequest,
UseCaseError,
handleUseCaseError,
} from "@/contexts/common/application/useCases";
import { IRepositoryManager } from "@/contexts/common/domain";
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
@ -55,7 +54,7 @@ export class DeleteUserUseCase
} catch (error: unknown) {
//const _error = error as IInfrastructureError;
return Result.fail(
handleUseCaseError(
UseCaseError.create(
UseCaseError.REPOSITORY_ERROR,
"Error al eliminar el usuario",
),

View File

@ -3,7 +3,6 @@ import {
IUseCaseError,
IUseCaseRequest,
UseCaseError,
handleUseCaseError,
} from "@/contexts/common/application/useCases";
import { IRepositoryManager } from "@/contexts/common/domain";
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
@ -64,7 +63,7 @@ export class GetUserUseCase
if (!user) {
return Result.fail(
handleUseCaseError(UseCaseError.NOT_FOUND_ERROR, "User not found"),
UseCaseError.create(UseCaseError.NOT_FOUND_ERROR, "User not found"),
);
}
@ -72,7 +71,7 @@ export class GetUserUseCase
} catch (error: unknown) {
const _error = error as IInfrastructureError;
return Result.fail(
handleUseCaseError(
UseCaseError.create(
UseCaseError.REPOSITORY_ERROR,
"Error al consultar el usuario",
_error,

View File

@ -2,7 +2,6 @@ import {
IUseCase,
IUseCaseError,
UseCaseError,
handleUseCaseError,
} from "@/contexts/common/application/useCases";
import { IRepositoryManager } from "@/contexts/common/domain";
import {
@ -65,7 +64,7 @@ export class ListUsersUseCase
} catch (error: unknown) {
const _error = error as IInfrastructureError;
return Result.fail(
handleUseCaseError(
UseCaseError.create(
UseCaseError.REPOSITORY_ERROR,
"Error al listar los usurios",
_error,

View File

@ -3,7 +3,6 @@ import {
IUseCaseError,
IUseCaseRequest,
UseCaseError,
handleUseCaseError,
} from "@/contexts/common/application/useCases";
import { IRepositoryManager } from "@/contexts/common/domain";
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
@ -15,6 +14,7 @@ import {
DomainError,
IDomainError,
IUpdateUser_DTO,
IUpdateUser_Request_DTO,
Note,
PostalCode,
Province,
@ -27,6 +27,7 @@ import {
UserName,
UserPhone,
UserTIN,
ensureIdIsValid,
} from "@shared/contexts";
import { IInfrastructureError } from "@/contexts/common/infrastructure";
@ -40,10 +41,11 @@ import {
UserAddressType,
UserShippingAddress,
} from "../domain";
import { existsUserByID } from "./userServices";
export interface IUpdateUserUseCaseRequest extends IUseCaseRequest {
id: UniqueID;
userDTO: IUpdateUser_DTO;
userDTO: IUpdateUser_Request_DTO;
}
export type UpdateUserResponseOrError =
@ -73,13 +75,30 @@ export class UpdateUserUseCase
request: IUpdateUserUseCaseRequest,
): Promise<UpdateUserResponseOrError> {
const { id, userDTO } = request;
const userRepository = this.getRepositoryByName<IUserRepository>("User");
// Validaciones de datos
const userIdOrError = ensureIdIsValid(userDTO.id);
if (userIdOrError.isFailure) {
return Result.fail(
UseCaseError.create(
UseCaseError.INVALID_INPUT_DATA,
"User ID is not valid",
userIdOrError.error,
),
);
}
// Comprobar que existe el user
const idExists = await this._findUserID(id);
const idExists = await existsUserByID(
userIdOrError.object,
this._adapter,
userRepository,
);
if (!idExists) {
const message = `User with ID ${id.toString()} not found`;
return Result.fail<IUseCaseError>(
handleUseCaseError(UseCaseError.NOT_FOUND_ERROR, message, [
UseCaseError.create(UseCaseError.NOT_FOUND_ERROR, message, [
{ path: "id" },
]),
);
@ -117,7 +136,7 @@ export class UpdateUserUseCase
}
return Result.fail<IUseCaseError>(
handleUseCaseError(errorCode, message, payload),
UseCaseError.create(errorCode, message, payload),
);
}
@ -326,7 +345,7 @@ export class UpdateUserUseCase
} catch (error: unknown) {
const _error = error as IInfrastructureError;
return Result.fail(
handleUseCaseError(
UseCaseError.create(
UseCaseError.REPOSITORY_ERROR,
"Error al guardar el usuario",
_error,

View File

@ -0,0 +1,9 @@
import { CompositeSpecification } from "@/contexts/common/domain";
import { User } from "./User";
export class UserHasName extends CompositeSpecification<User> {
public isSatisfiedBy(candidate: User): boolean {
return !candidate.name.isEmpty();
}
}

View File

@ -5,22 +5,25 @@ import {
Email,
IDomainError,
Name,
Password,
Result,
UniqueID,
handleDomainError,
} from "@shared/contexts";
import { UserHasName } from "./User.specifications";
export interface IUserProps {
name: Name;
email: Email;
password?: string;
hashed_password?: string;
password: Password;
}
//type ISecuredUserProps = Omit<IUserProps, "password">;
export interface IUser {
id: UniqueID;
name: Name;
email: Email;
hashed_password: string;
isUser: boolean;
isAdmin: boolean;
@ -28,33 +31,29 @@ export interface IUser {
}
export class User extends AggregateRoot<IUserProps> implements IUser {
static readonly ERROR_USER_WITHOUT_NAME = "ERROR_USER_WITHOUT_NAME";
public static create(
props: IUserProps,
id?: UniqueID,
): Result<User, IDomainError> {
//const isNew = !!id === false;
// Se hace en el constructor de la Entidad
/* if (isNew) {
id = UniqueEntityID.create();
}*/
const user = new User(props, id);
// Reglas de negocio / validaciones
const isValidUser = new UserHasName().isSatisfiedBy(user);
if (!isValidUser) {
return Result.fail(handleDomainError(User.ERROR_USER_WITHOUT_NAME));
}
return Result.ok<User>(user);
}
public static async hashPassword(password): Promise<string> {
return hashPassword(password, await genSalt());
return Password.hashPassword(password);
}
private _hashed_password: string;
private constructor(props: IUserProps, id?: UniqueID) {
super({ ...props, password: "", hashed_password: "" }, id);
this._protectPassword(props);
}
private _password: string;
get name(): Name {
return this.props.name;
@ -65,7 +64,7 @@ export class User extends AggregateRoot<IUserProps> implements IUser {
}
get hashed_password(): string {
return this._hashed_password;
return this._password;
}
get isUser(): boolean {
@ -77,17 +76,7 @@ export class User extends AggregateRoot<IUserProps> implements IUser {
}
public verifyPassword(candidatePassword: string): boolean {
return bCrypt.compareSync(candidatePassword, this._hashed_password!);
}
private async _protectPassword(props: IUserProps) {
const { password, hashed_password } = props;
if (password) {
this._hashed_password = await User.hashPassword(password);
} else {
this._hashed_password = hashed_password!;
}
return bCrypt.compareSync(candidatePassword, this._password!);
}
}
@ -108,5 +97,3 @@ async function hashPassword(password: string, salt: string): Promise<string> {
});
});
}
User.hashPassword("123456").then((value) => console.log(value));

View File

@ -3,6 +3,9 @@ import { Email, ICollection, IQueryCriteria, UniqueID } from "@shared/contexts";
import { User } from "../entities";
export interface IUserRepository extends IRepository<any> {
create(user: User): Promise<void>;
update(user: User): Promise<void>;
getById(id: UniqueID): Promise<User | null>;
findUserByEmail(email: Email): Promise<User | null>;
findAll(queryCriteria?: IQueryCriteria): Promise<ICollection<User>>;

View File

@ -30,6 +30,20 @@ export class UserRepository
this.mapper = mapper;
}
public async create(user: User): Promise<void> {
const userData = this.mapper.mapToPersistence(user);
await this._save("User_Model", user.id, userData);
}
public async update(user: User): Promise<void> {
const userData = this.mapper.mapToPersistence(user);
// borrando y luego creando
await this.removeById(user.id, true);
await this._save("User_Model", user.id, userData, {});
}
public async getById(id: UniqueID): Promise<User | null> {
const rawUser: any = await this._getById("User_Model", id);

View File

@ -3,13 +3,9 @@ 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 { CreateUserUseCase } from "@/contexts/users/application";
import { User } from "@/contexts/users/domain";
import {
ICreateUser_Request_DTO,
@ -48,7 +44,7 @@ export class CreateUserController extends ExpressController {
if (userDTOOrError.isFailure) {
const errorMessage = "User data not valid";
const infraError = handleInfrastructureError(
const infraError = InfrastructureError.create(
InfrastructureError.INVALID_INPUT_DATA,
errorMessage,
userDTOOrError.error,
@ -57,9 +53,7 @@ export class CreateUserController extends ExpressController {
}
// Llamar al caso de uso
const result: CreateUserResponseOrError = await this.useCase.execute(
userDTO,
);
const result = await this.useCase.execute(userDTO);
if (result.isFailure) {
return this._handleExecuteError(result.error);
@ -82,7 +76,7 @@ export class CreateUserController extends ExpressController {
switch (error.code) {
case UseCaseError.INVALID_INPUT_DATA:
errorMessage = "User data not valid";
infraError = handleInfrastructureError(
infraError = InfrastructureError.create(
InfrastructureError.INVALID_INPUT_DATA,
errorMessage,
error,
@ -93,18 +87,28 @@ export class CreateUserController extends ExpressController {
case UseCaseError.RESOURCE_ALREADY_EXITS:
errorMessage = "User already exists";
infraError = handleInfrastructureError(
InfrastructureError.INVALID_INPUT_DATA,
infraError = InfrastructureError.create(
InfrastructureError.RESOURCE_ALREADY_REGISTERED,
errorMessage,
error,
);
return this.conflictError(error.message, error);
return this.conflictError(errorMessage, infraError);
break;
case UseCaseError.REPOSITORY_ERROR:
errorMessage = "Error saving user";
infraError = InfrastructureError.create(
InfrastructureError.UNEXCEPTED_ERROR,
errorMessage,
error,
);
return this.conflictError(errorMessage, infraError);
break;
case UseCaseError.UNEXCEPTED_ERROR:
errorMessage = error.message;
infraError = handleInfrastructureError(
infraError = InfrastructureError.create(
InfrastructureError.UNEXCEPTED_ERROR,
errorMessage,
error,

View File

@ -6,7 +6,6 @@ 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";
@ -33,7 +32,7 @@ export class DeleteUserController extends ExpressController {
const userIdOrError = ensureIdIsValid(userId);
if (userIdOrError.isFailure) {
const errorMessage = "User ID is not valid";
const infraError = handleInfrastructureError(
const infraError = InfrastructureError.create(
InfrastructureError.INVALID_INPUT_DATA,
errorMessage,
userIdOrError.error,
@ -63,7 +62,7 @@ export class DeleteUserController extends ExpressController {
case UseCaseError.NOT_FOUND_ERROR:
errorMessage = "User not found";
infraError = handleInfrastructureError(
infraError = InfrastructureError.create(
InfrastructureError.RESOURCE_NOT_FOUND_ERROR,
errorMessage,
error,
@ -75,7 +74,7 @@ export class DeleteUserController extends ExpressController {
case UseCaseError.UNEXCEPTED_ERROR:
errorMessage = error.message;
infraError = handleInfrastructureError(
infraError = InfrastructureError.create(
InfrastructureError.UNEXCEPTED_ERROR,
errorMessage,
error,

View File

@ -11,7 +11,6 @@ import { IServerError } from "@/contexts/common/domain/errors";
import {
IInfrastructureError,
InfrastructureError,
handleInfrastructureError,
} from "@/contexts/common/infrastructure";
import { IUserContext } from "../../../User.context";
import { IGetUserPresenter } from "./presenter";
@ -43,7 +42,7 @@ export class GetUserController extends ExpressController {
const userIdOrError = ensureIdIsValid(userId);
if (userIdOrError.isFailure) {
const errorMessage = "User ID is not valid";
const infraError = handleInfrastructureError(
const infraError = InfrastructureError.create(
InfrastructureError.INVALID_INPUT_DATA,
errorMessage,
userIdOrError.error,
@ -78,7 +77,7 @@ export class GetUserController extends ExpressController {
case UseCaseError.NOT_FOUND_ERROR:
errorMessage = "User not found";
infraError = handleInfrastructureError(
infraError = InfrastructureError.create(
InfrastructureError.RESOURCE_NOT_FOUND_ERROR,
errorMessage,
error,
@ -90,7 +89,7 @@ export class GetUserController extends ExpressController {
case UseCaseError.UNEXCEPTED_ERROR:
errorMessage = error.message;
infraError = handleInfrastructureError(
infraError = InfrastructureError.create(
InfrastructureError.UNEXCEPTED_ERROR,
errorMessage,
error,

View File

@ -3,7 +3,6 @@ import { IServerError } from "@/contexts/common/domain/errors";
import {
IInfrastructureError,
InfrastructureError,
handleInfrastructureError,
} from "@/contexts/common/infrastructure";
import { ExpressController } from "@/contexts/common/infrastructure/express";
import {
@ -49,7 +48,7 @@ export class UpdateUserController extends ExpressController {
const userIdOrError = ensureIdIsValid(userId);
if (userIdOrError.isFailure) {
const errorMessage = "User ID is not valid";
const infraError = handleInfrastructureError(
const infraError = InfrastructureError.create(
InfrastructureError.INVALID_INPUT_DATA,
errorMessage,
userIdOrError.error,
@ -62,7 +61,7 @@ export class UpdateUserController extends ExpressController {
if (userDTOOrError.isFailure) {
const errorMessage = "User data not valid";
const infraError = handleInfrastructureError(
const infraError = InfrastructureError.create(
InfrastructureError.INVALID_INPUT_DATA,
errorMessage,
userDTOOrError.error,
@ -98,7 +97,7 @@ export class UpdateUserController extends ExpressController {
case UseCaseError.NOT_FOUND_ERROR:
errorMessage = "User not found";
infraError = handleInfrastructureError(
infraError = InfrastructureError.create(
InfrastructureError.RESOURCE_NOT_FOUND_ERROR,
errorMessage,
error,
@ -110,7 +109,7 @@ export class UpdateUserController extends ExpressController {
case UseCaseError.INVALID_INPUT_DATA:
errorMessage = "User data not valid";
infraError = handleInfrastructureError(
infraError = InfrastructureError.create(
InfrastructureError.INVALID_INPUT_DATA,
"Datos del cliente a actulizar erróneos",
error,
@ -121,7 +120,7 @@ export class UpdateUserController extends ExpressController {
case UseCaseError.UNEXCEPTED_ERROR:
errorMessage = error.message;
infraError = handleInfrastructureError(
infraError = InfrastructureError.create(
InfrastructureError.UNEXCEPTED_ERROR,
errorMessage,
error,

View File

@ -2,7 +2,7 @@ import {
ISequelizeMapper,
SequelizeMapper,
} from "@/contexts/common/infrastructure";
import { Email, Name, UniqueID } from "@shared/contexts";
import { Email, Name, Password, UniqueID } from "@shared/contexts";
import { IUserProps, User } from "../../domain";
import { IUserContext } from "../User.context";
import { TCreationUser_Attributes, User_Model } from "../sequelize/user.model";
@ -22,7 +22,11 @@ class UserMapper
const props: IUserProps = {
name: this.mapsValue(source, "name", Name.create),
email: this.mapsValue(source, "email", Email.create),
hashed_password: source.password,
password: this.mapsValue(
source,
"password",
Password.createFromHashedText,
),
};
const id = this.mapsValue(source, "id", UniqueID.create);
@ -43,7 +47,7 @@ class UserMapper
id: source.id.toPrimitive(),
name: source.name.toPrimitive(),
email: source.email.toPrimitive(),
password: source.hashed_password,
password: "",
};
}
}

View File

@ -0,0 +1,8 @@
import { RepositoryManager } from "@/contexts/common/domain";
import { createSequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
export const createContextMiddleware = () => ({
adapter: createSequelizeAdapter(),
repositoryManager: RepositoryManager.getInstance(),
services: {},
});

View File

@ -1,9 +1,9 @@
import { AuthRouter } from "@/contexts/auth";
import { CatalogRouter } from "@/contexts/catalog";
import { RepositoryManager } from "@/contexts/common/domain";
import { createSequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
import { createMiddlewareMap } from "@/contexts/common/infrastructure/express";
import { UserRouter } from "@/contexts/users";
import Express from "express";
import { createContextMiddleware } from "./context.middleware";
export const v1Routes = () => {
const routes = Express.Router({ mergeParams: true });
@ -12,22 +12,14 @@ export const v1Routes = () => {
res.send("Hello world!");
});
//v1Routes.use("/auth", authRoutes);
//v1Routes.use("/catalog", catalogRoutes);
routes.use(
(
req: Express.Request,
res: Express.Response,
next: Express.NextFunction,
) => {
res.locals["context"] = {
adapter: createSequelizeAdapter(),
repositoryManager: RepositoryManager.getInstance(),
services: {},
};
res.locals["middlewares"] = new Map<string, Express.RequestHandler>();
res.locals["context"] = createContextMiddleware();
res.locals["middlewares"] = createMiddlewareMap();
return next();
},

View File

@ -0,0 +1,110 @@
import bCrypt from "bcryptjs";
import Joi from "joi";
import { UndefinedOr } from "../../../../utilities";
import { RuleValidator } from "../RuleValidator";
import { DomainError, handleDomainError } from "../errors";
import { Result } from "./Result";
import {
IStringValueObjectOptions,
StringValueObject,
} from "./StringValueObject";
export interface IPasswordOptions extends IStringValueObjectOptions {}
export class Password extends StringValueObject {
private static readonly MIN_LENGTH = 4;
private static readonly MAX_LENGTH = 255;
protected static validate(
value: UndefinedOr<string>,
options: IPasswordOptions,
) {
const rule = Joi.string()
.allow(null)
.allow("")
.default("")
.trim()
.min(Password.MIN_LENGTH)
.max(Password.MAX_LENGTH)
.label(options.label ? options.label : "value");
return RuleValidator.validate<string>(rule, value);
}
public static createFromHashedText(
value: UndefinedOr<string>,
options: IPasswordOptions = {},
) {
const _options = {
label: "password",
...options,
};
const validationResult = Password.validate(value, _options);
if (validationResult.isFailure) {
return Result.fail(
handleDomainError(
DomainError.INVALID_INPUT_DATA,
validationResult.error.message,
_options,
),
);
}
return Result.ok(new Password(validationResult.object));
}
public static async createFromPlainText(
value: UndefinedOr<string>,
options: IPasswordOptions = {},
) {
const _options = {
label: "password",
...options,
};
const validationResult = Password.validate(value, _options);
if (validationResult.isFailure) {
return Result.fail(
handleDomainError(
DomainError.INVALID_INPUT_DATA,
validationResult.error.message,
_options,
),
);
}
return Result.ok(
new Password(await Password.hashPassword(validationResult.object)),
);
}
public static async hashPassword(plainText: string): Promise<string> {
return hashPassword(plainText, await genSalt());
}
public verifyPassword(candidatePassword: string): boolean {
return bCrypt.compareSync(candidatePassword, this.value!);
}
}
async function genSalt(rounds = 10): Promise<string> {
return new Promise((resolve, reject) => {
bCrypt.genSalt(rounds, function (err, salt) {
if (err) return reject(err);
return resolve(salt);
});
});
}
async function hashPassword(password: string, salt: string): Promise<string> {
return new Promise((resolve, reject) => {
bCrypt.hash(password, salt, function (err, hash) {
if (err) return reject(err);
return resolve(hash);
});
});
}

View File

@ -11,6 +11,7 @@ export * from "./MoneyValue";
export * from "./Name";
export * from "./Note";
export * from "./NullableValueObject";
export * from "./Password";
export * from "./Percentage";
export * from "./Phone";
export * from "./Quantity";

View File

@ -5,15 +5,17 @@ export interface ICreateUser_Request_DTO {
id: string;
name: string;
email: string;
password: string;
}
export function ensureCreateUser_Request_DTOIsValid(
userDTO: ICreateUser_Request_DTO,
): Result<boolean, Error> {
) {
const schema = Joi.object({
id: Joi.string(),
name: Joi.string(),
email: Joi.string(),
password: Joi.string(),
}).unknown(true);
const result = RuleValidator.validate<ICreateUser_Request_DTO>(