.
This commit is contained in:
parent
09ab90f923
commit
9264739af9
@ -16,15 +16,21 @@
|
||||
"author": "Rodax Software <dev@rodax-software.com>",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/cors": "^2.8.13",
|
||||
"@types/dinero.js": "^1.9.1",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/express-session": "^1.18.0",
|
||||
"@types/glob": "^8.1.0",
|
||||
"@types/jest": "^29.5.6",
|
||||
"@types/jsonwebtoken": "^9.0.6",
|
||||
"@types/luxon": "^3.3.1",
|
||||
"@types/module-alias": "^2.0.1",
|
||||
"@types/morgan": "^1.9.4",
|
||||
"@types/node": "^20.4.9",
|
||||
"@types/node": "^20.12.11",
|
||||
"@types/passport": "^1.0.16",
|
||||
"@types/passport-jwt": "^4.0.1",
|
||||
"@types/passport-local": "^1.0.38",
|
||||
"@types/response-time": "^2.3.5",
|
||||
"@types/supertest": "^2.0.11",
|
||||
"@types/validator": "^13.11.1",
|
||||
@ -50,6 +56,7 @@
|
||||
"dependencies": {
|
||||
"@joi/date": "^2.1.0",
|
||||
"@reis/joi-luxon": "^3.0.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"cls-rtracer": "^2.6.3",
|
||||
"cors": "^2.8.5",
|
||||
"cross-env": "5.0.5",
|
||||
@ -59,11 +66,15 @@
|
||||
"helmet": "^7.0.0",
|
||||
"joi": "^17.12.3",
|
||||
"joi-phone-number": "^5.1.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^3.4.0",
|
||||
"moment": "^2.29.4",
|
||||
"morgan": "^1.10.0",
|
||||
"mysql2": "^3.6.0",
|
||||
"passport": "^0.7.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"passport-local": "^1.0.0",
|
||||
"path": "^0.12.7",
|
||||
"remove": "^0.1.5",
|
||||
"response-time": "^2.3.2",
|
||||
|
||||
@ -1,4 +1,13 @@
|
||||
module.exports = {
|
||||
jwt: {
|
||||
secret_key:
|
||||
"9d6c903873c341816995a8be0355c6f0d6d471fc6aedacf50790e9b1e49c45b3",
|
||||
refresh_secret_key:
|
||||
"3972dc40c69327b65352ed097419213b0b75561169dba562410b85660bb1f305",
|
||||
token_expiration: "15m",
|
||||
refresh_token_expiration: "7d",
|
||||
},
|
||||
|
||||
database: {
|
||||
username: "rodax",
|
||||
password: "rodax",
|
||||
|
||||
@ -1,4 +1,11 @@
|
||||
module.exports = {
|
||||
jwt: {
|
||||
secret_key: "",
|
||||
refresh_secret_key: "",
|
||||
token_expiration: "15m",
|
||||
refresh_token_expiration: "7d",
|
||||
},
|
||||
|
||||
database: {
|
||||
username: "uecko",
|
||||
password: "",
|
||||
|
||||
94
server/src/contexts/auth/application/LoginUseCase.ts
Normal file
94
server/src/contexts/auth/application/LoginUseCase.ts
Normal file
@ -0,0 +1,94 @@
|
||||
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 {
|
||||
Email,
|
||||
ILogin_DTO,
|
||||
Result,
|
||||
ensureUserEmailIsValid,
|
||||
} from "@shared/contexts";
|
||||
import { User } from "../domain";
|
||||
import { IAuthRepository } from "../domain/repository";
|
||||
|
||||
export type LoginResponseOrError =
|
||||
| Result<never, IUseCaseError>
|
||||
| Result<User, never>;
|
||||
|
||||
export class LoginUseCase
|
||||
implements IUseCase<ILogin_DTO, Promise<LoginResponseOrError>>
|
||||
{
|
||||
private _adapter: ISequelizeAdapter;
|
||||
private _repositoryManager: IRepositoryManager;
|
||||
|
||||
constructor(props: {
|
||||
adapter: ISequelizeAdapter;
|
||||
repositoryManager: IRepositoryManager;
|
||||
}) {
|
||||
this._adapter = props.adapter;
|
||||
this._repositoryManager = props.repositoryManager;
|
||||
}
|
||||
|
||||
private getRepositoryByName<T>(name: string) {
|
||||
return this._repositoryManager.getRepository<T>(name);
|
||||
}
|
||||
|
||||
async execute(request: ILogin_DTO): Promise<LoginResponseOrError> {
|
||||
const { email, password } = request;
|
||||
|
||||
// Validaciones de datos
|
||||
|
||||
const emailOrError = ensureUserEmailIsValid(email);
|
||||
if (emailOrError.isFailure) {
|
||||
return Result.fail(
|
||||
handleUseCaseError(
|
||||
UseCaseError.INVALID_INPUT_DATA,
|
||||
"Email or password is not valid",
|
||||
emailOrError.error,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Crear auth
|
||||
try {
|
||||
const user = await this.findUserEmail(emailOrError.object);
|
||||
if (user === null || !user.verifyPassword(password)) {
|
||||
return Result.fail(
|
||||
handleUseCaseError(
|
||||
UseCaseError.INVALID_INPUT_DATA,
|
||||
"Email or password is not valid",
|
||||
),
|
||||
);
|
||||
}
|
||||
return Result.ok<User>(user);
|
||||
} catch (error: unknown) {
|
||||
const _error = error as IInfrastructureError;
|
||||
return Result.fail(
|
||||
handleUseCaseError(
|
||||
UseCaseError.REPOSITORY_ERROR,
|
||||
"Error al buscar el usuario",
|
||||
_error,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async findUserEmail(email: Email): Promise<User | null> {
|
||||
const transaction = this._adapter.startTransaction();
|
||||
const authRepoBuilder = this.getRepositoryByName<IAuthRepository>("Auth");
|
||||
|
||||
let user: User | null = null;
|
||||
|
||||
await transaction.complete(async (t) => {
|
||||
const authRepo = authRepoBuilder({ transaction: t });
|
||||
user = await authRepo.findByEmail(email);
|
||||
});
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
||||
1
server/src/contexts/auth/application/index.ts
Normal file
1
server/src/contexts/auth/application/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./authServices";
|
||||
95
server/src/contexts/auth/domain/entities/User.ts
Normal file
95
server/src/contexts/auth/domain/entities/User.ts
Normal file
@ -0,0 +1,95 @@
|
||||
import bCrypt from "bcryptjs";
|
||||
|
||||
import {
|
||||
AggregateRoot,
|
||||
Email,
|
||||
IDomainError,
|
||||
Result,
|
||||
UniqueID,
|
||||
} from "@shared/contexts";
|
||||
|
||||
export interface IUserProps {
|
||||
email: Email;
|
||||
password?: string;
|
||||
hashed_password?: string;
|
||||
}
|
||||
|
||||
export interface IUser {
|
||||
id: UniqueID;
|
||||
email: Email;
|
||||
hashed_password: string;
|
||||
|
||||
verifyPassword: (candidatePassword: string) => boolean;
|
||||
}
|
||||
|
||||
export class User extends AggregateRoot<IUserProps> implements IUser {
|
||||
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);
|
||||
|
||||
return Result.ok<User>(user);
|
||||
}
|
||||
|
||||
public static async hashPassword(password): Promise<string> {
|
||||
return hashPassword(password, await genSalt());
|
||||
}
|
||||
|
||||
private _hashed_password: string;
|
||||
|
||||
private constructor(props: IUserProps, id?: UniqueID) {
|
||||
super({ ...props, password: "", hashed_password: "" }, id);
|
||||
|
||||
this._protectPassword(props);
|
||||
}
|
||||
|
||||
get email(): Email {
|
||||
return this.props.email;
|
||||
}
|
||||
|
||||
get hashed_password(): string {
|
||||
return this._hashed_password;
|
||||
}
|
||||
|
||||
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!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
User.hashPassword("123456").then((value) => console.log(value));
|
||||
1
server/src/contexts/auth/domain/entities/index.ts
Normal file
1
server/src/contexts/auth/domain/entities/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./User";
|
||||
1
server/src/contexts/auth/domain/index.ts
Normal file
1
server/src/contexts/auth/domain/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./entities";
|
||||
@ -0,0 +1,8 @@
|
||||
import { IRepository } from "@/contexts/common/domain";
|
||||
import { Email, UniqueID } from "@shared/contexts";
|
||||
import { User } from "../entities";
|
||||
|
||||
export interface IAuthRepository extends IRepository<any> {
|
||||
getById(id: UniqueID): Promise<User | null>;
|
||||
findByEmail(email: Email): Promise<User | null>;
|
||||
}
|
||||
1
server/src/contexts/auth/domain/repository/index.ts
Normal file
1
server/src/contexts/auth/domain/repository/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./AuthRepository.interface";
|
||||
1
server/src/contexts/auth/index.ts
Normal file
1
server/src/contexts/auth/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./infrastructure";
|
||||
35
server/src/contexts/auth/infrastructure/Auth.context.ts
Normal file
35
server/src/contexts/auth/infrastructure/Auth.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 IAuthContext {
|
||||
adapter: ISequelizeAdapter;
|
||||
repositoryManager: IRepositoryManager;
|
||||
//services: IApplicationService;
|
||||
}
|
||||
|
||||
export class AuthContext {
|
||||
private static instance: AuthContext | null = null;
|
||||
|
||||
public static getInstance(): IAuthContext {
|
||||
if (!AuthContext.instance) {
|
||||
AuthContext.instance = new AuthContext({
|
||||
adapter: createSequelizeAdapter(),
|
||||
repositoryManager: RepositoryManager.getInstance(),
|
||||
});
|
||||
}
|
||||
|
||||
return AuthContext.instance.context;
|
||||
}
|
||||
|
||||
private context: IAuthContext;
|
||||
|
||||
private constructor(context: IAuthContext) {
|
||||
this.context = context;
|
||||
}
|
||||
}
|
||||
86
server/src/contexts/auth/infrastructure/Auth.repository.ts
Normal file
86
server/src/contexts/auth/infrastructure/Auth.repository.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import { IAuthContext } from "./Auth.context";
|
||||
|
||||
import {
|
||||
ISequelizeAdapter,
|
||||
SequelizeRepository,
|
||||
} from "@/contexts/common/infrastructure/sequelize";
|
||||
import { Email, ICollection, IQueryCriteria, UniqueID } from "@shared/contexts";
|
||||
import { Transaction } from "sequelize";
|
||||
import { User } from "../domain/entities";
|
||||
import { IAuthRepository } from "../domain/repository/AuthRepository.interface";
|
||||
import { IUserMapper, createUserMapper } from "./mappers/user.mapper";
|
||||
|
||||
export type QueryParams = {
|
||||
pagination: Record<string, any>;
|
||||
filters: Record<string, any>;
|
||||
};
|
||||
|
||||
export class AuthRepository
|
||||
extends SequelizeRepository<User>
|
||||
implements IAuthRepository
|
||||
{
|
||||
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 findByEmail(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 registerAuthRepository = (context: IAuthContext) => {
|
||||
const adapter = context.adapter;
|
||||
const repoManager = context.repositoryManager;
|
||||
|
||||
repoManager.registerRepository("Auth", (params = { transaction: null }) => {
|
||||
const { transaction } = params;
|
||||
|
||||
return new AuthRepository({
|
||||
transaction,
|
||||
adapter,
|
||||
mapper: createUserMapper(context),
|
||||
});
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,67 @@
|
||||
// Import the necessary packages and modules
|
||||
import { IServerError } from "@/contexts/common/domain/errors";
|
||||
import { ExpressController } from "@/contexts/common/infrastructure/express";
|
||||
import passport from "passport";
|
||||
|
||||
// Export a middleware function to authenticate incoming requests
|
||||
/*export const authenticate = (req, res, next) => {
|
||||
// Use Passport to authenticate the request using the "jwt" strategy
|
||||
passport.authenticate("jwt", { session: false }, (err, user) => {
|
||||
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();
|
||||
})(req, res, next);
|
||||
};*/
|
||||
|
||||
export class AuthenticateController extends ExpressController {
|
||||
//private context: AuthContext;
|
||||
|
||||
constructor() {
|
||||
//context: IAuthContext,
|
||||
super();
|
||||
|
||||
/*const { useCase, presenter } = props;
|
||||
this.useCase = useCase;
|
||||
this.presenter = presenter;
|
||||
this.context = context;*/
|
||||
}
|
||||
|
||||
async executeImpl() {
|
||||
try {
|
||||
return passport.authenticate(
|
||||
"local-jwt",
|
||||
{ session: false },
|
||||
(
|
||||
err: any,
|
||||
user?: Express.User | false | null,
|
||||
info?: object | string | Array<string | undefined>,
|
||||
status?: number | Array<number | undefined>,
|
||||
) => {
|
||||
if (err) {
|
||||
return this.next(err);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return this.unauthorizedError(
|
||||
"Unauthorized access. No token provided.",
|
||||
);
|
||||
}
|
||||
|
||||
// If the user is authenticated, attach the user object to the request and move on to the next middleware
|
||||
this.req.user = user;
|
||||
return this.next();
|
||||
},
|
||||
);
|
||||
} catch (e: unknown) {
|
||||
return this.fail(e as IServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,108 @@
|
||||
import { IUseCaseError, UseCaseError } from "@/contexts/common/application";
|
||||
import { IServerError } from "@/contexts/common/domain/errors";
|
||||
import {
|
||||
IInfrastructureError,
|
||||
InfrastructureError,
|
||||
handleInfrastructureError,
|
||||
} from "@/contexts/common/infrastructure";
|
||||
import { ExpressController } from "@/contexts/common/infrastructure/express";
|
||||
import { ILogin_DTO, ensureLogin_DTOIsValid } from "@shared/contexts";
|
||||
|
||||
export class LoginController extends ExpressController {
|
||||
private useCase: LoginUseCase;
|
||||
private presenter: ILoginPresenter;
|
||||
private context: IAuthContext;
|
||||
|
||||
constructor(
|
||||
props: {
|
||||
useCase: LoginUseCase;
|
||||
presenter: ILoginPresenter;
|
||||
},
|
||||
context: IAuthContext,
|
||||
) {
|
||||
super();
|
||||
|
||||
const { useCase, presenter } = props;
|
||||
this.useCase = useCase;
|
||||
this.presenter = presenter;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
async executeImpl() {
|
||||
try {
|
||||
const loginDTO: ILogin_DTO = this.req.body;
|
||||
|
||||
// Validaciones de DTO
|
||||
const loginDTOOrError = ensureLogin_DTOIsValid(loginDTO);
|
||||
|
||||
if (loginDTOOrError.isFailure) {
|
||||
const errorMessage = "Login data not valid";
|
||||
const infraError = handleInfrastructureError(
|
||||
InfrastructureError.INVALID_INPUT_DATA,
|
||||
errorMessage,
|
||||
loginDTOOrError.error,
|
||||
);
|
||||
return this.invalidInputError(errorMessage, infraError);
|
||||
}
|
||||
|
||||
const result = await this.useCase.execute();
|
||||
|
||||
if (result.isFailure) {
|
||||
return this._handleExecuteError(result.error);
|
||||
}
|
||||
|
||||
console.log("login OK => generate token JWT");
|
||||
|
||||
const customer = <Customer>result.object;
|
||||
|
||||
return this.created<ICreateCustomer_Response_DTO>(
|
||||
this.presenter.map(customer, this.context),
|
||||
);
|
||||
} catch (e: unknown) {
|
||||
return this.fail(e as IServerError);
|
||||
}
|
||||
}
|
||||
|
||||
private _handleExecuteError(error: IUseCaseError) {
|
||||
let errorMessage: string;
|
||||
let infraError: IInfrastructureError;
|
||||
|
||||
switch (error.code) {
|
||||
case UseCaseError.INVALID_INPUT_DATA:
|
||||
errorMessage = "Login data not valid";
|
||||
infraError = handleInfrastructureError(
|
||||
InfrastructureError.INVALID_INPUT_DATA,
|
||||
errorMessage,
|
||||
error,
|
||||
);
|
||||
return this.invalidInputError(errorMessage, infraError);
|
||||
break;
|
||||
|
||||
case UseCaseError.NOT_FOUND_ERROR:
|
||||
errorMessage = "User not found";
|
||||
|
||||
infraError = handleInfrastructureError(
|
||||
InfrastructureError.INVALID_INPUT_DATA,
|
||||
errorMessage,
|
||||
error,
|
||||
);
|
||||
return this.conflictError(error.message, error);
|
||||
break;
|
||||
|
||||
case UseCaseError.UNEXCEPTED_ERROR:
|
||||
errorMessage = error.message;
|
||||
|
||||
infraError = handleInfrastructureError(
|
||||
InfrastructureError.UNEXCEPTED_ERROR,
|
||||
errorMessage,
|
||||
error,
|
||||
);
|
||||
return this.internalServerError(errorMessage, infraError);
|
||||
break;
|
||||
|
||||
default:
|
||||
errorMessage = error.message;
|
||||
return this.clientError(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
import { AuthenticateController } from "./AuthenticateController";
|
||||
|
||||
const authenticate = (context: any) => {
|
||||
//const adapter = context.adapter;
|
||||
//const repoManager = context.repositoryManager;
|
||||
|
||||
/*repoManager.registerRepository("Auth", (params = { transaction: null }) => {
|
||||
const { transaction } = params;
|
||||
|
||||
return new AuthRepository({
|
||||
transaction,
|
||||
adapter,
|
||||
mapper: createAuthMapper(context),
|
||||
});
|
||||
});*/
|
||||
|
||||
//const listArticlesUseCase = new ListArticlesUseCase(context);
|
||||
|
||||
return new AuthenticateController();
|
||||
};
|
||||
3
server/src/contexts/auth/infrastructure/express/index.ts
Normal file
3
server/src/contexts/auth/infrastructure/express/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from "./controllers";
|
||||
export * from "./passport";
|
||||
export * from "./routes";
|
||||
@ -0,0 +1,34 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
import Express from "express";
|
||||
import passport from "passport";
|
||||
|
||||
export const authenticate = (
|
||||
req: Express.Request,
|
||||
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?: Express.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();
|
||||
},
|
||||
)(req, res, next);
|
||||
};
|
||||
@ -0,0 +1,11 @@
|
||||
import { PassportStatic } from "passport";
|
||||
import { AuthContext } from "../../Auth.context";
|
||||
import { initEmailStrategy } from "./emailStrategy";
|
||||
import { jwtStrategy } from "./jwtStrategy";
|
||||
|
||||
// Export a function that will be used to configure Passport authentication
|
||||
export const configurePassportAuth = (passport: PassportStatic) => {
|
||||
console.log("passport: configuring strategies !!!!!!!!!!!!!!!!!!");
|
||||
passport.use("local-email", initEmailStrategy(AuthContext.getInstance()));
|
||||
passport.use("local-jwt", jwtStrategy);
|
||||
};
|
||||
@ -0,0 +1,69 @@
|
||||
import { LoginUseCase } from "@/contexts/auth/application/LoginUseCase";
|
||||
import { IServerError } from "@/contexts/common/domain/errors";
|
||||
import { PassportStrategyController } from "@/contexts/common/infrastructure/express";
|
||||
import { ensureLogin_DTOIsValid } from "@shared/contexts";
|
||||
import { Strategy as EmailStrategy, IVerifyOptions } from "passport-local";
|
||||
|
||||
import { IAuthContext } from "../../Auth.context";
|
||||
import { registerAuthRepository } from "../../Auth.repository";
|
||||
|
||||
const strategyOpts = {
|
||||
usernameField: "email", // Campo utilizado para el email en el formulario
|
||||
passwordField: "password",
|
||||
};
|
||||
|
||||
class EmailStrategyController extends PassportStrategyController {
|
||||
private useCase: LoginUseCase;
|
||||
private context: IAuthContext;
|
||||
|
||||
constructor(
|
||||
props: {
|
||||
useCase: LoginUseCase;
|
||||
},
|
||||
context: any,
|
||||
) {
|
||||
super();
|
||||
|
||||
const { useCase } = props;
|
||||
this.useCase = useCase;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public async verifyStrategy(
|
||||
email: string,
|
||||
password: string,
|
||||
done: (
|
||||
error: any,
|
||||
user?: Express.User | false,
|
||||
options?: IVerifyOptions,
|
||||
) => void,
|
||||
) {
|
||||
const loginDTOOrError = ensureLogin_DTOIsValid({ email, password });
|
||||
|
||||
if (loginDTOOrError.isFailure) {
|
||||
return done(null, false);
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await this.useCase.execute({ email, password });
|
||||
if (result.isFailure) {
|
||||
return done(null, false);
|
||||
}
|
||||
|
||||
return done(null, result.object);
|
||||
} catch (e: unknown) {
|
||||
return done(e as IServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const initEmailStrategy = (context: IAuthContext) =>
|
||||
new EmailStrategy(strategyOpts, async (...params) => {
|
||||
registerAuthRepository(context);
|
||||
return new EmailStrategyController(
|
||||
{
|
||||
useCase: new LoginUseCase(context),
|
||||
},
|
||||
context,
|
||||
).verifyStrategy(...params);
|
||||
});
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./authenticate";
|
||||
export * from "./configurePassportAuth";
|
||||
@ -0,0 +1,27 @@
|
||||
import { config } from "@/config";
|
||||
import { ExtractJwt, Strategy as JWTStrategy } from "passport-jwt";
|
||||
|
||||
const strategyOpts = {
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), // Extract the JWT from the Authorization header
|
||||
secretOrKey: config.jwt.secret_key,
|
||||
};
|
||||
|
||||
export const jwtStrategy = new JWTStrategy(
|
||||
strategyOpts,
|
||||
async (jwt_payload, done) => {
|
||||
console.log(
|
||||
"PASSPORT USE LOCAL-JWT !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!",
|
||||
);
|
||||
console.log(jwt_payload);
|
||||
|
||||
/*
|
||||
const user = await authenticateUserByEmail("assa@asds.com", "password");
|
||||
if (user) {
|
||||
return done(null, user);
|
||||
} else {
|
||||
return done(null, false);
|
||||
}
|
||||
*/
|
||||
return done(null, { id: "xzxxxxx", email: "assa@asds.com" });
|
||||
},
|
||||
);
|
||||
@ -0,0 +1,184 @@
|
||||
import {
|
||||
InfrastructureError,
|
||||
handleInfrastructureError,
|
||||
} from "@/contexts/common/infrastructure";
|
||||
import { ensureLogin_DTOIsValid } from "@shared/contexts";
|
||||
import passport from "passport";
|
||||
import { ExtractJwt, Strategy as JwtStrategy } from "passport-jwt";
|
||||
import { Strategy as LocalStrategy } from "passport-local";
|
||||
|
||||
/*declare global {
|
||||
namespace Express {
|
||||
interface User {
|
||||
id?: string;
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
//import { authenticateUserByEmail, deserializeUserById } from './../../../services';
|
||||
const authenticateUserByEmail = async (
|
||||
email: string,
|
||||
password: string,
|
||||
): Promise<Express.User | Error> => {
|
||||
// Simulación de búsqueda del usuario en la base de datos
|
||||
const user = {
|
||||
id: "1",
|
||||
email: "usuario@example.com",
|
||||
password: "$2a$10$EdfkgQ7vMTGvXq1qI7qJQ.g3WiYGqCSCdzhZ/LG20YKwA1YjgLJnO",
|
||||
}; // Contraseña hasheada: "password"
|
||||
|
||||
return user;
|
||||
|
||||
/*const res = await pool.query('SELECT * FROM users WHERE email = $1', [email]);
|
||||
|
||||
if (res.rows.length) {
|
||||
const user = res.rows[0];
|
||||
const match = await bcrypt.compare(password, user.password);
|
||||
|
||||
if (match) {
|
||||
return user;
|
||||
} else {
|
||||
throw new Error('Incorrect email and/or password');
|
||||
}
|
||||
} else {
|
||||
throw new Error('User not found');
|
||||
}*/
|
||||
};
|
||||
|
||||
const deserializeUserById = async (
|
||||
id: number,
|
||||
): Promise<Express.User | Error> => {
|
||||
/*const res = await pool.query('SELECT * FROM users WHERE id = $1', [id]);
|
||||
|
||||
if (res.rows.length) {
|
||||
return res.rows[0];
|
||||
} else {
|
||||
throw new Error('User was not found');
|
||||
}*/
|
||||
|
||||
const user = {
|
||||
id: "1",
|
||||
email: "usuario@example.com",
|
||||
password: "$2a$10$EdfkgQ7vMTGvXq1qI7qJQ.g3WiYGqCSCdzhZ/LG20YKwA1YjgLJnO",
|
||||
}; // Contraseña hasheada: "password"
|
||||
|
||||
return user;
|
||||
};
|
||||
|
||||
// Configurar la estrategia de autenticación local de Passport.js
|
||||
passport.use(
|
||||
"local-email",
|
||||
new LocalStrategy(
|
||||
{
|
||||
usernameField: "email", // Campo utilizado para el email en el formulario
|
||||
passwordField: "password",
|
||||
},
|
||||
async (email, password, done) => {
|
||||
console.log("local-email");
|
||||
|
||||
const loginDTO = { email, password };
|
||||
const loginDTOOrError = ensureLogin_DTOIsValid(loginDTO);
|
||||
|
||||
if (loginDTOOrError.isFailure) {
|
||||
const errorMessage = "Login data not valid";
|
||||
const infraError = handleInfrastructureError(
|
||||
InfrastructureError.INVALID_INPUT_DATA,
|
||||
errorMessage,
|
||||
loginDTOOrError.error,
|
||||
);
|
||||
done(infraError);
|
||||
}
|
||||
|
||||
const result = await this.useCase.execute();
|
||||
|
||||
try {
|
||||
// Buscar el usuario en la base de datos por email
|
||||
const user = await authenticateUserByEmail(email, password);
|
||||
return done(null, user as Express.User);
|
||||
} catch (error) {
|
||||
return done(error);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
passport.use(
|
||||
"local-jwt",
|
||||
new JwtStrategy(
|
||||
{
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
secretOrKey: "secret",
|
||||
},
|
||||
async (jwt_payload, done) => {
|
||||
console.log(jwt_payload);
|
||||
|
||||
const user = await authenticateUserByEmail("assa@asds.com", "password");
|
||||
return done(null, user);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
/*passport.use(
|
||||
"jwt2",
|
||||
new CustomStrategy(async (req, done) => {
|
||||
const token =
|
||||
req && req.headers && req.headers["x-access-token"]
|
||||
? req.headers["x-access-token"]
|
||||
: null;
|
||||
const appVersion =
|
||||
req && req.headers && req.headers["accept-version"]
|
||||
? req.headers["accept-version"]
|
||||
: null;
|
||||
console.log("appVersion: ", appVersion);
|
||||
|
||||
if (!token) {
|
||||
console.error("Unauthorized. Token missing.");
|
||||
return done(null, false, { message: "Unauthorized. Token missing." });
|
||||
}
|
||||
|
||||
const result = securityHelper.verify(token);
|
||||
//console.log('token result => ', result);
|
||||
|
||||
if (result && result.id) {
|
||||
//recuperamos el usuario de la petición
|
||||
let user = await authService.extraMethods.findUser({ id: result.id });
|
||||
if (user) {
|
||||
user = user.toJSON();
|
||||
userService._updateLastLoginAndVersionUser(user.id, appVersion);
|
||||
user.app_version = appVersion;
|
||||
user.token = token;
|
||||
delete user.password;
|
||||
|
||||
console.log("Logged in Successfully");
|
||||
console.log(user);
|
||||
return done(null, user, { message: "Logged in Successfully" });
|
||||
} else {
|
||||
console.error("Unauthorized. User not found.");
|
||||
return done(null, false, { message: "Unauthorized. User not found." });
|
||||
}
|
||||
} else {
|
||||
//console.log('Token no válido');
|
||||
console.error("Unauthorized. Invalid token.");
|
||||
return done(null, false, { message: "Unauthorized. Invalid token." });
|
||||
}
|
||||
}),
|
||||
);*/
|
||||
|
||||
// Serializar y deserializar el usuario para almacenar y recuperar la sesión
|
||||
passport.serializeUser((user: Express.User, done) => {
|
||||
done(null, user.id);
|
||||
});
|
||||
|
||||
passport.deserializeUser(async (id: number, done) => {
|
||||
try {
|
||||
const user = await deserializeUserById(id);
|
||||
|
||||
done(null, user as Express.User);
|
||||
} catch (error) {
|
||||
done(error, null);
|
||||
}
|
||||
});
|
||||
|
||||
export { passport };
|
||||
88
server/src/contexts/auth/infrastructure/express/routes.ts
Normal file
88
server/src/contexts/auth/infrastructure/express/routes.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import { config } from "@/config";
|
||||
import Express from "express";
|
||||
import JWT from "jsonwebtoken";
|
||||
import passport from "passport";
|
||||
import { User } from "../../domain";
|
||||
|
||||
/*authRoutes.post(
|
||||
"/login",
|
||||
passport.authenticate("local-email"),
|
||||
|
||||
(req: Express.Request, res: Express.Response, next: Express.NextFunction) => {
|
||||
console.log("login OK => generate token JWT");
|
||||
|
||||
// Generar token JWT
|
||||
const token = JWT.sign({ userId: req.user?.id }, "clave_secreta", {
|
||||
expiresIn: "1h",
|
||||
}); // Clave secreta y expiración de 1 hora
|
||||
|
||||
// Enviar token como respuesta
|
||||
res.json({ token });
|
||||
},
|
||||
);
|
||||
|
||||
authRoutes.post("/logout", passport.authenticate("local-jwt"));
|
||||
|
||||
authRoutes.get(
|
||||
"/profile",
|
||||
passport.authenticate("local-jwt", { session: false }),
|
||||
(req: Express.Request, res: Express.Response, next: Express.NextFunction) => {
|
||||
res.json({
|
||||
message: "You made it to the secure route",
|
||||
user: req.user,
|
||||
token: req.query.secret_token,
|
||||
});
|
||||
},
|
||||
);*/
|
||||
|
||||
//export { authRouter };
|
||||
|
||||
export const AuthRouter = (appRouter: Express.Router) => {
|
||||
const authRoutes: Express.Router = Express.Router({ mergeParams: true });
|
||||
|
||||
//appRouter.use(registerMiddleware("authenticate", authenticate));
|
||||
|
||||
authRoutes.post(
|
||||
"/login",
|
||||
passport.authenticate("local-email", { session: false }),
|
||||
(req, res, next) => {
|
||||
if (req.isAuthenticated()) {
|
||||
const user: User = req.user;
|
||||
|
||||
const accessToken = JWT.sign(
|
||||
{ id: user.id, email: user.email },
|
||||
config.jwt.secret_key,
|
||||
{ expiresIn: config.jwt.token_expiration },
|
||||
);
|
||||
const refreshToken = JWT.sign(
|
||||
{ id: user.id, email: user.email },
|
||||
config.jwt.refresh_secret_key,
|
||||
{ expiresIn: config.jwt.refresh_token_expiration },
|
||||
);
|
||||
|
||||
//refreshTokens.push(refreshToken);
|
||||
|
||||
return res.json({ accessToken, refreshToken });
|
||||
}
|
||||
return res.status(401).json({});
|
||||
},
|
||||
);
|
||||
|
||||
authRoutes.post(
|
||||
"/login2",
|
||||
(req: Express.Request, res: Express.Response, next: Express.NextFunction) =>
|
||||
passport.authenticate(
|
||||
"local-email",
|
||||
{ session: false },
|
||||
(err, user, info) => {
|
||||
console.log(err, user, info);
|
||||
next(err);
|
||||
},
|
||||
)(req, res, next),
|
||||
(req, res, next) => {
|
||||
res.status(200).json({});
|
||||
},
|
||||
);
|
||||
|
||||
appRouter.use("/auth", authRoutes);
|
||||
};
|
||||
2
server/src/contexts/auth/infrastructure/index.ts
Normal file
2
server/src/contexts/auth/infrastructure/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./express";
|
||||
export * from "./sequelize";
|
||||
1
server/src/contexts/auth/infrastructure/mappers/index.ts
Normal file
1
server/src/contexts/auth/infrastructure/mappers/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./user.mapper";
|
||||
@ -0,0 +1,52 @@
|
||||
import {
|
||||
ISequelizeMapper,
|
||||
SequelizeMapper,
|
||||
} from "@/contexts/common/infrastructure";
|
||||
import { Email, UniqueID } from "@shared/contexts";
|
||||
import { IUserProps, User } from "../../domain/entities";
|
||||
import { IAuthContext } from "../Auth.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: IAuthContext }) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
protected toDomainMappingImpl(source: User_Model, params: any): User {
|
||||
const props: IUserProps = {
|
||||
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(),
|
||||
email: source.email.toPrimitive(),
|
||||
password: source.hashed_password,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const createUserMapper = (context: IAuthContext): IUserMapper =>
|
||||
new UserMapper({
|
||||
context,
|
||||
});
|
||||
@ -0,0 +1 @@
|
||||
export * from "./user.model";
|
||||
@ -0,0 +1,61 @@
|
||||
import {
|
||||
DataTypes,
|
||||
InferAttributes,
|
||||
InferCreationAttributes,
|
||||
Model,
|
||||
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 email: string;
|
||||
declare password: string;
|
||||
}
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
User_Model.init(
|
||||
{
|
||||
id: {
|
||||
type: new DataTypes.UUID(),
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
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", unique: true, fields: ["email"] }],
|
||||
},
|
||||
);
|
||||
|
||||
return User_Model;
|
||||
};
|
||||
1
server/src/contexts/catalog/index.ts
Normal file
1
server/src/contexts/catalog/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./infrastructure/express";
|
||||
@ -4,9 +4,10 @@ import {
|
||||
} from "@/contexts/common/infrastructure/sequelize";
|
||||
import { ICollection, IQueryCriteria, UniqueID } from "@shared/contexts";
|
||||
import { Transaction } from "sequelize";
|
||||
import { ICatalogContext } from ".";
|
||||
import { Article } from "../domain/entities";
|
||||
import { ICatalogRepository } from "../domain/repository/CatalogRepository.interface";
|
||||
import { IArticleMapper } from "./mappers/article.mapper";
|
||||
import { IArticleMapper, createArticleMapper } from "./mappers/article.mapper";
|
||||
|
||||
export type QueryParams = {
|
||||
pagination: Record<string, any>;
|
||||
@ -40,11 +41,11 @@ export class CatalogRepository
|
||||
}
|
||||
|
||||
public async findAll(
|
||||
queryCriteria?: IQueryCriteria
|
||||
queryCriteria?: IQueryCriteria,
|
||||
): Promise<ICollection<any>> {
|
||||
const { rows, count } = await this._findAll(
|
||||
"Article_Model",
|
||||
queryCriteria
|
||||
queryCriteria,
|
||||
/*{
|
||||
include: [], // esto es para quitar las asociaciones al hacer la consulta
|
||||
}*/
|
||||
@ -53,3 +54,21 @@ export class CatalogRepository
|
||||
return this.mapper.mapArrayAndCountToDomain(rows, count);
|
||||
}
|
||||
}
|
||||
|
||||
export const registerCatalogRepository = (context: ICatalogContext) => {
|
||||
const adapter = context.adapter;
|
||||
const repoManager = context.repositoryManager;
|
||||
|
||||
repoManager.registerRepository(
|
||||
"Article",
|
||||
(params = { transaction: null }) => {
|
||||
const { transaction } = params;
|
||||
|
||||
return new CatalogRepository({
|
||||
transaction,
|
||||
adapter,
|
||||
mapper: createArticleMapper(context),
|
||||
});
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,40 +0,0 @@
|
||||
import express, { NextFunction, Request, Response, Router } from "express";
|
||||
|
||||
import { RepositoryManager } from "@/contexts/common/domain";
|
||||
import { createSequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||
import { createListArticlesController } from "./controllers";
|
||||
|
||||
const catalogRouter: Router = express.Router({ mergeParams: true });
|
||||
|
||||
const logMiddleware = (req, res, next) => {
|
||||
console.log(
|
||||
`[${new Date().toLocaleTimeString()}] Incoming request to ${req.path}`
|
||||
);
|
||||
next();
|
||||
};
|
||||
|
||||
catalogRouter.use(logMiddleware);
|
||||
|
||||
const contextMiddleware = (req: Request, res: Response, next: NextFunction) => {
|
||||
res.locals["context"] = {
|
||||
adapter: createSequelizeAdapter(),
|
||||
repositoryManager: RepositoryManager.getInstance(),
|
||||
services: {},
|
||||
};
|
||||
|
||||
return next();
|
||||
};
|
||||
|
||||
catalogRouter.use(contextMiddleware);
|
||||
|
||||
catalogRouter.get("/", (req: Request, res: Response, next: NextFunction) =>
|
||||
createListArticlesController(res.locals["context"]).execute(req, res, next)
|
||||
);
|
||||
|
||||
/*catalogRouter.get(
|
||||
"/:articleId",
|
||||
(req: Request, res: Response, next: NextFunction) =>
|
||||
createGetCustomerController(res.locals["context"]).execute(req, res, next)
|
||||
);*/
|
||||
|
||||
export { catalogRouter };
|
||||
@ -29,7 +29,7 @@ export class ListArticlesController extends ExpressController {
|
||||
useCase: ListArticlesUseCase;
|
||||
presenter: IListArticlesPresenter;
|
||||
},
|
||||
context: ICatalogContext
|
||||
context: ICatalogContext,
|
||||
) {
|
||||
super();
|
||||
|
||||
@ -79,7 +79,7 @@ export class ListArticlesController extends ExpressController {
|
||||
this.presenter.mapArray(customers, this.context, {
|
||||
page: queryCriteria.pagination.offset,
|
||||
limit: queryCriteria.pagination.limit,
|
||||
})
|
||||
}),
|
||||
);
|
||||
} catch (e: unknown) {
|
||||
return this.fail(e as IServerError);
|
||||
|
||||
@ -1,34 +1,18 @@
|
||||
import { ListArticlesUseCase } from "@/contexts/catalog/application";
|
||||
import { ICatalogContext } from "../../..";
|
||||
import { CatalogRepository } from "../../../Catalog.repository";
|
||||
import { createArticleMapper } from "../../../mappers/article.mapper";
|
||||
import { registerCatalogRepository } from "../../../Catalog.repository";
|
||||
import { ListArticlesController } from "./ListArticlesController";
|
||||
import { listArticlesPresenter } from "./presenter";
|
||||
|
||||
export const createListArticlesController = (context: ICatalogContext) => {
|
||||
const adapter = context.adapter;
|
||||
const repoManager = context.repositoryManager;
|
||||
|
||||
repoManager.registerRepository(
|
||||
"Article",
|
||||
(params = { transaction: null }) => {
|
||||
const { transaction } = params;
|
||||
|
||||
return new CatalogRepository({
|
||||
transaction,
|
||||
adapter,
|
||||
mapper: createArticleMapper(context),
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const listArticlesUseCase = new ListArticlesUseCase(context);
|
||||
registerCatalogRepository(context);
|
||||
|
||||
return new ListArticlesController(
|
||||
{
|
||||
useCase: listArticlesUseCase,
|
||||
presenter: listArticlesPresenter,
|
||||
},
|
||||
context
|
||||
context,
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
export * from "./catalogRoutes";
|
||||
export * from "./controllers";
|
||||
export * from "./routes";
|
||||
|
||||
26
server/src/contexts/catalog/infrastructure/express/routes.ts
Normal file
26
server/src/contexts/catalog/infrastructure/express/routes.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { applyMiddleware } from "@/contexts/common/infrastructure/express";
|
||||
import Express from "express";
|
||||
import { createListArticlesController } from "./controllers";
|
||||
|
||||
/*catalogRoutes.get(
|
||||
"/:articleId",
|
||||
(req: Request, res: Response, next: NextFunction) =>
|
||||
createGetCustomerController(res.locals["context"]).execute(req, res, next)
|
||||
);*/
|
||||
|
||||
export const CatalogRouter = (appRouter: Express.Router) => {
|
||||
const catalogRoutes: Express.Router = Express.Router({ mergeParams: true });
|
||||
|
||||
catalogRoutes.get(
|
||||
"/",
|
||||
applyMiddleware("authenticate"),
|
||||
(req: Express.Request, res: Express.Response, next: Express.NextFunction) =>
|
||||
createListArticlesController(res.locals["context"]).execute(
|
||||
req,
|
||||
res,
|
||||
next,
|
||||
),
|
||||
);
|
||||
|
||||
appRouter.use("/catalog", catalogRoutes);
|
||||
};
|
||||
@ -7,3 +7,5 @@ export interface ICatalogContext {
|
||||
repositoryManager: IRepositoryManager;
|
||||
services: IApplicationService;
|
||||
}
|
||||
|
||||
export * from "./express";
|
||||
|
||||
@ -80,7 +80,7 @@ export default (sequelize: Sequelize) => {
|
||||
{ name: "updated_at_idx", fields: ["updated_at"] },
|
||||
],
|
||||
|
||||
whereMergeStrategy: "and",
|
||||
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
|
||||
scopes: {
|
||||
quickSearch(value) {
|
||||
return {
|
||||
|
||||
@ -13,7 +13,7 @@ export class UseCaseError extends ServerError implements IUseCaseError {
|
||||
public static create(
|
||||
code: string,
|
||||
message: string,
|
||||
details?: Record<string, any>
|
||||
details?: Record<string, any>,
|
||||
): UseCaseError {
|
||||
return new UseCaseError(code, message, details);
|
||||
}
|
||||
@ -22,60 +22,7 @@ export class UseCaseError extends ServerError implements IUseCaseError {
|
||||
export function handleUseCaseError(
|
||||
code: string,
|
||||
message: string,
|
||||
payload?: Record<string, any>
|
||||
payload?: Record<string, any>,
|
||||
): IUseCaseError {
|
||||
return UseCaseError.create(code, message, payload);
|
||||
}
|
||||
|
||||
/*export function handleNotFoundError(
|
||||
message: string,
|
||||
validationError: Error,
|
||||
details?: Record<string, any>
|
||||
): Result<never, IUseCaseError> {
|
||||
return handleUseCaseError(
|
||||
UseCaseError.NOT_FOUND_ERROR,
|
||||
message,
|
||||
validationError,
|
||||
details
|
||||
);
|
||||
}
|
||||
|
||||
export function handleInvalidInputDataError(
|
||||
message: string,
|
||||
validationError: Error,
|
||||
details?: Record<string, any>
|
||||
): Result<never, IUseCaseError> {
|
||||
return handleUseCaseError(
|
||||
UseCaseError.INVALID_INPUT_DATA,
|
||||
message,
|
||||
validationError,
|
||||
details
|
||||
);
|
||||
}
|
||||
|
||||
export function handleResourceAlreadyExitsError(
|
||||
message: string,
|
||||
validationError: Error,
|
||||
details?: Record<string, any>
|
||||
): Result<never, IUseCaseError> {
|
||||
return handleUseCaseError(
|
||||
UseCaseError.RESOURCE_ALREADY_EXITS,
|
||||
message,
|
||||
validationError,
|
||||
details
|
||||
);
|
||||
}
|
||||
|
||||
export function handleRepositoryError(
|
||||
message: string,
|
||||
repositoryError: Error,
|
||||
details?: Record<string, any>
|
||||
): Result<never, IUseCaseError> {
|
||||
return handleUseCaseError(
|
||||
UseCaseError.REPOSITORY_ERROR,
|
||||
message,
|
||||
repositoryError,
|
||||
details
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
@ -0,0 +1 @@
|
||||
export interface IController {}
|
||||
@ -7,11 +7,10 @@ import {
|
||||
} from "@shared/contexts";
|
||||
import { UseCaseError } from "../../application";
|
||||
import { IServerError } from "../../domain/errors";
|
||||
import { IController } from "../Controller.interface";
|
||||
import { InfrastructureError } from "../InfrastructureError";
|
||||
import { ProblemDocument, ProblemDocumentExtension } from "./ProblemDocument";
|
||||
|
||||
export interface IController {}
|
||||
|
||||
export abstract class ExpressController implements IController {
|
||||
protected req: express.Request;
|
||||
protected res: express.Response;
|
||||
@ -25,7 +24,7 @@ export abstract class ExpressController implements IController {
|
||||
public execute(
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
next: express.NextFunction
|
||||
next: express.NextFunction,
|
||||
): void {
|
||||
this.req = req;
|
||||
this.res = res;
|
||||
@ -33,7 +32,7 @@ export abstract class ExpressController implements IController {
|
||||
|
||||
this.serverURL = `${
|
||||
new URL(
|
||||
`${this.req.protocol}://${this.req.get("host")}${this.req.originalUrl}`
|
||||
`${this.req.protocol}://${this.req.get("host")}${this.req.originalUrl}`,
|
||||
).origin
|
||||
}/api/v1`;
|
||||
|
||||
@ -121,7 +120,7 @@ export abstract class ExpressController implements IController {
|
||||
|
||||
private _jsonResponse(
|
||||
statusCode: number,
|
||||
jsonPayload: any
|
||||
jsonPayload: any,
|
||||
): express.Response<any> {
|
||||
return this.res.status(statusCode).json(jsonPayload).send();
|
||||
}
|
||||
@ -129,7 +128,7 @@ export abstract class ExpressController implements IController {
|
||||
private _errorResponse(
|
||||
statusCode: number,
|
||||
message?: string,
|
||||
error?: Error | InfrastructureError
|
||||
error?: Error | InfrastructureError,
|
||||
): express.Response<IError_Response_DTO> {
|
||||
const context = {};
|
||||
|
||||
@ -164,13 +163,13 @@ export abstract class ExpressController implements IController {
|
||||
detail: message,
|
||||
instance: this.req.baseUrl,
|
||||
},
|
||||
extension
|
||||
)
|
||||
extension,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private _processError(
|
||||
error: Error | InfrastructureError
|
||||
error: Error | InfrastructureError,
|
||||
): IErrorExtra_Response_DTO {
|
||||
/**
|
||||
*
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
import { IController } from "../Controller.interface";
|
||||
|
||||
export abstract class PassportStrategyController implements IController {}
|
||||
@ -1 +1,3 @@
|
||||
export * from './ExpressController';
|
||||
export * from "./ExpressController";
|
||||
export * from "./PassportStrategyController";
|
||||
export * from "./middlewares";
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
import Express from "express";
|
||||
|
||||
const registeredMiddlewares: Record<string, Express.RequestHandler> = {};
|
||||
|
||||
function registerMiddleware(name: string, middleware: Express.RequestHandler) {
|
||||
return (
|
||||
req: Express.Request,
|
||||
res: Express.Response,
|
||||
next: Express.NextFunction,
|
||||
) => {
|
||||
registeredMiddlewares[name] = middleware;
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
function applyMiddleware(middlewares: string | Array<string>) {
|
||||
const middlewareNames =
|
||||
typeof middlewares === "string" ? [middlewares] : middlewares;
|
||||
|
||||
return (
|
||||
req: Express.Request,
|
||||
res: Express.Response,
|
||||
next: Express.NextFunction,
|
||||
) => {
|
||||
middlewareNames.forEach((name) => {
|
||||
const middleware = registeredMiddlewares[name];
|
||||
if (middleware) {
|
||||
middleware(req, res, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export { applyMiddleware, registerMiddleware };
|
||||
@ -1,3 +1,4 @@
|
||||
export * from "./ContextFactory";
|
||||
export * from "./Controller.interface";
|
||||
export * from "./InfrastructureError";
|
||||
export * from "./mappers";
|
||||
|
||||
@ -1,12 +1,45 @@
|
||||
import { catalogRouter } from "@/contexts/catalog/infrastructure/express/catalogRoutes";
|
||||
import express from "express";
|
||||
import { AuthRouter } from "@/contexts/auth";
|
||||
import { CatalogRouter } from "@/contexts/catalog";
|
||||
import { RepositoryManager } from "@/contexts/common/domain";
|
||||
import { createSequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||
import Express from "express";
|
||||
|
||||
const v1Router = express.Router({ mergeParams: true });
|
||||
export const v1Routes = () => {
|
||||
const routes = Express.Router({ mergeParams: true });
|
||||
|
||||
v1Router.get("/hello", (req, res) => {
|
||||
res.send("Hello world!");
|
||||
});
|
||||
routes.get("/hello", (req, res) => {
|
||||
res.send("Hello world!");
|
||||
});
|
||||
|
||||
v1Router.use("/catalog", catalogRouter);
|
||||
//v1Routes.use("/auth", authRoutes);
|
||||
//v1Routes.use("/catalog", catalogRoutes);
|
||||
|
||||
export { v1Router };
|
||||
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>();
|
||||
|
||||
return next();
|
||||
},
|
||||
);
|
||||
routes.use((req, res, next) => {
|
||||
console.log(
|
||||
`[${new Date().toLocaleTimeString()}] Incoming request to ${req.path}`,
|
||||
);
|
||||
next();
|
||||
});
|
||||
|
||||
AuthRouter(routes);
|
||||
CatalogRouter(routes);
|
||||
|
||||
return routes;
|
||||
};
|
||||
|
||||
@ -4,9 +4,11 @@ import express from "express";
|
||||
import helmet from "helmet";
|
||||
import responseTime from "response-time";
|
||||
|
||||
import { configurePassportAuth } from "@/contexts/auth";
|
||||
import morgan from "morgan";
|
||||
import passport from "passport";
|
||||
import { initLogger } from "../logger";
|
||||
import { v1Router } from "./api/v1";
|
||||
import { v1Routes } from "./api/v1";
|
||||
|
||||
const logger = initLogger(rTracer);
|
||||
|
||||
@ -39,7 +41,7 @@ app.use(
|
||||
"Pagination-Page",
|
||||
"Pagination-Limit",
|
||||
],
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
// secure apps by setting various HTTP headers
|
||||
@ -49,9 +51,13 @@ app.use(helmet());
|
||||
//app.use(morgan('common'));
|
||||
app.use(morgan("dev"));
|
||||
|
||||
// Autentication
|
||||
app.use(passport.initialize());
|
||||
configurePassportAuth(passport);
|
||||
|
||||
// Express configuration
|
||||
app.set("port", process.env.PORT ?? 3000);
|
||||
|
||||
app.use("/api/v1", v1Router);
|
||||
app.use("/api/v1", v1Routes());
|
||||
|
||||
export default app;
|
||||
export { app };
|
||||
|
||||
@ -7,7 +7,7 @@ import { DateTime, Settings } from "luxon";
|
||||
import { createSequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||
import { trace } from "console";
|
||||
import { config } from "../../config";
|
||||
import app from "../express/app";
|
||||
import { app } from "../express/app";
|
||||
import { initLogger } from "../logger";
|
||||
|
||||
process.env.TZ = "UTC";
|
||||
@ -25,7 +25,7 @@ export const currentState = assign(
|
||||
environment: config.enviroment,
|
||||
connections: {},
|
||||
},
|
||||
config
|
||||
config,
|
||||
);
|
||||
|
||||
const serverStop = (server: http.Server) => {
|
||||
@ -36,7 +36,7 @@ const serverStop = (server: http.Server) => {
|
||||
|
||||
setTimeout(() => {
|
||||
logger.error(
|
||||
"Could not close connections in time, forcefully shutting down"
|
||||
"Could not close connections in time, forcefully shutting down",
|
||||
);
|
||||
resolve();
|
||||
}, forceTimeout).unref();
|
||||
@ -68,7 +68,7 @@ const serverError = (error: any) => {
|
||||
if (error.code === "EADDRINUSE") {
|
||||
logger.debug(`⛔️ Server wasn't able to start properly.`);
|
||||
logger.error(
|
||||
`The port ${error.port} is already used by another application.`
|
||||
`The port ${error.port} is already used by another application.`,
|
||||
);
|
||||
} else {
|
||||
logger.debug(`⛔️ Server wasn't able to start properly.`);
|
||||
@ -99,12 +99,12 @@ const server: http.Server = http
|
||||
process.on("SIGINT", () => {
|
||||
//firebirdConn.disconnect();
|
||||
serverStop(server);
|
||||
})
|
||||
}),
|
||||
)
|
||||
.on("close", () =>
|
||||
logger.info(
|
||||
`Shut down at: ${DateTime.now().toLocaleString(DateTime.DATETIME_FULL)}`
|
||||
)
|
||||
`Shut down at: ${DateTime.now().toLocaleString(DateTime.DATETIME_FULL)}`,
|
||||
),
|
||||
)
|
||||
.on("connection", serverConnection)
|
||||
.on("error", serverError);
|
||||
@ -116,16 +116,16 @@ try {
|
||||
server.listen(currentState.server.port, () => {
|
||||
const now = DateTime.now();
|
||||
logger.info(
|
||||
`Time: ${now.toLocaleString(DateTime.DATETIME_FULL)} ${now.zoneName}`
|
||||
`Time: ${now.toLocaleString(DateTime.DATETIME_FULL)} ${now.zoneName}`,
|
||||
);
|
||||
logger.info(
|
||||
`Launched in: ${now.diff(currentState.launchedAt).toMillis()} ms`
|
||||
`Launched in: ${now.diff(currentState.launchedAt).toMillis()} ms`,
|
||||
);
|
||||
logger.info(`Environment: ${currentState.environment}`);
|
||||
logger.info(`Process PID: ${process.pid}`);
|
||||
logger.info("To shut down your server, press <CTRL> + C at any time");
|
||||
logger.info(
|
||||
`⚡️ Server: http://${currentState.server.hostname}:${currentState.server.port}`
|
||||
`⚡️ Server: http://${currentState.server.hostname}:${currentState.server.port}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
10
shared/lib/contexts/auth/application/User.service.ts
Normal file
10
shared/lib/contexts/auth/application/User.service.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { UndefinedOr } from "../../../utilities";
|
||||
import { Email, Result } from "../../common";
|
||||
|
||||
export const ensureUserEmailIsValid = (value: UndefinedOr<string>) => {
|
||||
const valueOrError = Email.create(value);
|
||||
|
||||
return valueOrError.isSuccess
|
||||
? Result.ok(valueOrError.object)
|
||||
: Result.fail(valueOrError.error);
|
||||
};
|
||||
24
shared/lib/contexts/auth/application/dto/ILogin.dto.ts
Normal file
24
shared/lib/contexts/auth/application/dto/ILogin.dto.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import Joi from "joi";
|
||||
import { Result, RuleValidator } from "../../../common";
|
||||
|
||||
export interface ILogin_DTO {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export function ensureLogin_DTOIsValid(
|
||||
loginDTO: ILogin_DTO,
|
||||
): Result<boolean, Error> {
|
||||
const schema = Joi.object({
|
||||
email: Joi.string().email().required(),
|
||||
password: Joi.string().min(4).alphanum().required(),
|
||||
});
|
||||
|
||||
let result = RuleValidator.validate<ILogin_DTO>(schema, loginDTO);
|
||||
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
}
|
||||
|
||||
return Result.ok(true);
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
export interface ILogin_Response_DTO {
|
||||
token: string;
|
||||
}
|
||||
2
shared/lib/contexts/auth/application/dto/index.ts
Normal file
2
shared/lib/contexts/auth/application/dto/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./ILogin.dto";
|
||||
export * from "./ILogin_Response.dto";
|
||||
2
shared/lib/contexts/auth/application/index.ts
Normal file
2
shared/lib/contexts/auth/application/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./User.service";
|
||||
export * from "./dto";
|
||||
1
shared/lib/contexts/auth/index.ts
Normal file
1
shared/lib/contexts/auth/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./application";
|
||||
@ -1,2 +1,3 @@
|
||||
export * from "./auth";
|
||||
export * from "./catalog";
|
||||
export * from "./common";
|
||||
|
||||
Loading…
Reference in New Issue
Block a user