This commit is contained in:
David Arranz 2024-06-10 19:16:39 +02:00
parent 33010c0c6b
commit 090f8acf5c
24 changed files with 132 additions and 101 deletions

View File

@ -40,6 +40,7 @@ module.exports = {
name: "Administrador",
email: "darranz@rodax-software.com",
password: "123456",
language: "es",
},
uploads: {

View File

@ -68,6 +68,6 @@ export class AuthUser extends AggregateRoot<IAuthUserProps> implements IAuthUser
}
private _hasRole(role: AuthUserRole): boolean {
return this.roles.some((r) => r.equals(role));
return (this.roles || []).some((r) => r.equals(role));
}
}

View File

@ -2,7 +2,9 @@ import { IAuthUser } from "@/contexts/auth/domain";
import { IAuthContext } from "@/contexts/auth/infrastructure/Auth.context";
import { IIdentity_Response_DTO } from "@shared/contexts";
export interface IIdentityUser extends IAuthUser {}
export interface IIdentityUser extends IAuthUser {
language: string;
}
export interface IIdentityPresenter {
map: (user: IIdentityUser, context: IAuthContext) => IIdentity_Response_DTO;
@ -15,6 +17,7 @@ export const identityPresenter: IIdentityPresenter = {
id: user.id.toString(),
name: user.name.toString(),
email: user.email.toString(),
language: "es",
roles: user.getRoles()?.map((rol) => rol.toString()),
};
},

View File

@ -16,12 +16,13 @@ export const loginPresenter: ILoginPresenter = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
map: (loginUser: ILoginUser, context: IAuthContext): ILogin_Response_DTO => {
const { user, token, refreshToken } = loginUser;
const roles = user.getRoles()?.map((rol) => rol.toString()) || [];
return {
id: user.id.toString(),
name: user.name.toString(),
email: user.email.toString(),
roles: user.getRoles()?.map((rol) => rol.toString()),
roles,
token,
refresh_token: refreshToken,
};

View File

@ -0,0 +1,17 @@
import { AuthUser } from "@/contexts/auth/domain";
import { generateExpressError } from "@/contexts/common/infrastructure/express";
import Express from "express";
import httpStatus from "http-status";
const profileMiddleware = (
req: Express.Request,
res: Express.Response,
next: Express.NextFunction
) => {
const user = <AuthUser>req.user;
if (!user.isAdmin) {
generateExpressError(req, res, httpStatus.UNAUTHORIZED);
}
next();
};

View File

@ -1,5 +1,6 @@
import { AuthUser } from "@/contexts/auth/domain";
import { composeMiddleware, generateExpressError } from "@/contexts/common/infrastructure/express";
import { UniqueID } from "@shared/contexts";
import Express from "express";
import httpStatus from "http-status";
import passport from "passport";
@ -25,10 +26,22 @@ export const isUser = composeMiddleware([
export const isAdmin = composeMiddleware([
isUser,
(req: Express.Request, res: Express.Response, next: Express.NextFunction) => {
const user = <AuthUser>req.authInfo;
const user = <AuthUser>req.user;
if (!user.isAdmin) {
generateExpressError(req, res, httpStatus.UNAUTHORIZED);
}
next();
},
]);
export const isAdminOrMe = composeMiddleware([
isUser,
(req: Express.Request, res: Express.Response, next: Express.NextFunction) => {
const user = <AuthUser>req.user;
const { userId } = req.params;
if (user.isAdmin || user.id.equals(UniqueID.create(userId).object)) {
next();
} else generateExpressError(req, res, httpStatus.UNAUTHORIZED);
},
]);

View File

@ -1,22 +1,13 @@
import { Password } from "@/contexts/common/domain";
import {
ISequelizeMapper,
SequelizeMapper,
} from "@/contexts/common/infrastructure";
import { ISequelizeMapper, SequelizeMapper } from "@/contexts/common/infrastructure";
import { UserRole } from "@/contexts/users/domain";
import { Email, Name, UniqueID } from "@shared/contexts";
import { AuthUser, IAuthUserProps } from "../../domain/entities";
import { IAuthContext } from "../Auth.context";
import {
AuthUserCreationAttributes,
AuthUser_Model,
} from "../sequelize/authUser.model";
import { AuthUserCreationAttributes, AuthUser_Model } from "../sequelize/authUser.model";
export interface IUserMapper
extends ISequelizeMapper<
AuthUser_Model,
AuthUserCreationAttributes,
AuthUser
> {}
extends ISequelizeMapper<AuthUser_Model, AuthUserCreationAttributes, AuthUser> {}
class AuthUserMapper
extends SequelizeMapper<AuthUser_Model, AuthUserCreationAttributes, AuthUser>
@ -30,11 +21,8 @@ class AuthUserMapper
const props: IAuthUserProps = {
name: this.mapsValue(source, "name", Name.create),
email: this.mapsValue(source, "email", Email.create),
password: this.mapsValue(
source,
"password",
Password.createFromHashedTextPassword,
),
password: this.mapsValue(source, "password", Password.createFromHashedTextPassword),
roles: source.roles.map((rol) => UserRole.create(rol).object),
};
const id = this.mapsValue(source, "id", UniqueID.create);
@ -47,15 +35,13 @@ class AuthUserMapper
return userOrError.object;
}
protected toPersistenceMappingImpl(
source: AuthUser,
params?: Record<string, any> | undefined,
) {
protected toPersistenceMappingImpl(source: AuthUser, params?: Record<string, any> | undefined) {
return {
id: source.id.toPrimitive(),
name: source.name.toPrimitive(),
email: source.email.toPrimitive(),
password: source.password.toPrimitive(),
roles: source.getRoles().map((role) => role.toPrimitive()),
};
}
}

View File

@ -1,3 +1,4 @@
import { USER_ROLES } from "@/contexts/users";
import { DataTypes, InferAttributes, InferCreationAttributes, Model, Sequelize } from "sequelize";
export type AuthUserCreationAttributes = InferCreationAttributes<AuthUser_Model>;
@ -18,6 +19,7 @@ export class AuthUser_Model extends Model<
declare name: string;
declare email: string;
declare password: string;
declare roles: string[];
}
export default (sequelize: Sequelize) => {
@ -41,6 +43,20 @@ export default (sequelize: Sequelize) => {
type: DataTypes.STRING,
allowNull: false,
},
roles: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: USER_ROLES.ROLE_USER,
get(this: AuthUser_Model): string[] {
const rawValue = this.getDataValue("roles") as any;
return String(rawValue).split(";");
},
set(this: AuthUser_Model, value: string[]) {
const rawValue = value.join(";") as any;
this.setDataValue("roles", rawValue);
},
},
},
{
sequelize,

View File

@ -14,8 +14,8 @@ export interface IArticleProps {
catalog_name: Slug;
id_article: ArticleIdentifier;
reference: Slug;
family: Description;
subfamily: Description;
//family: Description;
//subfamily: Description;
description: Description;
points: Quantity;
retail_price: UnitPrice;
@ -26,18 +26,15 @@ export interface IArticle {
catalog_name: Slug;
id_article: ArticleIdentifier;
reference: Slug;
family: Description;
subfamily: Description;
//family: Description;
//subfamily: Description;
description: Description;
points: Quantity;
retail_price: UnitPrice;
}
export class Article extends AggregateRoot<IArticleProps> implements IArticle {
public static create(
props: IArticleProps,
id?: UniqueID,
): Result<Article, IDomainError> {
public static create(props: IArticleProps, id?: UniqueID): Result<Article, IDomainError> {
//const isNew = !!id === false;
// Se hace en el constructor de la Entidad
@ -70,13 +67,13 @@ export class Article extends AggregateRoot<IArticleProps> implements IArticle {
return this.props.reference;
}
get family(): Description {
/*get family(): Description {
return this.props.family;
}
get subfamily(): Description {
return this.props.subfamily;
}
}*/
get description(): Description {
return this.props.description;

View File

@ -1,16 +1,9 @@
import { Article } from "@/contexts/catalog/domain";
import { ICatalogContext } from "@/contexts/catalog/infrastructure";
import {
ICollection,
IListArticles_Response_DTO,
IListResponse_DTO,
} from "@shared/contexts";
import { ICollection, IListArticles_Response_DTO, IListResponse_DTO } from "@shared/contexts";
export interface IListArticlesPresenter {
map: (
article: Article,
context: ICatalogContext,
) => IListArticles_Response_DTO;
map: (article: Article, context: ICatalogContext) => IListArticles_Response_DTO;
mapArray: (
articles: ICollection<Article>,
@ -18,23 +11,20 @@ export interface IListArticlesPresenter {
params: {
page: number;
limit: number;
},
}
) => IListResponse_DTO<IListArticles_Response_DTO>;
}
export const listArticlesPresenter: IListArticlesPresenter = {
map: (
article: Article,
context: ICatalogContext,
): IListArticles_Response_DTO => {
map: (article: Article, context: ICatalogContext): IListArticles_Response_DTO => {
const result: IListArticles_Response_DTO = {
id: article.id.toString(),
catalog_name: article.catalog_name.toString(),
id_article: article.id_article.toString(),
reference: article.reference.toString(),
description: article.description.toString(),
family: article.family.toString(),
subfamily: article.subfamily.toString(),
//family: article.family.toString(),
//subfamily: article.subfamily.toString(),
points: article.points.toNumber(),
retail_price: article.retail_price.toObject(),
};
@ -47,13 +37,13 @@ export const listArticlesPresenter: IListArticlesPresenter = {
params: {
page: number;
limit: number;
},
}
): IListResponse_DTO<IListArticles_Response_DTO> => {
const { page, limit } = params;
const totalCount = articles.totalCount ?? 0;
const items = articles.items.map((article: Article) =>
listArticlesPresenter.map(article, context),
listArticlesPresenter.map(article, context)
);
const result = {

View File

@ -13,11 +13,10 @@ export const CatalogRouter = (appRouter: Express.Router) => {
catalogRoutes.get(
"/",
isUser,
(req: Express.Request, res: Express.Response, next: Express.NextFunction) =>
listArticlesController(res.locals["context"]).execute(req, res, next)
);
catalogRoutes.use(isUser);
appRouter.use("/catalog", catalogRoutes);
};

View File

@ -1,24 +1,8 @@
import {
ISequelizeMapper,
SequelizeMapper,
} from "@/contexts/common/infrastructure";
import {
Description,
Quantity,
Slug,
UniqueID,
UnitPrice,
} from "@shared/contexts";
import { ISequelizeMapper, SequelizeMapper } from "@/contexts/common/infrastructure";
import { Description, Quantity, Slug, UniqueID, UnitPrice } from "@shared/contexts";
import { ICatalogContext } from "..";
import {
Article,
ArticleIdentifier,
IArticleProps,
} from "../../domain/entities";
import {
ArticleCreationAttributes,
Article_Model,
} from "../sequelize/article.model";
import { Article, ArticleIdentifier, IArticleProps } from "../../domain/entities";
import { ArticleCreationAttributes, Article_Model } from "../sequelize/article.model";
export interface IArticleMapper
extends ISequelizeMapper<Article_Model, ArticleCreationAttributes, Article> {}
@ -34,18 +18,14 @@ class ArticleMapper
protected toDomainMappingImpl(source: Article_Model, params: any): Article {
const props: IArticleProps = {
catalog_name: this.mapsValue(source, "catalog_name", Slug.create),
id_article: this.mapsValue(
source,
"id_article",
ArticleIdentifier.create,
),
id_article: this.mapsValue(source, "id_article", ArticleIdentifier.create),
reference: this.mapsValue(source, "reference", Slug.create),
family: this.mapsValue(source, "family", Description.create),
subfamily: this.mapsValue(source, "subfamily", Description.create),
//family: this.mapsValue(source, "family", Description.create),
//subfamily: this.mapsValue(source, "subfamily", Description.create),
description: this.mapsValue(source, "description", Description.create),
points: this.mapsValue(source, "points", Quantity.create),
retail_price: this.mapsValue(source, "retail_price", (value: any) =>
UnitPrice.create({ amount: value, precision: 4 }),
UnitPrice.create({ amount: value, precision: 4 })
),
};

View File

@ -26,8 +26,8 @@ export class Article_Model extends Model<
declare catalog_name: string;
declare id_article: string; // number ??
declare reference: CreationOptional<string>;
declare family: CreationOptional<string>;
declare subfamily: CreationOptional<string>;
//declare family: CreationOptional<string>;
//declare subfamily: CreationOptional<string>;
declare description: CreationOptional<string>;
declare points: CreationOptional<number>;
declare retail_price: CreationOptional<number>;
@ -50,8 +50,8 @@ export default (sequelize: Sequelize) => {
allowNull: false,
},
reference: DataTypes.STRING(),
family: DataTypes.STRING(),
subfamily: DataTypes.STRING(),
//family: DataTypes.STRING(),
//subfamily: DataTypes.STRING(),
description: DataTypes.STRING(),
points: {
type: DataTypes.SMALLINT().UNSIGNED,
@ -74,8 +74,8 @@ export default (sequelize: Sequelize) => {
indexes: [
{ name: "catalog_name_idx", fields: ["catalog_name"] },
{ name: "id_article_idx", fields: ["id_article"] },
{ name: "family_idx", fields: ["family"] },
{ name: "family_subfamily_idx", fields: ["family", "subfamily"] },
//{ name: "family_idx", fields: ["family"] },
//{ name: "family_subfamily_idx", fields: ["family", "subfamily"] },
{ name: "updated_at_idx", fields: ["updated_at"] },
],
@ -88,12 +88,12 @@ export default (sequelize: Sequelize) => {
reference: {
[Op.like]: `%${value}%`,
},
family: {
/*family: {
[Op.like]: `%${value}%`,
},
subfamily: {
[Op.like]: `%${value}%`,
},
},*/
description: {
[Op.like]: `%${value}%`,
},
@ -102,7 +102,7 @@ export default (sequelize: Sequelize) => {
};
},
},
},
}
);
return Article_Model;

View File

@ -50,6 +50,7 @@ export class Dealer_Model extends Model<
declare default_legal_terms: string;
declare default_quote_validity: string;
declare status: DEALER_STATUS;
declare language: string;
}
export default (sequelize: Sequelize) => {
@ -75,6 +76,7 @@ export default (sequelize: Sequelize) => {
default_notes: DataTypes.STRING,
default_legal_terms: DataTypes.STRING,
default_quote_validity: DataTypes.STRING,
language: DataTypes.STRING,
status: {
type: DataTypes.ENUM(...Object.values(DEALER_STATUS)),

View File

@ -1,6 +1,6 @@
import { config } from "@/config";
import { IAdapter, Password, RepositoryBuilder } from "@/contexts/common/domain";
import { Email, Name, UniqueID } from "@shared/contexts";
import { Email, Language, Name, UniqueID } from "@shared/contexts";
import { IUserRepository, User, UserRole } from "../domain";
export const existsUserByID = async (
@ -66,6 +66,7 @@ export const initializeAdmin = async (
name: Name.create(config.admin.name).object,
email: Email.create(config.admin.email).object,
password: Password.createFromPlainTextPassword(config.admin.password).object,
language: Language.createFromCode(config.admin.language).object,
roles: [UserRole.ROLE_ADMIN],
},
UniqueID.generateNewID().object

View File

@ -3,6 +3,7 @@ import {
AggregateRoot,
Email,
IDomainError,
Language,
Name,
Result,
UniqueID,
@ -16,6 +17,7 @@ export interface IUserProps {
email: Email;
password: Password;
roles: UserRole[];
language: Language;
}
//type ISecuredUserProps = ;
@ -25,6 +27,7 @@ export interface IUser {
name: Name;
email: Email;
password: Password;
language: Language;
isUser: boolean;
isAdmin: boolean;
}
@ -69,6 +72,10 @@ export class User extends AggregateRoot<IUserProps> implements IUser {
return this.props.password;
}
get language(): Language {
return this.props.language;
}
get isUser(): boolean {
return this.hasRole(UserRole.ROLE_USER);
}

View File

@ -15,6 +15,10 @@ export class UserRole extends ValueObject<string> {
}
}
public toString(): string {
return this.props.toString();
}
public toPrimitive(): string {
return this.toString();
}

View File

@ -12,6 +12,7 @@ export const GetUserPresenter: IGetUserPresenter = {
id: user.id.toString(),
name: user.name.toString(),
email: user.email.toString(),
language: "es",
roles: user.getRoles().map((rol) => rol.toString()),
};
},

View File

@ -1,4 +1,4 @@
import { isAdmin } from "@/contexts/auth";
import { isAdmin, isAdminOrMe } from "@/contexts/auth";
import Express from "express";
import {
createCreateUserController,
@ -20,7 +20,7 @@ export const UserRouter = (appRouter: Express.Router) => {
userRoutes.get(
"/:userId",
isAdmin,
isAdminOrMe,
(req: Express.Request, res: Express.Response, next: Express.NextFunction) =>
createGetUserController(res.locals["context"]).execute(req, res, next)
);

View File

@ -1,6 +1,6 @@
import { Password } from "@/contexts/common/domain";
import { ISequelizeMapper, SequelizeMapper } from "@/contexts/common/infrastructure";
import { Email, Name, UniqueID } from "@shared/contexts";
import { Email, Language, Name, UniqueID } from "@shared/contexts";
import { IUserProps, User, UserRole } from "../../domain";
import { IUserContext } from "../User.context";
import { UserCreationAttributes, User_Model } from "../sequelize/user.model";
@ -21,6 +21,7 @@ class UserMapper
email: this.mapsValue(source, "email", Email.create),
password: this.mapsValue(source, "password", Password.createFromHashedTextPassword),
roles: source.roles.map((rol) => UserRole.create(rol).object),
language: this.mapsValue(source, "language", Language.createFromCode),
};
const id = this.mapsValue(source, "id", UniqueID.create);
@ -39,6 +40,7 @@ class UserMapper
name: source.name.toPrimitive(),
email: source.email.toPrimitive(),
password: source.password.toPrimitive(),
language: source.language.toPrimitive(),
roles: source.getRoles().map((rol) => rol.toPrimitive()),
};
}

View File

@ -38,6 +38,7 @@ export class User_Model extends Model<
declare name: string;
declare email: string;
declare password: string;
declare language: string;
declare roles: string[];
}
@ -63,15 +64,23 @@ export default (sequelize: Sequelize) => {
allowNull: false,
},
language: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: "es",
},
roles: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: USER_ROLES.ROLE_USER,
get(this: User_Model): string[] {
return this.getDataValue("roles").split(";");
const rawValue = this.getDataValue("roles") as any;
return String(rawValue).split(";");
},
set(this: User_Model, value: string[]) {
this.setDataValue("roles", value.join(";"));
const rawValue = value.join(";") as any;
this.setDataValue("roles", rawValue);
},
},
},

View File

@ -2,5 +2,6 @@ export interface IIdentity_Response_DTO {
id: string;
name: string;
email: string;
language: string;
roles: string[];
}

View File

@ -5,8 +5,8 @@ export interface IListArticles_Response_DTO {
catalog_name: string;
id_article: string;
reference: string;
family: string;
subfamily: string;
//family: string;
//subfamily: string;
description: string;
points: number;
retail_price: IMoney_Response_DTO;

View File

@ -2,5 +2,6 @@ export interface IGetUserResponse_DTO {
id: string;
name: string;
email: string;
language: string;
roles: string[];
}