This commit is contained in:
David Arranz 2024-05-27 11:55:04 +02:00
parent 8d280cbb48
commit 27717cc9b8
22 changed files with 180 additions and 136 deletions

View File

@ -1,17 +1,12 @@
import { Password } from "@/contexts/common/domain"; import { Password } from "@/contexts/common/domain";
import { import { AggregateRoot, Email, IDomainError, Name, Result, UniqueID } from "@shared/contexts";
AggregateRoot, import { AuthUserRole } from "./AuthUserRole";
Email,
IDomainError,
Name,
Result,
UniqueID,
} from "@shared/contexts";
export interface IAuthUserProps { export interface IAuthUserProps {
name: Name; name: Name;
email: Email; email: Email;
password: Password; password: Password;
roles: AuthUserRole[];
} }
export interface IAuthUser { export interface IAuthUser {
@ -21,18 +16,13 @@ export interface IAuthUser {
password: Password; password: Password;
isUser: boolean; isUser: boolean;
isAdmin: boolean; isAdmin: boolean;
getRoles: () => AuthUserRole[];
verifyPassword: (candidatePassword: string) => boolean; verifyPassword: (candidatePassword: string) => boolean;
} }
export class AuthUser export class AuthUser extends AggregateRoot<IAuthUserProps> implements IAuthUser {
extends AggregateRoot<IAuthUserProps> public static create(props: IAuthUserProps, id?: UniqueID): Result<AuthUser, IDomainError> {
implements IAuthUser
{
public static create(
props: IAuthUserProps,
id?: UniqueID,
): Result<AuthUser, IDomainError> {
const user = new AuthUser(props, id); const user = new AuthUser(props, id);
return Result.ok<AuthUser>(user); return Result.ok<AuthUser>(user);
} }
@ -41,6 +31,14 @@ export class AuthUser
return Password.hashPassword(password); return Password.hashPassword(password);
} }
private roles: AuthUserRole[];
constructor(props: IAuthUserProps, id?: UniqueID) {
const { roles } = props;
super(props, id);
this.roles = roles;
}
get name(): Name { get name(): Name {
return this.props.name; return this.props.name;
} }
@ -54,14 +52,22 @@ export class AuthUser
} }
get isUser(): boolean { get isUser(): boolean {
return true; return this._hasRole(AuthUserRole.ROLE_USER);
} }
get isAdmin(): boolean { get isAdmin(): boolean {
return true; return this._hasRole(AuthUserRole.ROLE_ADMIN);
}
public getRoles(): AuthUserRole[] {
return this.roles;
} }
public verifyPassword(candidatePassword: string): boolean { public verifyPassword(candidatePassword: string): boolean {
return this.props.password.verifyPassword(candidatePassword); return this.props.password.verifyPassword(candidatePassword);
} }
private _hasRole(role: AuthUserRole): boolean {
return this.roles.some((r) => r.equals(role));
}
} }

View File

@ -0,0 +1,21 @@
import { DomainError, Result, ValueObject, handleDomainError } from "@shared/contexts";
export class AuthUserRole extends ValueObject<string> {
static ROLE_ADMIN = new AuthUserRole("ROLE_ADMIN");
static ROLE_USER = new AuthUserRole("ROLE_USER");
public static create(value: string) {
switch (value) {
case "ROLE_ADMIN":
return Result.ok(AuthUserRole.ROLE_ADMIN);
case "ROLE_USER":
return Result.ok(AuthUserRole.ROLE_USER);
default:
return Result.fail(handleDomainError(DomainError.INVALID_INPUT_DATA));
}
}
public toPrimitive(): string {
return this.toString();
}
}

View File

@ -1 +1,2 @@
export * from "./AuthUser"; export * from "./AuthUser";
export * from "./AuthUserRole";

View File

@ -21,7 +21,7 @@ export const loginPresenter: ILoginPresenter = {
id: user.id.toString(), id: user.id.toString(),
name: user.name.toString(), name: user.name.toString(),
email: user.email.toString(), email: user.email.toString(),
roles: ["ROLE_USER"], roles: user.getRoles().map((rol) => rol.toString()),
token, token,
refresh_token: refreshToken, refresh_token: refreshToken,
}; };

View File

@ -6,7 +6,7 @@ import {
import { Name, UniqueID } from "@shared/contexts"; import { Name, UniqueID } from "@shared/contexts";
import { Dealer, IDealerProps } from "../../domain/entities"; import { Dealer, IDealerProps } from "../../domain/entities";
import { ISalesContext } from "../Sales.context"; import { ISalesContext } from "../Sales.context";
import { DealerCreationAttributes, DealerStatus, Dealer_Model } from "../sequelize"; import { DEALER_STATUS, DealerCreationAttributes, Dealer_Model } from "../sequelize";
export interface IDealerMapper export interface IDealerMapper
extends ISequelizeMapper<Dealer_Model, DealerCreationAttributes, Dealer> {} extends ISequelizeMapper<Dealer_Model, DealerCreationAttributes, Dealer> {}
@ -44,7 +44,7 @@ class DealerMapper
default_notes: "", default_notes: "",
default_legal_terms: "", default_legal_terms: "",
default_quote_validity: "", default_quote_validity: "",
status: DealerStatus.ACTIVE, status: DEALER_STATUS.STATUS_ACTIVE,
}; };
} }
} }

View File

@ -8,9 +8,9 @@ import {
} from "sequelize"; } from "sequelize";
import { Quote_Model } from "./quote.model"; import { Quote_Model } from "./quote.model";
export enum DealerStatus { export enum DEALER_STATUS {
ACTIVE = "active", STATUS_ACTIVE = "active",
BLOCKED = "blocked", STATUS_BLOCKED = "blocked",
} }
export type DealerCreationAttributes = InferCreationAttributes<Dealer_Model>; export type DealerCreationAttributes = InferCreationAttributes<Dealer_Model>;
@ -49,7 +49,7 @@ export class Dealer_Model extends Model<
declare default_notes: string; declare default_notes: string;
declare default_legal_terms: string; declare default_legal_terms: string;
declare default_quote_validity: string; declare default_quote_validity: string;
declare status: DealerStatus; declare status: DEALER_STATUS;
} }
export default (sequelize: Sequelize) => { export default (sequelize: Sequelize) => {
@ -77,7 +77,7 @@ export default (sequelize: Sequelize) => {
default_quote_validity: DataTypes.STRING, default_quote_validity: DataTypes.STRING,
status: { status: {
type: DataTypes.ENUM(...Object.values(DealerStatus)), type: DataTypes.ENUM(...Object.values(DEALER_STATUS)),
allowNull: false, allowNull: false,
}, },
}, },

View File

@ -1,8 +1,4 @@
import { import { IUseCase, IUseCaseError, UseCaseError } from "@/contexts/common/application";
IUseCase,
IUseCaseError,
UseCaseError,
} from "@/contexts/common/application";
import { IRepositoryManager, Password } from "@/contexts/common/domain"; import { IRepositoryManager, Password } 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";
@ -18,23 +14,19 @@ import {
ensureIdIsValid, ensureIdIsValid,
ensureNameIsValid, ensureNameIsValid,
} from "@shared/contexts"; } from "@shared/contexts";
import { IUserRepository, User } from "../domain"; import { IUserRepository, User, UserRole } from "../domain";
export type CreateUserResponseOrError = export type CreateUserResponseOrError =
| Result<never, IUseCaseError> // Misc errors (value objects) | Result<never, IUseCaseError> // Misc errors (value objects)
| Result<User, never>; // Success! | Result<User, never>; // Success!
export class CreateUserUseCase export class CreateUserUseCase
implements implements IUseCase<ICreateUser_Request_DTO, Promise<CreateUserResponseOrError>>
IUseCase<ICreateUser_Request_DTO, Promise<CreateUserResponseOrError>>
{ {
private _adapter: ISequelizeAdapter; private _adapter: ISequelizeAdapter;
private _repositoryManager: IRepositoryManager; private _repositoryManager: IRepositoryManager;
constructor(props: { constructor(props: { adapter: ISequelizeAdapter; repositoryManager: IRepositoryManager }) {
adapter: ISequelizeAdapter;
repositoryManager: IRepositoryManager;
}) {
this._adapter = props.adapter; this._adapter = props.adapter;
this._repositoryManager = props.repositoryManager; this._repositoryManager = props.repositoryManager;
} }
@ -47,9 +39,7 @@ export class CreateUserUseCase
if (idOrError.isFailure) { if (idOrError.isFailure) {
const message = idOrError.error.message; //`User ID ${userDTO.id} is not valid`; const message = idOrError.error.message; //`User ID ${userDTO.id} is not valid`;
return Result.fail( return Result.fail(
UseCaseError.create(UseCaseError.INVALID_INPUT_DATA, message, [ UseCaseError.create(UseCaseError.INVALID_INPUT_DATA, message, [{ path: "id" }])
{ path: "id" },
]),
); );
} }
@ -57,9 +47,7 @@ export class CreateUserUseCase
if (nameOrError.isFailure) { if (nameOrError.isFailure) {
const message = nameOrError.error.message; //`User ID ${userDTO.id} is not valid`; const message = nameOrError.error.message; //`User ID ${userDTO.id} is not valid`;
return Result.fail( return Result.fail(
UseCaseError.create(UseCaseError.INVALID_INPUT_DATA, message, [ UseCaseError.create(UseCaseError.INVALID_INPUT_DATA, message, [{ path: "name" }])
{ path: "name" },
]),
); );
} }
@ -67,9 +55,7 @@ export class CreateUserUseCase
if (emailOrError.isFailure) { if (emailOrError.isFailure) {
const message = emailOrError.error.message; const message = emailOrError.error.message;
return Result.fail( return Result.fail(
UseCaseError.create(UseCaseError.INVALID_INPUT_DATA, message, [ UseCaseError.create(UseCaseError.INVALID_INPUT_DATA, message, [{ path: "email" }])
{ path: "email" },
]),
); );
} }
@ -82,32 +68,28 @@ export class CreateUserUseCase
return Result.fail( return Result.fail(
UseCaseError.create(UseCaseError.RESOURCE_ALREADY_EXITS, message, { UseCaseError.create(UseCaseError.RESOURCE_ALREADY_EXITS, message, {
path: "id", path: "id",
}), })
); );
} }
const nameExists = await userRepository().existsUserWithName( const nameExists = await userRepository().existsUserWithName(nameOrError.object);
nameOrError.object,
);
if (nameExists) { if (nameExists) {
const message = `Another user with same name exists`; const message = `Another user with same name exists`;
return Result.fail( return Result.fail(
UseCaseError.create(UseCaseError.RESOURCE_ALREADY_EXITS, message, { UseCaseError.create(UseCaseError.RESOURCE_ALREADY_EXITS, message, {
path: "name", path: "name",
}), })
); );
} }
const emailExists = await userRepository().existsUserWithEmail( const emailExists = await userRepository().existsUserWithEmail(emailOrError.object);
emailOrError.object,
);
if (emailExists) { if (emailExists) {
const message = `Another user with same email exists`; const message = `Another user with same email exists`;
return Result.fail( return Result.fail(
UseCaseError.create(UseCaseError.RESOURCE_ALREADY_EXITS, message, { UseCaseError.create(UseCaseError.RESOURCE_ALREADY_EXITS, message, {
path: "email", path: "email",
}), })
); );
} }
@ -157,15 +139,13 @@ export class CreateUserUseCase
return Result.ok<User>(user); return Result.ok<User>(user);
} catch (error: unknown) { } catch (error: unknown) {
const _error = error as IInfrastructureError; const _error = error as IInfrastructureError;
return Result.fail( return Result.fail(UseCaseError.create(UseCaseError.REPOSITORY_ERROR, _error.message));
UseCaseError.create(UseCaseError.REPOSITORY_ERROR, _error.message),
);
} }
} }
private _tryCreateUserInstance( private _tryCreateUserInstance(
userDTO: ICreateUser_Request_DTO, userDTO: ICreateUser_Request_DTO,
userId: UniqueID, userId: UniqueID
): Result<User, IDomainError> { ): Result<User, IDomainError> {
const nameOrError = Name.create(userDTO.name); const nameOrError = Name.create(userDTO.name);
if (nameOrError.isFailure) { if (nameOrError.isFailure) {
@ -177,9 +157,7 @@ export class CreateUserUseCase
return Result.fail(emailOrError.error); return Result.fail(emailOrError.error);
} }
const passwordOrError = Password.createFromPlainTextPassword( const passwordOrError = Password.createFromPlainTextPassword(userDTO.password);
userDTO.password,
);
if (passwordOrError.isFailure) { if (passwordOrError.isFailure) {
return Result.fail(passwordOrError.error); return Result.fail(passwordOrError.error);
} }
@ -189,8 +167,9 @@ export class CreateUserUseCase
name: nameOrError.object, name: nameOrError.object,
email: emailOrError.object, email: emailOrError.object,
password: passwordOrError.object, password: passwordOrError.object,
roles: [UserRole.ROLE_USER],
}, },
userId, userId
); );
} }

View File

@ -16,7 +16,7 @@ import {
Result, Result,
UniqueID, UniqueID,
} from "@shared/contexts"; } from "@shared/contexts";
import { IUserRepository, User } from "../domain"; import { IUserRepository, User, UserRole } from "../domain";
export interface IUpdateUserUseCaseRequest extends IUseCaseRequest { export interface IUpdateUserUseCaseRequest extends IUseCaseRequest {
id: UniqueID; id: UniqueID;
@ -28,23 +28,17 @@ export type UpdateUserResponseOrError =
| Result<User, never>; // Success! | Result<User, never>; // Success!
export class UpdateUserUseCase export class UpdateUserUseCase
implements implements IUseCase<IUpdateUserUseCaseRequest, Promise<UpdateUserResponseOrError>>
IUseCase<IUpdateUserUseCaseRequest, Promise<UpdateUserResponseOrError>>
{ {
private _adapter: ISequelizeAdapter; private _adapter: ISequelizeAdapter;
private _repositoryManager: IRepositoryManager; private _repositoryManager: IRepositoryManager;
constructor(props: { constructor(props: { adapter: ISequelizeAdapter; repositoryManager: IRepositoryManager }) {
adapter: ISequelizeAdapter;
repositoryManager: IRepositoryManager;
}) {
this._adapter = props.adapter; this._adapter = props.adapter;
this._repositoryManager = props.repositoryManager; this._repositoryManager = props.repositoryManager;
} }
async execute( async execute(request: IUpdateUserUseCaseRequest): Promise<UpdateUserResponseOrError> {
request: IUpdateUserUseCaseRequest,
): Promise<UpdateUserResponseOrError> {
const { id, userDTO } = request; const { id, userDTO } = request;
const userRepository = this._getUserRepository(); const userRepository = this._getUserRepository();
@ -55,7 +49,7 @@ export class UpdateUserUseCase
return Result.fail( return Result.fail(
UseCaseError.create(UseCaseError.NOT_FOUND_ERROR, message, { UseCaseError.create(UseCaseError.NOT_FOUND_ERROR, message, {
path: "id", path: "id",
}), })
); );
} }
@ -106,15 +100,13 @@ export class UpdateUserUseCase
return Result.ok<User>(user); return Result.ok<User>(user);
} catch (error: unknown) { } catch (error: unknown) {
const _error = error as IInfrastructureError; const _error = error as IInfrastructureError;
return Result.fail( return Result.fail(UseCaseError.create(UseCaseError.REPOSITORY_ERROR, _error.message));
UseCaseError.create(UseCaseError.REPOSITORY_ERROR, _error.message),
);
} }
} }
private _tryCreateUserInstance( private _tryCreateUserInstance(
userDTO: IUpdateUser_Request_DTO, userDTO: IUpdateUser_Request_DTO,
userId: UniqueID, userId: UniqueID
): Result<User, IDomainError> { ): Result<User, IDomainError> {
const nameOrError = Name.create(userDTO.name); const nameOrError = Name.create(userDTO.name);
if (nameOrError.isFailure) { if (nameOrError.isFailure) {
@ -126,9 +118,7 @@ export class UpdateUserUseCase
return Result.fail(emailOrError.error); return Result.fail(emailOrError.error);
} }
const passwordOrError = Password.createFromPlainTextPassword( const passwordOrError = Password.createFromPlainTextPassword(userDTO.password);
userDTO.password,
);
if (passwordOrError.isFailure) { if (passwordOrError.isFailure) {
return Result.fail(passwordOrError.error); return Result.fail(passwordOrError.error);
} }
@ -138,8 +128,9 @@ export class UpdateUserUseCase
name: nameOrError.object, name: nameOrError.object,
email: emailOrError.object, email: emailOrError.object,
password: passwordOrError.object, password: passwordOrError.object,
roles: [UserRole.ROLE_USER],
}, },
userId, userId
); );
} }

View File

@ -9,14 +9,16 @@ import {
handleDomainError, handleDomainError,
} from "@shared/contexts"; } from "@shared/contexts";
import { UserHasName } from "./User.specifications"; import { UserHasName } from "./User.specifications";
import { UserRole } from "./UserRole";
export interface IUserProps { export interface IUserProps {
name: Name; name: Name;
email: Email; email: Email;
password: Password; password: Password;
roles: UserRole[];
} }
//type ISecuredUserProps = Omit<IUserProps, "password">; //type ISecuredUserProps = ;
export interface IUser { export interface IUser {
id: UniqueID; id: UniqueID;
@ -30,10 +32,7 @@ export interface IUser {
export class User extends AggregateRoot<IUserProps> implements IUser { export class User extends AggregateRoot<IUserProps> implements IUser {
static readonly ERROR_USER_WITHOUT_NAME = "ERROR_USER_WITHOUT_NAME"; static readonly ERROR_USER_WITHOUT_NAME = "ERROR_USER_WITHOUT_NAME";
public static create( public static create(props: IUserProps, id?: UniqueID): Result<User, IDomainError> {
props: IUserProps,
id?: UniqueID,
): Result<User, IDomainError> {
const user = new User(props, id); const user = new User(props, id);
// Reglas de negocio / validaciones // Reglas de negocio / validaciones
@ -50,6 +49,14 @@ export class User extends AggregateRoot<IUserProps> implements IUser {
return Password.hashPassword(password); return Password.hashPassword(password);
} }
private roles: UserRole[];
constructor(props: IUserProps, id?: UniqueID) {
const { roles } = props;
super(props, id);
this.roles = roles;
}
get name(): Name { get name(): Name {
return this.props.name; return this.props.name;
} }
@ -63,10 +70,28 @@ export class User extends AggregateRoot<IUserProps> implements IUser {
} }
get isUser(): boolean { get isUser(): boolean {
return true; return this.hasRole(UserRole.ROLE_USER);
} }
get isAdmin(): boolean { get isAdmin(): boolean {
return true; return this.hasRole(UserRole.ROLE_ADMIN);
}
public hasRole(role: UserRole): boolean {
return this.roles.some((r) => r.equals(role));
}
public getRoles(): UserRole[] {
return this.roles;
}
public addRole(role: UserRole): void {
if (!this.roles.some((r) => r.equals(role))) {
this.roles.push(role);
}
}
public removeRole(role: UserRole): void {
this.roles = this.roles.filter((r) => !r.equals(role));
} }
} }

View File

@ -0,0 +1,21 @@
import { DomainError, Result, ValueObject, handleDomainError } from "@shared/contexts";
export class UserRole extends ValueObject<string> {
static ROLE_ADMIN = new UserRole("ROLE_ADMIN");
static ROLE_USER = new UserRole("ROLE_USER");
public static create(value: string) {
switch (value) {
case "ROLE_ADMIN":
return Result.ok(UserRole.ROLE_ADMIN);
case "ROLE_USER":
return Result.ok(UserRole.ROLE_USER);
default:
return Result.fail(handleDomainError(DomainError.INVALID_INPUT_DATA));
}
}
public toPrimitive(): string {
return this.toString();
}
}

View File

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

View File

@ -12,6 +12,7 @@ export const CreateUserPresenter: ICreateUserPresenter = {
id: user.id.toString(), id: user.id.toString(),
name: user.name.toString(), name: user.name.toString(),
email: user.email.toString(), email: user.email.toString(),
roles: user.getRoles().map((rol) => rol.toString()),
}; };
}, },
}; };

View File

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

View File

@ -1,10 +1,6 @@
import { User } from "@/contexts/users/domain"; import { User } from "@/contexts/users/domain";
import { IUserContext } from "@/contexts/users/infrastructure/User.context"; import { IUserContext } from "@/contexts/users/infrastructure/User.context";
import { import { ICollection, IListResponse_DTO, IListUsers_Response_DTO } from "@shared/contexts";
ICollection,
IListResponse_DTO,
IListUsers_Response_DTO,
} from "@shared/contexts";
export interface IListUsersPresenter { export interface IListUsersPresenter {
map: (user: User, context: IUserContext) => IListUsers_Response_DTO; map: (user: User, context: IUserContext) => IListUsers_Response_DTO;
@ -15,7 +11,7 @@ export interface IListUsersPresenter {
params: { params: {
page: number; page: number;
limit: number; limit: number;
}, }
) => IListResponse_DTO<IListUsers_Response_DTO>; ) => IListResponse_DTO<IListUsers_Response_DTO>;
} }
@ -26,6 +22,7 @@ export const listUsersPresenter: IListUsersPresenter = {
id: user.id.toString(), id: user.id.toString(),
name: user.name.toString(), name: user.name.toString(),
email: user.email.toString(), email: user.email.toString(),
roles: user.getRoles().map((rol) => rol.toString()),
}; };
}, },
@ -35,14 +32,12 @@ export const listUsersPresenter: IListUsersPresenter = {
params: { params: {
page: number; page: number;
limit: number; limit: number;
}, }
): IListResponse_DTO<IListUsers_Response_DTO> => { ): IListResponse_DTO<IListUsers_Response_DTO> => {
const { page, limit } = params; const { page, limit } = params;
const totalCount = users.totalCount ?? 0; const totalCount = users.totalCount ?? 0;
const items = users.items.map((user: User) => const items = users.items.map((user: User) => listUsersPresenter.map(user, context));
listUsersPresenter.map(user, context),
);
const result = { const result = {
page, page,

View File

@ -12,6 +12,7 @@ export const UpdateUserPresenter: IUpdateUserPresenter = {
id: user.id.toString(), id: user.id.toString(),
name: user.name.toString(), name: user.name.toString(),
email: user.email.toString(), email: user.email.toString(),
roles: user.getRoles().map((rol) => rol.toString()),
}; };
}, },
}; };

View File

@ -1,15 +1,11 @@
import { Password } from "@/contexts/common/domain"; import { Password } from "@/contexts/common/domain";
import { import { ISequelizeMapper, SequelizeMapper } from "@/contexts/common/infrastructure";
ISequelizeMapper,
SequelizeMapper,
} from "@/contexts/common/infrastructure";
import { Email, Name, UniqueID } from "@shared/contexts"; import { Email, Name, UniqueID } from "@shared/contexts";
import { IUserProps, User } from "../../domain"; import { IUserProps, User, UserRole } from "../../domain";
import { IUserContext } from "../User.context"; import { IUserContext } from "../User.context";
import { UserCreationAttributes, User_Model } from "../sequelize/user.model"; import { UserCreationAttributes, User_Model } from "../sequelize/user.model";
export interface IUserMapper export interface IUserMapper extends ISequelizeMapper<User_Model, UserCreationAttributes, User> {}
extends ISequelizeMapper<User_Model, UserCreationAttributes, User> {}
class UserMapper class UserMapper
extends SequelizeMapper<User_Model, UserCreationAttributes, User> extends SequelizeMapper<User_Model, UserCreationAttributes, User>
@ -23,11 +19,8 @@ class UserMapper
const props: IUserProps = { const props: IUserProps = {
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),
password: this.mapsValue( password: this.mapsValue(source, "password", Password.createFromHashedTextPassword),
source, roles: source.roles.map((rol) => UserRole.create(rol).object),
"password",
Password.createFromHashedTextPassword,
),
}; };
const id = this.mapsValue(source, "id", UniqueID.create); const id = this.mapsValue(source, "id", UniqueID.create);
@ -40,15 +33,13 @@ class UserMapper
return userOrError.object; return userOrError.object;
} }
protected toPersistenceMappingImpl( protected toPersistenceMappingImpl(source: User, params?: Record<string, any> | undefined) {
source: User,
params?: Record<string, any> | undefined,
) {
return { return {
id: source.id.toPrimitive(), id: source.id.toPrimitive(),
name: source.name.toPrimitive(), name: source.name.toPrimitive(),
email: source.email.toPrimitive(), email: source.email.toPrimitive(),
password: source.password.toPrimitive(), password: source.password.toPrimitive(),
roles: source.getRoles().map((rol) => rol.toPrimitive()),
}; };
} }
} }

View File

@ -7,6 +7,11 @@ import {
Sequelize, Sequelize,
} from "sequelize"; } from "sequelize";
export enum USER_ROLES {
ROLE_ADMIN = "ROLE_ADMIN",
ROLE_USER = "ROLE_USER",
}
export type UserCreationAttributes = InferCreationAttributes<User_Model>; export type UserCreationAttributes = InferCreationAttributes<User_Model>;
export class User_Model extends Model< export class User_Model extends Model<
@ -33,6 +38,7 @@ export class User_Model extends Model<
declare name: string; declare name: string;
declare email: string; declare email: string;
declare password: string; declare password: string;
declare roles: string[];
} }
export default (sequelize: Sequelize) => { export default (sequelize: Sequelize) => {
@ -56,6 +62,18 @@ export default (sequelize: Sequelize) => {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: false, allowNull: false,
}, },
roles: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: USER_ROLES.ROLE_USER,
get(this: User_Model): string[] {
return this.getDataValue("roles").split(";");
},
set(this: User_Model, value: string[]) {
this.setDataValue("roles", value.join(";"));
},
},
}, },
{ {
sequelize, sequelize,

View File

@ -25,7 +25,7 @@ export const currentState = assign(
environment: config.enviroment, environment: config.enviroment,
connections: {}, connections: {},
}, },
config, config
); );
const serverStop = (server: http.Server) => { const serverStop = (server: http.Server) => {
@ -35,9 +35,7 @@ const serverStop = (server: http.Server) => {
logger.warn("⚡️ Shutting down server"); logger.warn("⚡️ Shutting down server");
setTimeout(() => { setTimeout(() => {
logger.error( logger.error("Could not close connections in time, forcefully shutting down");
"Could not close connections in time, forcefully shutting down",
);
resolve(); resolve();
}, forceTimeout).unref(); }, forceTimeout).unref();
@ -67,9 +65,7 @@ const serverStop = (server: http.Server) => {
const serverError = (error: any) => { const serverError = (error: any) => {
if (error.code === "EADDRINUSE") { if (error.code === "EADDRINUSE") {
logger.debug(`⛔️ Server wasn't able to start properly.`); logger.debug(`⛔️ Server wasn't able to start properly.`);
logger.error( logger.error(`The port ${error.port} is already used by another application.`);
`The port ${error.port} is already used by another application.`,
);
} else { } else {
logger.debug(`⛔️ Server wasn't able to start properly.`); logger.debug(`⛔️ Server wasn't able to start properly.`);
logger.error(error); logger.error(error);
@ -99,12 +95,10 @@ const server: http.Server = http
process.on("SIGINT", () => { process.on("SIGINT", () => {
//firebirdConn.disconnect(); //firebirdConn.disconnect();
serverStop(server); serverStop(server);
}), })
) )
.on("close", () => .on("close", () =>
logger.info( logger.info(`Shut down at: ${DateTime.now().toLocaleString(DateTime.DATETIME_FULL)}`)
`Shut down at: ${DateTime.now().toLocaleString(DateTime.DATETIME_FULL)}`,
),
) )
.on("connection", serverConnection) .on("connection", serverConnection)
.on("error", serverError); .on("error", serverError);
@ -115,18 +109,12 @@ try {
// Launch server // Launch server
server.listen(currentState.server.port, () => { server.listen(currentState.server.port, () => {
const now = DateTime.now(); const now = DateTime.now();
logger.info( 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`);
);
logger.info(
`Launched in: ${now.diff(currentState.launchedAt).toMillis()} ms`,
);
logger.info(`Environment: ${currentState.environment}`); logger.info(`Environment: ${currentState.environment}`);
logger.info(`Process PID: ${process.pid}`); logger.info(`Process PID: ${process.pid}`);
logger.info("To shut down your server, press <CTRL> + C at any time"); logger.info("To shut down your server, press <CTRL> + C at any time");
logger.info( logger.info(`⚡️ Server: http://${currentState.server.hostname}:${currentState.server.port}`);
`⚡️ Server: http://${currentState.server.hostname}:${currentState.server.port}`,
);
}); });
}); });
//}); //});

View File

@ -2,4 +2,5 @@ export interface ICreateUser_Response_DTO {
id: string; id: string;
name: string; name: string;
email: string; email: string;
roles: string[];
} }

View File

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

View File

@ -2,4 +2,5 @@ export interface IListUsers_Response_DTO {
id: string; id: string;
name: string; name: string;
email: string; email: string;
roles: string[];
} }

View File

@ -2,4 +2,5 @@ export interface IUpdateUser_Response_DTO {
id: string; id: string;
name: string; name: string;
email: string; email: string;
roles: string[];
} }