.
This commit is contained in:
parent
8d280cbb48
commit
27717cc9b8
@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
21
server/src/contexts/auth/domain/entities/AuthUserRole.ts
Normal file
21
server/src/contexts/auth/domain/entities/AuthUserRole.ts
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1 +1,2 @@
|
|||||||
export * from "./AuthUser";
|
export * from "./AuthUser";
|
||||||
|
export * from "./AuthUserRole";
|
||||||
|
|||||||
@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
21
server/src/contexts/users/domain/entities/UserRole.ts
Normal file
21
server/src/contexts/users/domain/entities/UserRole.ts
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1 +1,2 @@
|
|||||||
export * from "./User";
|
export * from "./User";
|
||||||
|
export * from "./UserRole";
|
||||||
|
|||||||
@ -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()),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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()),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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()),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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}`,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
//});
|
//});
|
||||||
|
|||||||
@ -2,4 +2,5 @@ export interface ICreateUser_Response_DTO {
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
|
roles: string[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,4 +2,5 @@ export interface IGetUserResponse_DTO {
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
|
roles: string[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,4 +2,5 @@ export interface IListUsers_Response_DTO {
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
|
roles: string[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,4 +2,5 @@ export interface IUpdateUser_Response_DTO {
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
|
roles: string[];
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user