.
This commit is contained in:
parent
173ec9e8d8
commit
38ead39cb3
@ -22,6 +22,7 @@
|
|||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/express-session": "^1.18.0",
|
"@types/express-session": "^1.18.0",
|
||||||
"@types/glob": "^8.1.0",
|
"@types/glob": "^8.1.0",
|
||||||
|
"@types/http-status": "^1.1.2",
|
||||||
"@types/jest": "^29.5.6",
|
"@types/jest": "^29.5.6",
|
||||||
"@types/jsonwebtoken": "^9.0.6",
|
"@types/jsonwebtoken": "^9.0.6",
|
||||||
"@types/luxon": "^3.3.1",
|
"@types/luxon": "^3.3.1",
|
||||||
@ -64,6 +65,7 @@
|
|||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-openapi-validator": "^5.0.4",
|
"express-openapi-validator": "^5.0.4",
|
||||||
"helmet": "^7.0.0",
|
"helmet": "^7.0.0",
|
||||||
|
"http-status": "^1.7.4",
|
||||||
"joi": "^17.12.3",
|
"joi": "^17.12.3",
|
||||||
"joi-phone-number": "^5.1.1",
|
"joi-phone-number": "^5.1.1",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
|||||||
@ -4,7 +4,7 @@ module.exports = {
|
|||||||
"9d6c903873c341816995a8be0355c6f0d6d471fc6aedacf50790e9b1e49c45b3",
|
"9d6c903873c341816995a8be0355c6f0d6d471fc6aedacf50790e9b1e49c45b3",
|
||||||
refresh_secret_key:
|
refresh_secret_key:
|
||||||
"3972dc40c69327b65352ed097419213b0b75561169dba562410b85660bb1f305",
|
"3972dc40c69327b65352ed097419213b0b75561169dba562410b85660bb1f305",
|
||||||
token_expiration: "15m",
|
token_expiration: "5m",
|
||||||
refresh_token_expiration: "7d",
|
refresh_token_expiration: "7d",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { IRepositoryManager } from "@/contexts/common/domain";
|
|||||||
import { IInfrastructureError } from "@/contexts/common/infrastructure";
|
import { IInfrastructureError } from "@/contexts/common/infrastructure";
|
||||||
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||||
import { Result, ensureUserEmailIsValid } from "@shared/contexts";
|
import { Result, ensureUserEmailIsValid } from "@shared/contexts";
|
||||||
import { User } from "../domain";
|
import { AuthUser } from "../domain";
|
||||||
import { findUserByEmail } from "./authServices";
|
import { findUserByEmail } from "./authServices";
|
||||||
|
|
||||||
export interface FindUserByEmailRequest extends IUseCaseRequest {
|
export interface FindUserByEmailRequest extends IUseCaseRequest {
|
||||||
@ -18,7 +18,7 @@ export interface FindUserByEmailRequest extends IUseCaseRequest {
|
|||||||
|
|
||||||
export type FindUserByEmailResponseOrError =
|
export type FindUserByEmailResponseOrError =
|
||||||
| Result<never, IUseCaseError>
|
| Result<never, IUseCaseError>
|
||||||
| Result<User, never>;
|
| Result<AuthUser, never>;
|
||||||
|
|
||||||
export class FindUserByEmailUseCase
|
export class FindUserByEmailUseCase
|
||||||
implements
|
implements
|
||||||
@ -73,7 +73,7 @@ export class FindUserByEmailUseCase
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Result.ok<User>(user);
|
return Result.ok<AuthUser>(user);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const _error = error as IInfrastructureError;
|
const _error = error as IInfrastructureError;
|
||||||
return Result.fail(
|
return Result.fail(
|
||||||
|
|||||||
@ -8,12 +8,12 @@ import { IRepositoryManager } from "@/contexts/common/domain";
|
|||||||
import { IInfrastructureError } from "@/contexts/common/infrastructure";
|
import { IInfrastructureError } from "@/contexts/common/infrastructure";
|
||||||
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||||
import { ILogin_DTO, Result, ensureUserEmailIsValid } from "@shared/contexts";
|
import { ILogin_DTO, Result, ensureUserEmailIsValid } from "@shared/contexts";
|
||||||
import { User } from "../domain";
|
import { AuthUser } from "../domain";
|
||||||
import { findUserByEmail } from "./authServices";
|
import { findUserByEmail } from "./authServices";
|
||||||
|
|
||||||
export type LoginResponseOrError =
|
export type LoginResponseOrError =
|
||||||
| Result<never, IUseCaseError>
|
| Result<never, IUseCaseError>
|
||||||
| Result<User, never>;
|
| Result<AuthUser, never>;
|
||||||
|
|
||||||
export class LoginUseCase
|
export class LoginUseCase
|
||||||
implements IUseCase<ILogin_DTO, Promise<LoginResponseOrError>>
|
implements IUseCase<ILogin_DTO, Promise<LoginResponseOrError>>
|
||||||
@ -64,7 +64,7 @@ export class LoginUseCase
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Result.ok<User>(user);
|
return Result.ok<AuthUser>(user);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const _error = error as IInfrastructureError;
|
const _error = error as IInfrastructureError;
|
||||||
return Result.fail(
|
return Result.fail(
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { IAdapter, RepositoryBuilder } from "@/contexts/common/domain";
|
import { IAdapter, RepositoryBuilder } from "@/contexts/common/domain";
|
||||||
import { Email } from "@shared/contexts";
|
import { Email } from "@shared/contexts";
|
||||||
import { User } from "../domain";
|
import { AuthUser } from "../domain";
|
||||||
import { IAuthRepository } from "../domain/repository";
|
import { IAuthRepository } from "../domain/repository";
|
||||||
|
|
||||||
export const findUserByEmail = async (
|
export const findUserByEmail = async (
|
||||||
email: Email,
|
email: Email,
|
||||||
adapter: IAdapter,
|
adapter: IAdapter,
|
||||||
repository: RepositoryBuilder<IAuthRepository>,
|
repository: RepositoryBuilder<IAuthRepository>,
|
||||||
): Promise<User | null> => {
|
): Promise<AuthUser | null> => {
|
||||||
const user = await adapter
|
const user = await adapter
|
||||||
.startTransaction()
|
.startTransaction()
|
||||||
.complete(async (t) =>
|
.complete(async (t) =>
|
||||||
|
|||||||
115
server/src/contexts/auth/domain/entities/AuthUser.ts
Normal file
115
server/src/contexts/auth/domain/entities/AuthUser.ts
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import bCrypt from "bcryptjs";
|
||||||
|
|
||||||
|
import {
|
||||||
|
AggregateRoot,
|
||||||
|
Email,
|
||||||
|
IDomainError,
|
||||||
|
Name,
|
||||||
|
Result,
|
||||||
|
UniqueID,
|
||||||
|
} from "@shared/contexts";
|
||||||
|
|
||||||
|
export interface IAuthUserProps {
|
||||||
|
name: Name;
|
||||||
|
email: Email;
|
||||||
|
password?: string;
|
||||||
|
hashed_password?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAuthUser {
|
||||||
|
id: UniqueID;
|
||||||
|
name: Name;
|
||||||
|
email: Email;
|
||||||
|
hashed_password: string;
|
||||||
|
isUser: boolean;
|
||||||
|
isAdmin: boolean;
|
||||||
|
|
||||||
|
verifyPassword: (candidatePassword: string) => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AuthUser
|
||||||
|
extends AggregateRoot<IAuthUserProps>
|
||||||
|
implements IAuthUser
|
||||||
|
{
|
||||||
|
public static create(
|
||||||
|
props: IAuthUserProps,
|
||||||
|
id?: UniqueID,
|
||||||
|
): Result<AuthUser, IDomainError> {
|
||||||
|
//const isNew = !!id === false;
|
||||||
|
|
||||||
|
// Se hace en el constructor de la Entidad
|
||||||
|
/* if (isNew) {
|
||||||
|
id = UniqueEntityID.create();
|
||||||
|
}*/
|
||||||
|
|
||||||
|
const user = new AuthUser(props, id);
|
||||||
|
|
||||||
|
return Result.ok<AuthUser>(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async hashPassword(password): Promise<string> {
|
||||||
|
return hashPassword(password, await genSalt());
|
||||||
|
}
|
||||||
|
|
||||||
|
private _hashed_password: string;
|
||||||
|
|
||||||
|
private constructor(props: IAuthUserProps, id?: UniqueID) {
|
||||||
|
super({ ...props, password: "", hashed_password: "" }, id);
|
||||||
|
|
||||||
|
this._protectPassword(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
get name(): Name {
|
||||||
|
return this.props.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
get email(): Email {
|
||||||
|
return this.props.email;
|
||||||
|
}
|
||||||
|
|
||||||
|
get hashed_password(): string {
|
||||||
|
return this._hashed_password;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isUser(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isAdmin(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public verifyPassword(candidatePassword: string): boolean {
|
||||||
|
return bCrypt.compareSync(candidatePassword, this._hashed_password!);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _protectPassword(props: IAuthUserProps) {
|
||||||
|
const { password, hashed_password } = props;
|
||||||
|
|
||||||
|
if (password) {
|
||||||
|
this._hashed_password = await AuthUser.hashPassword(password);
|
||||||
|
} else {
|
||||||
|
this._hashed_password = hashed_password!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthUser.hashPassword("123456").then((value) => console.log(value));
|
||||||
@ -1 +1 @@
|
|||||||
export * from "./User";
|
export * from "./AuthUser";
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { IRepository } from "@/contexts/common/domain";
|
import { IRepository } from "@/contexts/common/domain";
|
||||||
import { Email } from "@shared/contexts";
|
import { Email } from "@shared/contexts";
|
||||||
import { User } from "../entities";
|
import { AuthUser } from "../entities";
|
||||||
|
|
||||||
export interface IAuthRepository extends IRepository<any> {
|
export interface IAuthRepository extends IRepository<any> {
|
||||||
findUserByEmail(email: Email): Promise<User | null>;
|
findUserByEmail(email: Email): Promise<AuthUser | null>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import {
|
|||||||
} from "@/contexts/common/infrastructure/sequelize";
|
} from "@/contexts/common/infrastructure/sequelize";
|
||||||
import { Email, ICollection, IQueryCriteria, UniqueID } from "@shared/contexts";
|
import { Email, ICollection, IQueryCriteria, UniqueID } from "@shared/contexts";
|
||||||
import { Transaction } from "sequelize";
|
import { Transaction } from "sequelize";
|
||||||
import { User } from "../domain/entities";
|
import { AuthUser } from "../domain/entities";
|
||||||
import { IAuthRepository } from "../domain/repository/AuthRepository.interface";
|
import { IAuthRepository } from "../domain/repository/AuthRepository.interface";
|
||||||
import { IUserMapper, createUserMapper } from "./mappers/user.mapper";
|
import { IUserMapper, createUserMapper } from "./mappers/user.mapper";
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ export type QueryParams = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class AuthRepository
|
export class AuthRepository
|
||||||
extends SequelizeRepository<User>
|
extends SequelizeRepository<AuthUser>
|
||||||
implements IAuthRepository
|
implements IAuthRepository
|
||||||
{
|
{
|
||||||
protected mapper: IUserMapper;
|
protected mapper: IUserMapper;
|
||||||
@ -31,8 +31,8 @@ export class AuthRepository
|
|||||||
this.mapper = mapper;
|
this.mapper = mapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getById(id: UniqueID): Promise<User | null> {
|
public async getById(id: UniqueID): Promise<AuthUser | null> {
|
||||||
const rawUser: any = await this._getById("User_Model", id);
|
const rawUser: any = await this._getById("AuthUser_Model", id);
|
||||||
|
|
||||||
if (!rawUser === true) {
|
if (!rawUser === true) {
|
||||||
return null;
|
return null;
|
||||||
@ -41,9 +41,9 @@ export class AuthRepository
|
|||||||
return this.mapper.mapToDomain(rawUser);
|
return this.mapper.mapToDomain(rawUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async findUserByEmail(email: Email): Promise<User | null> {
|
public async findUserByEmail(email: Email): Promise<AuthUser | null> {
|
||||||
const rawUser: any = await this._getBy(
|
const rawUser: any = await this._getBy(
|
||||||
"User_Model",
|
"AuthUser_Model",
|
||||||
"email",
|
"email",
|
||||||
email.toPrimitive(),
|
email.toPrimitive(),
|
||||||
);
|
);
|
||||||
@ -59,7 +59,7 @@ export class AuthRepository
|
|||||||
queryCriteria?: IQueryCriteria,
|
queryCriteria?: IQueryCriteria,
|
||||||
): Promise<ICollection<any>> {
|
): Promise<ICollection<any>> {
|
||||||
const { rows, count } = await this._findAll(
|
const { rows, count } = await this._findAll(
|
||||||
"User_Model",
|
"AuthUser_Model",
|
||||||
queryCriteria,
|
queryCriteria,
|
||||||
/*{
|
/*{
|
||||||
include: [], // esto es para quitar las asociaciones al hacer la consulta
|
include: [], // esto es para quitar las asociaciones al hacer la consulta
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
// Import the necessary packages and modules
|
// Import the necessary packages and modules
|
||||||
import { User } from "@/contexts/auth/domain";
|
import { AuthUser } from "@/contexts/auth/domain";
|
||||||
import { IServerError } from "@/contexts/common/domain/errors";
|
import { IServerError } from "@/contexts/common/domain/errors";
|
||||||
import { ExpressController } from "@/contexts/common/infrastructure/express";
|
import { ExpressController } from "@/contexts/common/infrastructure/express";
|
||||||
import passport from "passport";
|
import passport from "passport";
|
||||||
@ -42,7 +42,7 @@ export class AuthenticateController extends ExpressController {
|
|||||||
{ session: false },
|
{ session: false },
|
||||||
(
|
(
|
||||||
err: any,
|
err: any,
|
||||||
user?: User | false | null,
|
user?: AuthUser | false | null,
|
||||||
info?: object | string | Array<string | undefined>,
|
info?: object | string | Array<string | undefined>,
|
||||||
status?: number | Array<number | undefined>,
|
status?: number | Array<number | undefined>,
|
||||||
) => {
|
) => {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { config } from "@/config";
|
import { config } from "@/config";
|
||||||
import { User } from "@/contexts/auth/domain";
|
import { AuthUser } from "@/contexts/auth/domain";
|
||||||
import { IServerError } from "@/contexts/common/domain/errors";
|
import { IServerError } from "@/contexts/common/domain/errors";
|
||||||
import {
|
import {
|
||||||
InfrastructureError,
|
InfrastructureError,
|
||||||
@ -30,7 +30,7 @@ export class LoginController extends ExpressController {
|
|||||||
|
|
||||||
async executeImpl() {
|
async executeImpl() {
|
||||||
try {
|
try {
|
||||||
const user = <User>this.req.user;
|
const user = <AuthUser>this.req.user;
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
const errorMessage = "Unexpected missing user data";
|
const errorMessage = "Unexpected missing user data";
|
||||||
@ -55,13 +55,13 @@ export class LoginController extends ExpressController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _generateUserToken(user: User) {
|
private _generateUserToken(user: AuthUser) {
|
||||||
return JWT.sign({ email: user.email.toString() }, config.jwt.secret_key, {
|
return JWT.sign({ email: user.email.toString() }, config.jwt.secret_key, {
|
||||||
expiresIn: config.jwt.token_expiration,
|
expiresIn: config.jwt.token_expiration,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _generateUserRefreshToken(user: User) {
|
private _generateUserRefreshToken(user: AuthUser) {
|
||||||
return JWT.sign(
|
return JWT.sign(
|
||||||
{ email: user.email.toString() },
|
{ email: user.email.toString() },
|
||||||
config.jwt.refresh_secret_key,
|
config.jwt.refresh_secret_key,
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { IUser } from "@/contexts/auth/domain";
|
import { IAuthUser } from "@/contexts/auth/domain";
|
||||||
import { IAuthContext } from "@/contexts/auth/infrastructure/Auth.context";
|
import { IAuthContext } from "@/contexts/auth/infrastructure/Auth.context";
|
||||||
import { ILogin_Response_DTO } from "@shared/contexts";
|
import { ILogin_Response_DTO } from "@shared/contexts";
|
||||||
|
|
||||||
export interface ILoginUser {
|
export interface ILoginUser {
|
||||||
user: IUser;
|
user: IAuthUser;
|
||||||
token: string;
|
token: string;
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,40 +1,55 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
import { AuthUser } from "@/contexts/auth/domain";
|
||||||
|
import { generateExpressErrorResponse } from "@/contexts/common/infrastructure/express/ExpressErrorResponse";
|
||||||
|
import Express from "express";
|
||||||
|
import httpStatus from "http-status";
|
||||||
import passport from "passport";
|
import passport from "passport";
|
||||||
|
|
||||||
export const isLoggedUser = passport.authenticate("local-jwt", {
|
function compose(middlewareArray: any[]) {
|
||||||
session: false,
|
if (!middlewareArray.length) {
|
||||||
});
|
return function (
|
||||||
|
req: Express.Request,
|
||||||
/*export const authenticate = (
|
res: Express.Response,
|
||||||
req: Express.Request,
|
next: Express.NextFunction,
|
||||||
res: Express.Response,
|
) {
|
||||||
next: Express.NextFunction,
|
|
||||||
) => {
|
|
||||||
// Use Passport to authenticate the request using the "jwt" strategy
|
|
||||||
passport.authenticate(
|
|
||||||
"local-jwt",
|
|
||||||
{ session: false },
|
|
||||||
(
|
|
||||||
err: any,
|
|
||||||
user?: User | false | null,
|
|
||||||
info?: object | string | Array<string | undefined>,
|
|
||||||
status?: number | Array<number | undefined>,
|
|
||||||
) => {
|
|
||||||
console.log(user);
|
|
||||||
if (err) next(err); // If there's an error, pass it on to the next middleware
|
|
||||||
if (!user) {
|
|
||||||
// If the user is not authenticated, send a 401 Unauthorized response
|
|
||||||
return res.status(401).json({
|
|
||||||
message: "Unauthorized access. No token provided.",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// If the user is authenticated, attach the user object to the request and move on to the next middleware
|
|
||||||
req.user = user;
|
|
||||||
next();
|
next();
|
||||||
},
|
};
|
||||||
)(req, res, next);
|
}
|
||||||
};
|
|
||||||
|
|
||||||
|
const head = middlewareArray[0];
|
||||||
|
const tail = middlewareArray.slice(1);
|
||||||
|
|
||||||
*/
|
return function (
|
||||||
|
req: Express.Request,
|
||||||
|
res: Express.Response,
|
||||||
|
next: Express.NextFunction,
|
||||||
|
) {
|
||||||
|
head(req, res, function (err: unknown) {
|
||||||
|
if (err) return next(err);
|
||||||
|
compose(tail)(req, res, next);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isLoggedUser = compose([
|
||||||
|
passport.authenticate("local-jwt", {
|
||||||
|
session: false,
|
||||||
|
}),
|
||||||
|
(req: Express.Request, res: Express.Response, next: Express.NextFunction) => {
|
||||||
|
const user = <AuthUser>req.user;
|
||||||
|
if (user.isUser) {
|
||||||
|
return generateExpressErrorResponse(req, res, httpStatus.UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const isAdminUser = compose([
|
||||||
|
isLoggedUser,
|
||||||
|
(req: Express.Request, res: Express.Response, next: Express.NextFunction) => {
|
||||||
|
const user = <AuthUser>req.user;
|
||||||
|
if (!user.isAdmin) {
|
||||||
|
return generateExpressErrorResponse(req, res, httpStatus.UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { ensureLogin_DTOIsValid } from "@shared/contexts";
|
|||||||
import { Strategy as EmailStrategy, IVerifyOptions } from "passport-local";
|
import { Strategy as EmailStrategy, IVerifyOptions } from "passport-local";
|
||||||
|
|
||||||
import { LoginUseCase } from "@/contexts/auth/application";
|
import { LoginUseCase } from "@/contexts/auth/application";
|
||||||
import { User } from "@/contexts/auth/domain";
|
import { AuthUser } from "@/contexts/auth/domain";
|
||||||
import { IAuthContext } from "../../Auth.context";
|
import { IAuthContext } from "../../Auth.context";
|
||||||
import { registerAuthRepository } from "../../Auth.repository";
|
import { registerAuthRepository } from "../../Auth.repository";
|
||||||
|
|
||||||
@ -33,7 +33,11 @@ class EmailStrategyController extends PassportStrategyController {
|
|||||||
public async verifyStrategy(
|
public async verifyStrategy(
|
||||||
email: string,
|
email: string,
|
||||||
password: string,
|
password: string,
|
||||||
done: (error: any, user?: User | false, options?: IVerifyOptions) => void,
|
done: (
|
||||||
|
error: any,
|
||||||
|
user?: AuthUser | false,
|
||||||
|
options?: IVerifyOptions,
|
||||||
|
) => void,
|
||||||
) {
|
) {
|
||||||
const loginDTOOrError = ensureLogin_DTOIsValid({ email, password });
|
const loginDTOOrError = ensureLogin_DTOIsValid({ email, password });
|
||||||
|
|
||||||
|
|||||||
@ -49,5 +49,20 @@ export const AuthRouter = (appRouter: Express.Router) => {
|
|||||||
createLoginController(res.locals["context"]).execute(req, res, next),
|
createLoginController(res.locals["context"]).execute(req, res, next),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
authRoutes.post(
|
||||||
|
"/logout",
|
||||||
|
isLoggedUser,
|
||||||
|
(
|
||||||
|
req: Express.Request,
|
||||||
|
res: Express.Response,
|
||||||
|
next: Express.NextFunction,
|
||||||
|
) => {
|
||||||
|
//req.logout(); <-- ??
|
||||||
|
return res.status(200).json();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
authRoutes.post("/register");
|
||||||
|
|
||||||
appRouter.use("/auth", authRoutes);
|
appRouter.use("/auth", authRoutes);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,30 +3,37 @@ import {
|
|||||||
SequelizeMapper,
|
SequelizeMapper,
|
||||||
} from "@/contexts/common/infrastructure";
|
} from "@/contexts/common/infrastructure";
|
||||||
import { Email, Name, UniqueID } from "@shared/contexts";
|
import { Email, Name, UniqueID } from "@shared/contexts";
|
||||||
import { IUserProps, User } from "../../domain/entities";
|
import { AuthUser, IAuthUserProps } from "../../domain/entities";
|
||||||
import { IAuthContext } from "../Auth.context";
|
import { IAuthContext } from "../Auth.context";
|
||||||
import { TCreationUser_Attributes, User_Model } from "../sequelize/user.model";
|
import {
|
||||||
|
AuthUser_Model,
|
||||||
|
TCreationUser_Attributes,
|
||||||
|
} from "../sequelize/authUser.model";
|
||||||
|
|
||||||
export interface IUserMapper
|
export interface IUserMapper
|
||||||
extends ISequelizeMapper<User_Model, TCreationUser_Attributes, User> {}
|
extends ISequelizeMapper<
|
||||||
|
AuthUser_Model,
|
||||||
|
TCreationUser_Attributes,
|
||||||
|
AuthUser
|
||||||
|
> {}
|
||||||
|
|
||||||
class UserMapper
|
class UserMapper
|
||||||
extends SequelizeMapper<User_Model, TCreationUser_Attributes, User>
|
extends SequelizeMapper<AuthUser_Model, TCreationUser_Attributes, AuthUser>
|
||||||
implements IUserMapper
|
implements IUserMapper
|
||||||
{
|
{
|
||||||
public constructor(props: { context: IAuthContext }) {
|
public constructor(props: { context: IAuthContext }) {
|
||||||
super(props);
|
super(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected toDomainMappingImpl(source: User_Model, params: any): User {
|
protected toDomainMappingImpl(source: AuthUser_Model, params: any): AuthUser {
|
||||||
const props: IUserProps = {
|
const props: IAuthUserProps = {
|
||||||
name: this.mapsValue(source, "name", Name.create),
|
name: this.mapsValue(source, "name", Name.create),
|
||||||
email: this.mapsValue(source, "email", Email.create),
|
email: this.mapsValue(source, "email", Email.create),
|
||||||
hashed_password: source.password,
|
hashed_password: source.password,
|
||||||
};
|
};
|
||||||
|
|
||||||
const id = this.mapsValue(source, "id", UniqueID.create);
|
const id = this.mapsValue(source, "id", UniqueID.create);
|
||||||
const userOrError = User.create(props, id);
|
const userOrError = AuthUser.create(props, id);
|
||||||
|
|
||||||
if (userOrError.isFailure) {
|
if (userOrError.isFailure) {
|
||||||
throw userOrError.error;
|
throw userOrError.error;
|
||||||
@ -36,7 +43,7 @@ class UserMapper
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected toPersistenceMappingImpl(
|
protected toPersistenceMappingImpl(
|
||||||
source: User,
|
source: AuthUser,
|
||||||
params?: Record<string, any> | undefined,
|
params?: Record<string, any> | undefined,
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -6,11 +6,11 @@ import {
|
|||||||
Sequelize,
|
Sequelize,
|
||||||
} from "sequelize";
|
} from "sequelize";
|
||||||
|
|
||||||
export type TCreationUser_Attributes = InferCreationAttributes<User_Model>;
|
export type TCreationUser_Attributes = InferCreationAttributes<AuthUser_Model>;
|
||||||
|
|
||||||
export class User_Model extends Model<
|
export class AuthUser_Model extends Model<
|
||||||
InferAttributes<User_Model>,
|
InferAttributes<AuthUser_Model>,
|
||||||
InferCreationAttributes<User_Model>
|
InferCreationAttributes<AuthUser_Model>
|
||||||
> {
|
> {
|
||||||
// To avoid table creation
|
// To avoid table creation
|
||||||
/*static async sync(): Promise<any> {
|
/*static async sync(): Promise<any> {
|
||||||
@ -26,7 +26,7 @@ export class User_Model extends Model<
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default (sequelize: Sequelize) => {
|
export default (sequelize: Sequelize) => {
|
||||||
User_Model.init(
|
AuthUser_Model.init(
|
||||||
{
|
{
|
||||||
id: {
|
id: {
|
||||||
type: new DataTypes.UUID(),
|
type: new DataTypes.UUID(),
|
||||||
@ -63,5 +63,5 @@ export default (sequelize: Sequelize) => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return User_Model;
|
return AuthUser_Model;
|
||||||
};
|
};
|
||||||
@ -1 +1 @@
|
|||||||
export * from "./user.model";
|
export * from "./authUser.model";
|
||||||
|
|||||||
@ -5,8 +5,8 @@ import {
|
|||||||
import { ICollection, IQueryCriteria, UniqueID } from "@shared/contexts";
|
import { ICollection, IQueryCriteria, UniqueID } from "@shared/contexts";
|
||||||
import { Transaction } from "sequelize";
|
import { Transaction } from "sequelize";
|
||||||
import { ICatalogContext } from ".";
|
import { ICatalogContext } from ".";
|
||||||
|
import { ICatalogRepository } from "../domain";
|
||||||
import { Article } from "../domain/entities";
|
import { Article } from "../domain/entities";
|
||||||
import { ICatalogRepository } from "../domain/repository/CatalogRepository.interface";
|
|
||||||
import { IArticleMapper, createArticleMapper } from "./mappers/article.mapper";
|
import { IArticleMapper, createArticleMapper } from "./mappers/article.mapper";
|
||||||
|
|
||||||
export type QueryParams = {
|
export type QueryParams = {
|
||||||
|
|||||||
@ -5,12 +5,11 @@ import { ListArticlesController } from "./ListArticlesController";
|
|||||||
import { listArticlesPresenter } from "./presenter";
|
import { listArticlesPresenter } from "./presenter";
|
||||||
|
|
||||||
export const createListArticlesController = (context: ICatalogContext) => {
|
export const createListArticlesController = (context: ICatalogContext) => {
|
||||||
const listArticlesUseCase = new ListArticlesUseCase(context);
|
|
||||||
registerCatalogRepository(context);
|
registerCatalogRepository(context);
|
||||||
|
|
||||||
return new ListArticlesController(
|
return new ListArticlesController(
|
||||||
{
|
{
|
||||||
useCase: listArticlesUseCase,
|
useCase: new ListArticlesUseCase(context),
|
||||||
presenter: listArticlesPresenter,
|
presenter: listArticlesPresenter,
|
||||||
},
|
},
|
||||||
context,
|
context,
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import {
|
|||||||
export interface IListArticlesPresenter {
|
export interface IListArticlesPresenter {
|
||||||
map: (
|
map: (
|
||||||
article: Article,
|
article: Article,
|
||||||
context: ICatalogContext
|
context: ICatalogContext,
|
||||||
) => IListArticles_Response_DTO;
|
) => IListArticles_Response_DTO;
|
||||||
|
|
||||||
mapArray: (
|
mapArray: (
|
||||||
@ -18,17 +18,15 @@ export interface IListArticlesPresenter {
|
|||||||
params: {
|
params: {
|
||||||
page: number;
|
page: number;
|
||||||
limit: number;
|
limit: number;
|
||||||
}
|
},
|
||||||
) => IListResponse_DTO<IListArticles_Response_DTO>;
|
) => IListResponse_DTO<IListArticles_Response_DTO>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const listArticlesPresenter: IListArticlesPresenter = {
|
export const listArticlesPresenter: IListArticlesPresenter = {
|
||||||
map: (
|
map: (
|
||||||
article: Article,
|
article: Article,
|
||||||
context: ICatalogContext
|
context: ICatalogContext,
|
||||||
): IListArticles_Response_DTO => {
|
): IListArticles_Response_DTO => {
|
||||||
console.time("listArticlesPresenter.map");
|
|
||||||
|
|
||||||
const result: IListArticles_Response_DTO = {
|
const result: IListArticles_Response_DTO = {
|
||||||
id: article.id.toString(),
|
id: article.id.toString(),
|
||||||
catalog_name: article.catalog_name.toString(),
|
catalog_name: article.catalog_name.toString(),
|
||||||
@ -40,9 +38,6 @@ export const listArticlesPresenter: IListArticlesPresenter = {
|
|||||||
points: article.points.toNumber(),
|
points: article.points.toNumber(),
|
||||||
retail_price: article.retail_price.toObject(),
|
retail_price: article.retail_price.toObject(),
|
||||||
};
|
};
|
||||||
|
|
||||||
console.timeEnd("listArticlesPresenter.map");
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -52,15 +47,13 @@ export const listArticlesPresenter: IListArticlesPresenter = {
|
|||||||
params: {
|
params: {
|
||||||
page: number;
|
page: number;
|
||||||
limit: number;
|
limit: number;
|
||||||
}
|
},
|
||||||
): IListResponse_DTO<IListArticles_Response_DTO> => {
|
): IListResponse_DTO<IListArticles_Response_DTO> => {
|
||||||
console.time("listArticlesPresenter.mapArray");
|
|
||||||
|
|
||||||
const { page, limit } = params;
|
const { page, limit } = params;
|
||||||
|
|
||||||
const totalCount = articles.totalCount ?? 0;
|
const totalCount = articles.totalCount ?? 0;
|
||||||
const items = articles.items.map((article: Article) =>
|
const items = articles.items.map((article: Article) =>
|
||||||
listArticlesPresenter.map(article, context)
|
listArticlesPresenter.map(article, context),
|
||||||
);
|
);
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
@ -71,8 +64,6 @@ export const listArticlesPresenter: IListArticlesPresenter = {
|
|||||||
items,
|
items,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.timeEnd("listArticlesPresenter.mapArray");
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,15 +1,11 @@
|
|||||||
|
import { IError_Response_DTO } from "@shared/contexts";
|
||||||
import * as express from "express";
|
import * as express from "express";
|
||||||
|
import httpStatus from "http-status";
|
||||||
import { URL } from "url";
|
import { URL } from "url";
|
||||||
|
|
||||||
import {
|
|
||||||
IErrorExtra_Response_DTO,
|
|
||||||
IError_Response_DTO,
|
|
||||||
} from "@shared/contexts";
|
|
||||||
import { UseCaseError } from "../../application";
|
|
||||||
import { IServerError } from "../../domain/errors";
|
import { IServerError } from "../../domain/errors";
|
||||||
import { IController } from "../Controller.interface";
|
import { IController } from "../Controller.interface";
|
||||||
import { InfrastructureError } from "../InfrastructureError";
|
import { InfrastructureError } from "../InfrastructureError";
|
||||||
import { ProblemDocument, ProblemDocumentExtension } from "./ProblemDocument";
|
import { generateExpressErrorResponse } from "./ExpressErrorResponse";
|
||||||
|
|
||||||
export abstract class ExpressController implements IController {
|
export abstract class ExpressController implements IController {
|
||||||
protected req: express.Request;
|
protected req: express.Request;
|
||||||
@ -55,19 +51,22 @@ export abstract class ExpressController implements IController {
|
|||||||
console.trace("Show me");
|
console.trace("Show me");
|
||||||
console.groupEnd();
|
console.groupEnd();
|
||||||
|
|
||||||
return this._errorResponse(500, error ? error.toString() : "Fail");
|
return this._errorResponse(
|
||||||
|
httpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
error ? error.toString() : "Fail",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public created<T>(dto?: T) {
|
public created<T>(dto?: T) {
|
||||||
if (dto) {
|
if (dto) {
|
||||||
return this.res.status(201).json(dto).send();
|
return this.res.status(httpStatus.CREATED).json(dto).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.res.status(201).send();
|
return this.res.status(httpStatus.CREATED).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
public noContent() {
|
public noContent() {
|
||||||
return this.res.status(204).send();
|
return this.res.status(httpStatus.NO_CONTENT).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
public download(filepath: string, filename: string, done?: any) {
|
public download(filepath: string, filename: string, done?: any) {
|
||||||
@ -75,47 +74,51 @@ export abstract class ExpressController implements IController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public clientError(message?: string) {
|
public clientError(message?: string) {
|
||||||
return this._errorResponse(400, message);
|
return this._errorResponse(httpStatus.BAD_REQUEST, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public unauthorizedError(message?: string) {
|
public unauthorizedError(message?: string) {
|
||||||
return this._errorResponse(401, message);
|
return this._errorResponse(httpStatus.UNAUTHORIZED, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public paymentRequiredError(message?: string) {
|
public paymentRequiredError(message?: string) {
|
||||||
return this._errorResponse(402, message);
|
return this._errorResponse(httpStatus.PAYMENT_REQUIRED, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public forbiddenError(message?: string) {
|
public forbiddenError(message?: string) {
|
||||||
return this._errorResponse(403, message);
|
return this._errorResponse(httpStatus.FORBIDDEN, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public notFoundError(message: string, error?: IServerError) {
|
public notFoundError(message: string, error?: IServerError) {
|
||||||
return this._errorResponse(404, message, error);
|
return this._errorResponse(httpStatus.NOT_FOUND, message, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
public conflictError(message: string, error?: IServerError) {
|
public conflictError(message: string, error?: IServerError) {
|
||||||
return this._errorResponse(409, message, error);
|
return this._errorResponse(httpStatus.CONFLICT, message, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
public invalidInputError(message?: string, error?: InfrastructureError) {
|
public invalidInputError(message?: string, error?: InfrastructureError) {
|
||||||
return this._errorResponse(422, message, error);
|
return this._errorResponse(httpStatus.UNPROCESSABLE_ENTITY, message, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
public tooManyError(message: string, error?: Error) {
|
public tooManyError(message: string, error?: Error) {
|
||||||
return this._errorResponse(429, message, error);
|
return this._errorResponse(httpStatus.TOO_MANY_REQUESTS, message, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
public internalServerError(message?: string, error?: IServerError) {
|
public internalServerError(message?: string, error?: IServerError) {
|
||||||
return this._errorResponse(500, message, error);
|
return this._errorResponse(
|
||||||
|
httpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
message,
|
||||||
|
error,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public todoError(message?: string) {
|
public todoError(message?: string) {
|
||||||
return this._errorResponse(501, message);
|
return this._errorResponse(httpStatus.NOT_IMPLEMENTED, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public unavailableError(message?: string) {
|
public unavailableError(message?: string) {
|
||||||
return this._errorResponse(503, message);
|
return this._errorResponse(httpStatus.SERVICE_UNAVAILABLE, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _jsonResponse(
|
private _jsonResponse(
|
||||||
@ -130,102 +133,12 @@ export abstract class ExpressController implements IController {
|
|||||||
message?: string,
|
message?: string,
|
||||||
error?: Error | InfrastructureError,
|
error?: Error | InfrastructureError,
|
||||||
): express.Response<IError_Response_DTO> {
|
): express.Response<IError_Response_DTO> {
|
||||||
const context = {};
|
return generateExpressErrorResponse(
|
||||||
|
this.req,
|
||||||
if (Object.keys(this.res.locals).length) {
|
this.res,
|
||||||
if ("user" in this.res.locals) {
|
|
||||||
context["user"] = this.res.locals.user;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Object.keys(this.req.params).length) {
|
|
||||||
context["params"] = this.req.params;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Object.keys(this.req.query).length) {
|
|
||||||
context["query"] = this.req.query;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Object.keys(this.req.body).length) {
|
|
||||||
context["body"] = this.req.body;
|
|
||||||
}
|
|
||||||
|
|
||||||
const extension = new ProblemDocumentExtension({
|
|
||||||
context,
|
|
||||||
extra: error ? { ...this._processError(error) } : {},
|
|
||||||
});
|
|
||||||
|
|
||||||
return this._jsonResponse(
|
|
||||||
statusCode,
|
statusCode,
|
||||||
new ProblemDocument(
|
message,
|
||||||
{
|
error,
|
||||||
status: statusCode,
|
|
||||||
detail: message,
|
|
||||||
instance: this.req.baseUrl,
|
|
||||||
},
|
|
||||||
extension,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _processError(
|
|
||||||
error: Error | InfrastructureError,
|
|
||||||
): IErrorExtra_Response_DTO {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
{
|
|
||||||
code: "INVALID_INPUT_DATA",
|
|
||||||
payload: {
|
|
||||||
label: "tin",
|
|
||||||
path: "tin", // [{path: "first_name"}, {path: "last_name"}]
|
|
||||||
},
|
|
||||||
name: "UseCaseError",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
{
|
|
||||||
code: "INVALID_INPUT_DATA",
|
|
||||||
payload: [
|
|
||||||
{
|
|
||||||
tin: "{tin} is not allowed to be empty",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
first_name: "{first_name} is not allowed to be empty",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
last_name: "{last_name} is not allowed to be empty",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
company_name: "{company_name} is not allowed to be empty",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
name: "InfrastructureError",
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
const useCaseError = <UseCaseError>error;
|
|
||||||
|
|
||||||
const payload = !Array.isArray(useCaseError.payload)
|
|
||||||
? Array(useCaseError.payload)
|
|
||||||
: useCaseError.payload;
|
|
||||||
|
|
||||||
const errors = payload.map((item) => {
|
|
||||||
if (item.path) {
|
|
||||||
return item.path
|
|
||||||
? {
|
|
||||||
[String(item.path)]: useCaseError.message,
|
|
||||||
}
|
|
||||||
: {};
|
|
||||||
} else {
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
errors,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,78 @@
|
|||||||
|
import {
|
||||||
|
IErrorExtra_Response_DTO,
|
||||||
|
IError_Response_DTO,
|
||||||
|
} from "@shared/contexts";
|
||||||
|
import Express from "express";
|
||||||
|
import { UseCaseError } from "../../application";
|
||||||
|
import { InfrastructureError } from "../InfrastructureError";
|
||||||
|
import { ProblemDocument, ProblemDocumentExtension } from "./ProblemDocument";
|
||||||
|
|
||||||
|
export const generateExpressErrorResponse = (
|
||||||
|
req: Express.Request,
|
||||||
|
res: Express.Response,
|
||||||
|
statusCode: number,
|
||||||
|
message?: string,
|
||||||
|
error?: Error | InfrastructureError,
|
||||||
|
): Express.Response<IError_Response_DTO> => {
|
||||||
|
const context = {};
|
||||||
|
|
||||||
|
if (Object.keys(res.locals).length) {
|
||||||
|
if ("user" in res.locals) {
|
||||||
|
context["user"] = res.locals.user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(req.params).length) {
|
||||||
|
context["params"] = req.params;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(req.query).length) {
|
||||||
|
context["query"] = req.query;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(req.body).length) {
|
||||||
|
context["body"] = req.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
const extension = new ProblemDocumentExtension({
|
||||||
|
context,
|
||||||
|
extra: error ? { ...generateExpressError(error) } : {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const jsonPayload = new ProblemDocument(
|
||||||
|
{
|
||||||
|
status: statusCode,
|
||||||
|
detail: message,
|
||||||
|
instance: req.baseUrl,
|
||||||
|
},
|
||||||
|
extension,
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(statusCode).json(jsonPayload).send();
|
||||||
|
};
|
||||||
|
|
||||||
|
function generateExpressError(
|
||||||
|
error: Error | InfrastructureError,
|
||||||
|
): IErrorExtra_Response_DTO {
|
||||||
|
const useCaseError = <UseCaseError>error;
|
||||||
|
|
||||||
|
const payload = !Array.isArray(useCaseError.payload)
|
||||||
|
? Array(useCaseError.payload)
|
||||||
|
: useCaseError.payload;
|
||||||
|
|
||||||
|
const errors = payload.map((item) => {
|
||||||
|
if (item.path) {
|
||||||
|
return item.path
|
||||||
|
? {
|
||||||
|
[String(item.path)]: useCaseError.message,
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
} else {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
errors,
|
||||||
|
};
|
||||||
|
}
|
||||||
76
server/src/contexts/users/application/ListUsersUseCase.ts
Normal file
76
server/src/contexts/users/application/ListUsersUseCase.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import {
|
||||||
|
IUseCase,
|
||||||
|
IUseCaseError,
|
||||||
|
UseCaseError,
|
||||||
|
handleUseCaseError,
|
||||||
|
} from "@/contexts/common/application/useCases";
|
||||||
|
import { IRepositoryManager } from "@/contexts/common/domain";
|
||||||
|
import {
|
||||||
|
Collection,
|
||||||
|
ICollection,
|
||||||
|
IQueryCriteria,
|
||||||
|
Result,
|
||||||
|
} from "@shared/contexts";
|
||||||
|
|
||||||
|
import { IInfrastructureError } from "@/contexts/common/infrastructure";
|
||||||
|
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||||
|
import { User } from "../domain";
|
||||||
|
import { IUserRepository } from "../domain/repository";
|
||||||
|
|
||||||
|
export interface IListUsersParams {
|
||||||
|
queryCriteria: IQueryCriteria;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ListUsersResult =
|
||||||
|
| Result<never, IUseCaseError> // Misc errors (value objects)
|
||||||
|
| Result<ICollection<User>, never>; // Success!
|
||||||
|
|
||||||
|
export class ListUsersUseCase
|
||||||
|
implements IUseCase<IListUsersParams, Promise<ListUsersResult>>
|
||||||
|
{
|
||||||
|
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(params: Partial<IListUsersParams>): Promise<ListUsersResult> {
|
||||||
|
const { queryCriteria } = params;
|
||||||
|
|
||||||
|
return this.findUsers(queryCriteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async findUsers(queryCriteria) {
|
||||||
|
const transaction = this._adapter.startTransaction();
|
||||||
|
const userRepoBuilder = this.getRepositoryByName<IUserRepository>("User");
|
||||||
|
|
||||||
|
let users: ICollection<User> = new Collection();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await transaction.complete(async (t) => {
|
||||||
|
users = await userRepoBuilder({ transaction: t }).findAll(
|
||||||
|
queryCriteria,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return Result.ok(users);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const _error = error as IInfrastructureError;
|
||||||
|
return Result.fail(
|
||||||
|
handleUseCaseError(
|
||||||
|
UseCaseError.REPOSITORY_ERROR,
|
||||||
|
"Error al listar los usurios",
|
||||||
|
_error,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
server/src/contexts/users/application/index.ts
Normal file
1
server/src/contexts/users/application/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./ListUsersUseCase";
|
||||||
@ -21,6 +21,8 @@ export interface IUser {
|
|||||||
name: Name;
|
name: Name;
|
||||||
email: Email;
|
email: Email;
|
||||||
hashed_password: string;
|
hashed_password: string;
|
||||||
|
isUser: boolean;
|
||||||
|
isAdmin: boolean;
|
||||||
|
|
||||||
verifyPassword: (candidatePassword: string) => boolean;
|
verifyPassword: (candidatePassword: string) => boolean;
|
||||||
}
|
}
|
||||||
@ -66,6 +68,14 @@ export class User extends AggregateRoot<IUserProps> implements IUser {
|
|||||||
return this._hashed_password;
|
return this._hashed_password;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isUser(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isAdmin(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public verifyPassword(candidatePassword: string): boolean {
|
public verifyPassword(candidatePassword: string): boolean {
|
||||||
return bCrypt.compareSync(candidatePassword, this._hashed_password!);
|
return bCrypt.compareSync(candidatePassword, this._hashed_password!);
|
||||||
}
|
}
|
||||||
1
server/src/contexts/users/domain/entities/index.ts
Normal file
1
server/src/contexts/users/domain/entities/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./User";
|
||||||
2
server/src/contexts/users/domain/index.ts
Normal file
2
server/src/contexts/users/domain/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./entities";
|
||||||
|
export * from "./repository";
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
import { IRepository } from "@/contexts/common/domain";
|
||||||
|
import { ICollection, IQueryCriteria, UniqueID } from "@shared/contexts";
|
||||||
|
import { User } from "../entities";
|
||||||
|
|
||||||
|
export interface IUserRepository extends IRepository<any> {
|
||||||
|
getById(id: UniqueID): Promise<User | null>;
|
||||||
|
findAll(queryCriteria?: IQueryCriteria): Promise<ICollection<User>>;
|
||||||
|
}
|
||||||
1
server/src/contexts/users/domain/repository/index.ts
Normal file
1
server/src/contexts/users/domain/repository/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./UserRepository.interface";
|
||||||
1
server/src/contexts/users/index.ts
Normal file
1
server/src/contexts/users/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./infrastructure";
|
||||||
35
server/src/contexts/users/infrastructure/User.context.ts
Normal file
35
server/src/contexts/users/infrastructure/User.context.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import {
|
||||||
|
IRepositoryManager,
|
||||||
|
RepositoryManager,
|
||||||
|
} from "@/contexts/common/domain";
|
||||||
|
import {
|
||||||
|
ISequelizeAdapter,
|
||||||
|
createSequelizeAdapter,
|
||||||
|
} from "@/contexts/common/infrastructure/sequelize";
|
||||||
|
|
||||||
|
export interface IUserContext {
|
||||||
|
adapter: ISequelizeAdapter;
|
||||||
|
repositoryManager: IRepositoryManager;
|
||||||
|
//services: IApplicationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UserContext {
|
||||||
|
private static instance: UserContext | null = null;
|
||||||
|
|
||||||
|
public static getInstance(): IUserContext {
|
||||||
|
if (!UserContext.instance) {
|
||||||
|
UserContext.instance = new UserContext({
|
||||||
|
adapter: createSequelizeAdapter(),
|
||||||
|
repositoryManager: RepositoryManager.getInstance(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return UserContext.instance.context;
|
||||||
|
}
|
||||||
|
|
||||||
|
private context: IUserContext;
|
||||||
|
|
||||||
|
private constructor(context: IUserContext) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
}
|
||||||
85
server/src/contexts/users/infrastructure/User.repository.ts
Normal file
85
server/src/contexts/users/infrastructure/User.repository.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import {
|
||||||
|
ISequelizeAdapter,
|
||||||
|
SequelizeRepository,
|
||||||
|
} from "@/contexts/common/infrastructure/sequelize";
|
||||||
|
import { Email, ICollection, IQueryCriteria, UniqueID } from "@shared/contexts";
|
||||||
|
import { Transaction } from "sequelize";
|
||||||
|
import { User } from "../domain";
|
||||||
|
import { IUserRepository } from "../domain/repository";
|
||||||
|
import { IUserContext } from "./User.context";
|
||||||
|
import { IUserMapper, createUserMapper } from "./mappers";
|
||||||
|
|
||||||
|
export type QueryParams = {
|
||||||
|
pagination: Record<string, any>;
|
||||||
|
filters: Record<string, any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class UserRepository
|
||||||
|
extends SequelizeRepository<User>
|
||||||
|
implements IUserRepository
|
||||||
|
{
|
||||||
|
protected mapper: IUserMapper;
|
||||||
|
|
||||||
|
public constructor(props: {
|
||||||
|
mapper: IUserMapper;
|
||||||
|
adapter: ISequelizeAdapter;
|
||||||
|
transaction: Transaction;
|
||||||
|
}) {
|
||||||
|
const { adapter, mapper, transaction } = props;
|
||||||
|
super({ adapter, transaction });
|
||||||
|
this.mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getById(id: UniqueID): Promise<User | null> {
|
||||||
|
const rawUser: any = await this._getById("User_Model", id);
|
||||||
|
|
||||||
|
if (!rawUser === true) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.mapper.mapToDomain(rawUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findUserByEmail(email: Email): Promise<User | null> {
|
||||||
|
const rawUser: any = await this._getBy(
|
||||||
|
"User_Model",
|
||||||
|
"email",
|
||||||
|
email.toPrimitive(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!rawUser === true) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.mapper.mapToDomain(rawUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findAll(
|
||||||
|
queryCriteria?: IQueryCriteria,
|
||||||
|
): Promise<ICollection<any>> {
|
||||||
|
const { rows, count } = await this._findAll(
|
||||||
|
"User_Model",
|
||||||
|
queryCriteria,
|
||||||
|
/*{
|
||||||
|
include: [], // esto es para quitar las asociaciones al hacer la consulta
|
||||||
|
}*/
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.mapper.mapArrayAndCountToDomain(rows, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const registerUserRepository = (context: IUserContext) => {
|
||||||
|
const adapter = context.adapter;
|
||||||
|
const repoManager = context.repositoryManager;
|
||||||
|
|
||||||
|
repoManager.registerRepository("User", (params = { transaction: null }) => {
|
||||||
|
const { transaction } = params;
|
||||||
|
|
||||||
|
return new UserRepository({
|
||||||
|
transaction,
|
||||||
|
adapter,
|
||||||
|
mapper: createUserMapper(context),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./listUsers";
|
||||||
@ -0,0 +1,88 @@
|
|||||||
|
import Joi from "joi";
|
||||||
|
|
||||||
|
import { QueryCriteriaService } from "@/contexts/common/application/services";
|
||||||
|
import { IServerError } from "@/contexts/common/domain/errors";
|
||||||
|
import { ExpressController } from "@/contexts/common/infrastructure/express";
|
||||||
|
import {
|
||||||
|
ListUsersResult,
|
||||||
|
ListUsersUseCase,
|
||||||
|
} from "@/contexts/users/application";
|
||||||
|
import { User } from "@/contexts/users/domain";
|
||||||
|
import {
|
||||||
|
ICollection,
|
||||||
|
IListResponse_DTO,
|
||||||
|
IListUsers_Response_DTO,
|
||||||
|
IQueryCriteria,
|
||||||
|
Result,
|
||||||
|
RuleValidator,
|
||||||
|
} from "@shared/contexts";
|
||||||
|
import { IUserContext } from "../../../User.context";
|
||||||
|
import { IListUsersPresenter } from "./presenter";
|
||||||
|
|
||||||
|
export class ListUsersController extends ExpressController {
|
||||||
|
private useCase: ListUsersUseCase;
|
||||||
|
private presenter: IListUsersPresenter;
|
||||||
|
private context: IUserContext;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
props: {
|
||||||
|
useCase: ListUsersUseCase;
|
||||||
|
presenter: IListUsersPresenter;
|
||||||
|
},
|
||||||
|
context: IUserContext,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
const { useCase, presenter } = props;
|
||||||
|
this.useCase = useCase;
|
||||||
|
this.presenter = presenter;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected validateQuery(query): Result<any> {
|
||||||
|
const schema = Joi.object({
|
||||||
|
page: Joi.number().optional(),
|
||||||
|
limit: Joi.number().optional(),
|
||||||
|
$sort_by: Joi.string().optional(),
|
||||||
|
$filters: Joi.string().optional(),
|
||||||
|
q: Joi.string().optional(),
|
||||||
|
}).optional();
|
||||||
|
|
||||||
|
return RuleValidator.validate(schema, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeImpl() {
|
||||||
|
const queryOrError = this.validateQuery(this.req.query);
|
||||||
|
if (queryOrError.isFailure) {
|
||||||
|
return this.clientError(queryOrError.error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryParams = queryOrError.object;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const queryCriteria: IQueryCriteria =
|
||||||
|
QueryCriteriaService.parse(queryParams);
|
||||||
|
|
||||||
|
console.log(queryCriteria);
|
||||||
|
|
||||||
|
const result: ListUsersResult = await this.useCase.execute({
|
||||||
|
queryCriteria,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.isFailure) {
|
||||||
|
return this.clientError(result.error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const customers = <ICollection<User>>result.object;
|
||||||
|
|
||||||
|
return this.ok<IListResponse_DTO<IListUsers_Response_DTO>>(
|
||||||
|
this.presenter.mapArray(customers, this.context, {
|
||||||
|
page: queryCriteria.pagination.offset,
|
||||||
|
limit: queryCriteria.pagination.limit,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} catch (e: unknown) {
|
||||||
|
return this.fail(e as IServerError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
import { ListUsersUseCase } from "@/contexts/users/application";
|
||||||
|
import { IUserContext } from "../../../User.context";
|
||||||
|
import { registerUserRepository } from "../../../User.repository";
|
||||||
|
import { ListUsersController } from "./ListUsers.controller";
|
||||||
|
import { listUsersPresenter } from "./presenter";
|
||||||
|
|
||||||
|
export const createListUsersController = (context: IUserContext) => {
|
||||||
|
registerUserRepository(context);
|
||||||
|
return new ListUsersController(
|
||||||
|
{
|
||||||
|
useCase: new ListUsersUseCase(context),
|
||||||
|
presenter: listUsersPresenter,
|
||||||
|
},
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
import { User } from "@/contexts/users/domain";
|
||||||
|
import { IUserContext } from "@/contexts/users/infrastructure/User.context";
|
||||||
|
import {
|
||||||
|
ICollection,
|
||||||
|
IListResponse_DTO,
|
||||||
|
IListUsers_Response_DTO,
|
||||||
|
} from "@shared/contexts";
|
||||||
|
|
||||||
|
export interface IListUsersPresenter {
|
||||||
|
map: (user: User, context: IUserContext) => IListUsers_Response_DTO;
|
||||||
|
|
||||||
|
mapArray: (
|
||||||
|
users: ICollection<User>,
|
||||||
|
context: IUserContext,
|
||||||
|
params: {
|
||||||
|
page: number;
|
||||||
|
limit: number;
|
||||||
|
},
|
||||||
|
) => IListResponse_DTO<IListUsers_Response_DTO>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const listUsersPresenter: IListUsersPresenter = {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
map: (user: User, context: IUserContext): IListUsers_Response_DTO => {
|
||||||
|
return {
|
||||||
|
id: user.id.toString(),
|
||||||
|
name: user.name.toString(),
|
||||||
|
email: user.email.toString(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
mapArray: (
|
||||||
|
users: ICollection<User>,
|
||||||
|
context: IUserContext,
|
||||||
|
params: {
|
||||||
|
page: number;
|
||||||
|
limit: number;
|
||||||
|
},
|
||||||
|
): IListResponse_DTO<IListUsers_Response_DTO> => {
|
||||||
|
const { page, limit } = params;
|
||||||
|
|
||||||
|
const totalCount = users.totalCount ?? 0;
|
||||||
|
const items = users.items.map((user: User) =>
|
||||||
|
listUsersPresenter.map(user, context),
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
page,
|
||||||
|
per_page: limit,
|
||||||
|
total_pages: Math.ceil(totalCount / limit),
|
||||||
|
total_items: totalCount,
|
||||||
|
items,
|
||||||
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./ListUsers.presenter";
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./controllers";
|
||||||
|
export * from "./routes";
|
||||||
23
server/src/contexts/users/infrastructure/express/routes.ts
Normal file
23
server/src/contexts/users/infrastructure/express/routes.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { applyMiddleware } from "@/contexts/common/infrastructure/express";
|
||||||
|
import Express from "express";
|
||||||
|
import { createListUsersController } from "./controllers";
|
||||||
|
|
||||||
|
export const UserRouter = (appRouter: Express.Router) => {
|
||||||
|
const userRoutes: Express.Router = Express.Router({ mergeParams: true });
|
||||||
|
|
||||||
|
userRoutes.get(
|
||||||
|
"/",
|
||||||
|
applyMiddleware("isAdminUser"),
|
||||||
|
(req: Express.Request, res: Express.Response, next: Express.NextFunction) =>
|
||||||
|
createListUsersController(res.locals["context"]).execute(req, res, next),
|
||||||
|
);
|
||||||
|
|
||||||
|
/*userRoutes.get(
|
||||||
|
"/:id",
|
||||||
|
applyMiddleware("isAdminUser"),
|
||||||
|
(req: Express.Request, res: Express.Response, next: Express.NextFunction) =>
|
||||||
|
createGettUserController(res.locals["context"]).execute(req, res, next),
|
||||||
|
);*/
|
||||||
|
|
||||||
|
appRouter.use("/users", userRoutes);
|
||||||
|
};
|
||||||
2
server/src/contexts/users/infrastructure/index.ts
Normal file
2
server/src/contexts/users/infrastructure/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./express";
|
||||||
|
export * from "./sequelize";
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./user.mapper";
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
import {
|
||||||
|
ISequelizeMapper,
|
||||||
|
SequelizeMapper,
|
||||||
|
} from "@/contexts/common/infrastructure";
|
||||||
|
import { Email, Name, UniqueID } from "@shared/contexts";
|
||||||
|
import { IUserProps, User } from "../../domain";
|
||||||
|
import { IUserContext } from "../User.context";
|
||||||
|
import { TCreationUser_Attributes, User_Model } from "../sequelize/user.model";
|
||||||
|
|
||||||
|
export interface IUserMapper
|
||||||
|
extends ISequelizeMapper<User_Model, TCreationUser_Attributes, User> {}
|
||||||
|
|
||||||
|
class UserMapper
|
||||||
|
extends SequelizeMapper<User_Model, TCreationUser_Attributes, User>
|
||||||
|
implements IUserMapper
|
||||||
|
{
|
||||||
|
public constructor(props: { context: IUserContext }) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected toDomainMappingImpl(source: User_Model, params: any): User {
|
||||||
|
const props: IUserProps = {
|
||||||
|
name: this.mapsValue(source, "name", Name.create),
|
||||||
|
email: this.mapsValue(source, "email", Email.create),
|
||||||
|
hashed_password: source.password,
|
||||||
|
};
|
||||||
|
|
||||||
|
const id = this.mapsValue(source, "id", UniqueID.create);
|
||||||
|
const userOrError = User.create(props, id);
|
||||||
|
|
||||||
|
if (userOrError.isFailure) {
|
||||||
|
throw userOrError.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return userOrError.object;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected toPersistenceMappingImpl(
|
||||||
|
source: User,
|
||||||
|
params?: Record<string, any> | undefined,
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
id: source.id.toPrimitive(),
|
||||||
|
name: source.name.toPrimitive(),
|
||||||
|
email: source.email.toPrimitive(),
|
||||||
|
password: source.hashed_password,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createUserMapper = (context: IUserContext): IUserMapper =>
|
||||||
|
new UserMapper({
|
||||||
|
context,
|
||||||
|
});
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./user.model";
|
||||||
@ -0,0 +1,86 @@
|
|||||||
|
import {
|
||||||
|
DataTypes,
|
||||||
|
InferAttributes,
|
||||||
|
InferCreationAttributes,
|
||||||
|
Model,
|
||||||
|
Op,
|
||||||
|
Sequelize,
|
||||||
|
} from "sequelize";
|
||||||
|
|
||||||
|
export type TCreationUser_Attributes = InferCreationAttributes<User_Model>;
|
||||||
|
|
||||||
|
export class User_Model extends Model<
|
||||||
|
InferAttributes<User_Model>,
|
||||||
|
InferCreationAttributes<User_Model>
|
||||||
|
> {
|
||||||
|
// To avoid table creation
|
||||||
|
/*static async sync(): Promise<any> {
|
||||||
|
return Promise.resolve();
|
||||||
|
}*/
|
||||||
|
|
||||||
|
static associate(connection: Sequelize) {}
|
||||||
|
|
||||||
|
declare id: string;
|
||||||
|
declare name: string;
|
||||||
|
declare email: string;
|
||||||
|
declare password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (sequelize: Sequelize) => {
|
||||||
|
User_Model.init(
|
||||||
|
{
|
||||||
|
id: {
|
||||||
|
type: new DataTypes.UUID(),
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
name: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
},
|
||||||
|
|
||||||
|
email: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
password: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sequelize,
|
||||||
|
tableName: "users",
|
||||||
|
|
||||||
|
paranoid: true, // softs deletes
|
||||||
|
timestamps: true,
|
||||||
|
//version: true,
|
||||||
|
|
||||||
|
createdAt: "created_at",
|
||||||
|
updatedAt: "updated_at",
|
||||||
|
deletedAt: "deleted_at",
|
||||||
|
|
||||||
|
indexes: [{ name: "email_idx", fields: ["email"] }],
|
||||||
|
|
||||||
|
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
|
||||||
|
scopes: {
|
||||||
|
quickSearch(value) {
|
||||||
|
return {
|
||||||
|
where: {
|
||||||
|
[Op.or]: {
|
||||||
|
name: {
|
||||||
|
[Op.like]: `%${value}%`,
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
[Op.like]: `%${value}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return User_Model;
|
||||||
|
};
|
||||||
@ -2,6 +2,7 @@ import { AuthRouter } from "@/contexts/auth";
|
|||||||
import { CatalogRouter } from "@/contexts/catalog";
|
import { CatalogRouter } from "@/contexts/catalog";
|
||||||
import { RepositoryManager } from "@/contexts/common/domain";
|
import { RepositoryManager } from "@/contexts/common/domain";
|
||||||
import { createSequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
import { createSequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||||
|
import { UserRouter } from "@/contexts/users";
|
||||||
import Express from "express";
|
import Express from "express";
|
||||||
|
|
||||||
export const v1Routes = () => {
|
export const v1Routes = () => {
|
||||||
@ -39,6 +40,7 @@ export const v1Routes = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
AuthRouter(routes);
|
AuthRouter(routes);
|
||||||
|
UserRouter(routes);
|
||||||
CatalogRouter(routes);
|
CatalogRouter(routes);
|
||||||
|
|
||||||
return routes;
|
return routes;
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
export * from "./common";
|
||||||
|
|
||||||
export * from "./auth";
|
export * from "./auth";
|
||||||
export * from "./catalog";
|
export * from "./catalog";
|
||||||
export * from "./common";
|
export * from "./users";
|
||||||
|
|||||||
@ -0,0 +1,5 @@
|
|||||||
|
export interface IListUsers_Response_DTO {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./IListUsers_Response.dto";
|
||||||
1
shared/lib/contexts/users/application/dto/index.ts
Normal file
1
shared/lib/contexts/users/application/dto/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./IListUsers.dto";
|
||||||
1
shared/lib/contexts/users/application/index.ts
Normal file
1
shared/lib/contexts/users/application/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./dto";
|
||||||
1
shared/lib/contexts/users/index.ts
Normal file
1
shared/lib/contexts/users/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./application";
|
||||||
Loading…
Reference in New Issue
Block a user