This commit is contained in:
David Arranz 2025-02-21 11:06:27 +01:00
parent 085a390aa6
commit 829c839f9b
36 changed files with 211 additions and 237 deletions

View File

@ -1,5 +1,6 @@
import { Maybe, Result, ValueObject } from "@common/domain"; import { Maybe, Result } from "@common/helpers";
import { z } from "zod"; import { z } from "zod";
import { ValueObject } from "./value-object";
interface EmailAddressProps { interface EmailAddressProps {
value: string; value: string;
@ -47,4 +48,8 @@ export class EmailAddress extends ValueObject<EmailAddressProps> {
getValue(): string { getValue(): string {
return this.props.value; return this.props.value;
} }
toString(): string {
return this.getValue();
}
} }

View File

@ -1,5 +1,6 @@
import { Maybe, Result, ValueObject } from "@common/domain"; import { Maybe, Result } from "@common/helpers";
import { z } from "zod"; import { z } from "zod";
import { ValueObject } from "./value-object";
interface NameProps { interface NameProps {
value: string; value: string;

View File

@ -1,5 +1,6 @@
import { Maybe, Result, ValueObject } from "@common/domain"; import { Maybe, Result } from "@common/helpers";
import { z } from "zod"; import { z } from "zod";
import { ValueObject } from "./value-object";
interface SlugProps { interface SlugProps {
value: string; value: string;
@ -35,7 +36,7 @@ export class Slug extends ValueObject<SlugProps> {
return Result.ok(Maybe.None<Slug>()); return Result.ok(Maybe.None<Slug>());
} }
return Slug.create(value!).map((value) => Maybe.Some(value)); return Slug.create(value!).map((value: Slug) => Maybe.Some(value));
} }
getValue(): string { getValue(): string {

View File

@ -1,4 +1,4 @@
import { Result } from "@common/domain"; import { Result } from "@common/helpers";
import { ITransactionManager } from "@common/infrastructure/database"; import { ITransactionManager } from "@common/infrastructure/database";
import { User } from "@contexts/auth/domain"; import { User } from "@contexts/auth/domain";
import { IUserService } from "@contexts/auth/domain/services"; import { IUserService } from "@contexts/auth/domain/services";

View File

@ -1,6 +1,8 @@
import { AggregateRoot, Result, UniqueID } from "@common/domain"; import { Result } from "@common/helpers";
import { AggregateRoot, EmailAddress, UniqueID } from "@common/domain";
import { UserAuthenticatedEvent } from "../events"; import { UserAuthenticatedEvent } from "../events";
import { EmailAddress, HashPassword, PlainPassword, Username } from "../value-objects"; import { HashPassword, PlainPassword, Username } from "../value-objects";
export interface IAuthenticatedUserProps { export interface IAuthenticatedUserProps {
username: Username; username: Username;
@ -12,6 +14,7 @@ export interface IAuthenticatedUserProps {
export interface IAuthenticatedUser { export interface IAuthenticatedUser {
username: Username; username: Username;
email: EmailAddress; email: EmailAddress;
hashPassword: HashPassword;
accessToken: string; accessToken: string;
refreshToken: string; refreshToken: string;
@ -67,6 +70,10 @@ export class AuthenticatedUser
return this.props.email; return this.props.email;
} }
get hashPassword(): HashPassword {
return this.props.hashPassword;
}
get isUser(): boolean { get isUser(): boolean {
return this.hasRole("user"); return this.hasRole("user");
} }
@ -79,14 +86,6 @@ export class AuthenticatedUser
* 🔹 Devuelve una representación lista para persistencia * 🔹 Devuelve una representación lista para persistencia
*/ */
toPersistenceData(): any { toPersistenceData(): any {
return { return;
id: this.id.toString(),
username: this.props.username.toString(),
email: this.props.email.toString(),
hash_password: this.props.hashPassword.toString(),
roles: this.props.roles.map((role) => role.toString()),
access_token: this.accessToken,
refresh_token: this.refreshToken,
};
} }
} }

View File

@ -1,4 +1,5 @@
import { AggregateRoot, Result, UniqueID } from "@common/domain"; import { AggregateRoot, UniqueID } from "@common/domain";
import { Result } from "@common/helpers";
export interface IRoleProps {} export interface IRoleProps {}

View File

@ -1,4 +1,5 @@
import { AggregateRoot, EmailAddress, Result, UniqueID } from "@common/domain"; import { AggregateRoot, EmailAddress, UniqueID } from "@common/domain";
import { Result } from "@common/helpers";
import { UserAuthenticatedEvent } from "../events"; import { UserAuthenticatedEvent } from "../events";
import { Username } from "../value-objects"; import { Username } from "../value-objects";
@ -14,11 +15,11 @@ export interface IUser {
isUser: boolean; isUser: boolean;
isAdmin: boolean; isAdmin: boolean;
isActive: boolean;
hasRole(role: string): boolean; hasRole(role: string): boolean;
hasRoles(roles: string[]): boolean; hasRoles(roles: string[]): boolean;
getRoles(): string[]; getRoles(): string[];
toPersistenceData(): any;
} }
export class User extends AggregateRoot<IUserProps> implements IUser { export class User extends AggregateRoot<IUserProps> implements IUser {
@ -60,15 +61,7 @@ export class User extends AggregateRoot<IUserProps> implements IUser {
return this.hasRole("admin"); return this.hasRole("admin");
} }
/** get isActive(): boolean {
* 🔹 Devuelve una representación lista para persistencia return true;
*/
toPersistenceData(): any {
return {
id: this.id.toString(),
username: this.props.username.toString(),
email: this.props.email.toString(),
roles: this.props.roles.map((role) => role.toString()),
};
} }
} }

View File

@ -1,5 +1,5 @@
import { DomainEntity, Result, UniqueID } from "@common/domain"; import { DomainEntity, EmailAddress, UniqueID } from "@common/domain";
import { EmailAddress } from "../value-objects"; import { Result } from "@common/helpers";
export interface IJWTPayloadProps { export interface IJWTPayloadProps {
tabId: UniqueID; tabId: UniqueID;
@ -23,10 +23,6 @@ export interface IJWTPayload {
export class JWTPayload extends DomainEntity<IJWTPayloadProps> implements IJWTPayload { export class JWTPayload extends DomainEntity<IJWTPayloadProps> implements IJWTPayload {
static create(props: IJWTPayloadProps): Result<JWTPayload, Error> { static create(props: IJWTPayloadProps): Result<JWTPayload, Error> {
if (props.email.isEmpty()) {
return Result.fail(new Error("Email is required"));
}
return Result.ok(new JWTPayload(props)); return Result.ok(new JWTPayload(props));
} }
@ -42,10 +38,6 @@ export class JWTPayload extends DomainEntity<IJWTPayloadProps> implements IJWTPa
return Result.fail(result.error); return Result.fail(result.error);
} }
if (emailOrError.data.isEmpty()) {
return Result.fail(new Error("Email is required"));
}
return JWTPayload.create({ return JWTPayload.create({
email: emailOrError.data, email: emailOrError.data,
userId: userIdOrError.data, userId: userIdOrError.data,

View File

@ -1,5 +1,6 @@
import { DomainEntity, Result, UniqueID } from "@common/domain"; import { DomainEntity, EmailAddress, UniqueID } from "@common/domain";
import { EmailAddress, PlainPassword } from "../value-objects"; import { Result } from "@common/helpers";
import { PlainPassword } from "../value-objects";
export interface ILoginDataProps { export interface ILoginDataProps {
email: EmailAddress; email: EmailAddress;
@ -36,10 +37,6 @@ export class LoginData extends DomainEntity<ILoginDataProps> implements ILoginDa
return Result.fail(result.error); return Result.fail(result.error);
} }
if (emailOrError.data.isEmpty()) {
return Result.fail(new Error("Email is required"));
}
return LoginData.create({ return LoginData.create({
email: emailOrError.data, email: emailOrError.data,
plainPassword: plainPasswordOrError.data, plainPassword: plainPasswordOrError.data,

View File

@ -1,5 +1,5 @@
import { DomainEntity, Result, UniqueID } from "@common/domain"; import { DomainEntity, EmailAddress, UniqueID } from "@common/domain";
import { EmailAddress } from "../value-objects"; import { Result } from "@common/helpers";
export interface ILogoutDataProps { export interface ILogoutDataProps {
email: EmailAddress; email: EmailAddress;
@ -32,10 +32,6 @@ export class LogoutData extends DomainEntity<ILogoutDataProps> implements ILogou
return Result.fail(result.error); return Result.fail(result.error);
} }
if (emailOrError.data.isEmpty()) {
return Result.fail(new Error("Email is required"));
}
return LogoutData.create({ return LogoutData.create({
email: emailOrError.data, email: emailOrError.data,
tabId: tabIdOrError.data, tabId: tabIdOrError.data,

View File

@ -1,5 +1,6 @@
import { DomainEntity, Result } from "@common/domain"; import { DomainEntity, EmailAddress } from "@common/domain";
import { EmailAddress, HashPassword, Username } from "../value-objects"; import { Result } from "@common/helpers";
import { HashPassword, Username } from "../value-objects";
export interface IRegisterDataProps { export interface IRegisterDataProps {
username: Username; username: Username;
@ -37,9 +38,6 @@ export class RegisterData extends DomainEntity<IRegisterDataProps> implements IR
return Result.fail(result.error); return Result.fail(result.error);
} }
if (emailOrError.data.isEmpty()) {
return Result.fail(new Error("Email is required"));
}
return RegisterData.create({ return RegisterData.create({
username: userNameOrError.data, username: userNameOrError.data,
email: emailOrError.data, email: emailOrError.data,

View File

@ -1,4 +1,5 @@
import { DomainEntity, Result, UniqueID } from "@common/domain"; import { DomainEntity, UniqueID } from "@common/domain";
import { Result } from "@common/helpers";
export interface ITabContextProps { export interface ITabContextProps {
tabId: UniqueID; tabId: UniqueID;
@ -14,8 +15,6 @@ export interface ITabContextPrimitives {
export interface ITabContext { export interface ITabContext {
tabId: UniqueID; tabId: UniqueID;
userId: UniqueID; userId: UniqueID;
toPersistenceData(): any;
} }
export class TabContext extends DomainEntity<ITabContextProps> implements ITabContext { export class TabContext extends DomainEntity<ITabContextProps> implements ITabContext {
@ -47,12 +46,4 @@ export class TabContext extends DomainEntity<ITabContextProps> implements ITabCo
get userId(): UniqueID { get userId(): UniqueID {
return this.props.userId; return this.props.userId;
} }
toPersistenceData(): ITabContextPrimitives {
return {
id: this.id.toString(),
tab_id: this.tabId.toString(),
user_id: this.userId.toString(),
};
}
} }

View File

@ -1,6 +1,8 @@
import { Result } from "@common/domain"; import { Result } from "@common/helpers";
import { EmailAddress } from "@common/domain";
import { AuthenticatedUser } from "../aggregates"; import { AuthenticatedUser } from "../aggregates";
import { EmailAddress, Username } from "../value-objects"; import { Username } from "../value-objects";
export interface IAuthenticatedUserRepository { export interface IAuthenticatedUserRepository {
getUserByEmail(email: EmailAddress, transaction?: any): Promise<Result<AuthenticatedUser, Error>>; getUserByEmail(email: EmailAddress, transaction?: any): Promise<Result<AuthenticatedUser, Error>>;

View File

@ -1,4 +1,5 @@
import { Result, UniqueID } from "@common/domain"; import { UniqueID } from "@common/domain";
import { Result } from "@common/helpers";
import { Transaction } from "sequelize"; import { Transaction } from "sequelize";
import { TabContext } from "../entities"; import { TabContext } from "../entities";

View File

@ -1,9 +1,9 @@
import { Result, UniqueID } from "@common/domain"; import { EmailAddress, UniqueID } from "@common/domain";
import { Collection, Result } from "@common/helpers";
import { User } from "../aggregates"; import { User } from "../aggregates";
import { EmailAddress } from "../value-objects";
export interface IUserRepository { export interface IUserRepository {
findAll(transaction?: any): Promise<Result<User[], Error>>; findAll(transaction?: any): Promise<Result<Collection<User>, Error>>;
findById(id: UniqueID, transaction?: any): Promise<Result<User, Error>>; findById(id: UniqueID, transaction?: any): Promise<Result<User, Error>>;
findByEmail(email: EmailAddress, transaction?: any): Promise<Result<User, Error>>; findByEmail(email: EmailAddress, transaction?: any): Promise<Result<User, Error>>;
} }

View File

@ -1,7 +1,7 @@
import { Result } from "@common/domain"; import { EmailAddress } from "@common/domain";
import { Result } from "@common/helpers";
import { import {
AuthenticatedUser, AuthenticatedUser,
EmailAddress,
IJWTPayload, IJWTPayload,
LoginData, LoginData,
LogoutData, LogoutData,

View File

@ -1,15 +1,9 @@
import { Result, UniqueID } from "@common/domain"; import { EmailAddress } from "@common/domain";
import { import { Result } from "@common/helpers";
AuthenticatedUser, import { AuthenticatedUser, IJWTPayload, LoginData, RegisterData, TabContext, Token } from "..";
EmailAddress,
IAuthenticatedUserRepository, import { UniqueID } from "@common/domain";
IJWTPayload, import { IAuthenticatedUserRepository, JWTPayload } from "..";
JWTPayload,
LoginData,
RegisterData,
TabContext,
Token,
} from "..";
import { JwtHelper } from "../../infraestructure/passport/jwt.helper"; import { JwtHelper } from "../../infraestructure/passport/jwt.helper";
import { ITabContextRepository } from "../repositories/tab-context-repository.interface"; import { ITabContextRepository } from "../repositories/tab-context-repository.interface";
import { IAuthService } from "./auth-service.interface"; import { IAuthService } from "./auth-service.interface";
@ -101,11 +95,6 @@ export class AuthService implements IAuthService {
let result: any; let result: any;
const { email, plainPassword, tabId } = loginData; const { email, plainPassword, tabId } = loginData;
// Verificar que el tab ID está definido
if (!tabId.isDefined()) {
return Result.fail(new Error("Invalid tab id"));
}
// 🔹 Verificar si el usuario existe en la base de datos // 🔹 Verificar si el usuario existe en la base de datos
result = await this.authUserRepo.getUserByEmail(email, transaction); result = await this.authUserRepo.getUserByEmail(email, transaction);
if (result.isFailure) { if (result.isFailure) {
@ -171,11 +160,6 @@ export class AuthService implements IAuthService {
): Promise<Result<void, Error>> { ): Promise<Result<void, Error>> {
const { email, tabId } = params; const { email, tabId } = params;
// Verificar que el tab ID está definido
if (!tabId.isDefined()) {
return Result.fail(new Error("Invalid tab id"));
}
// 🔹 Verificar si el usuario existe en la base de datos // 🔹 Verificar si el usuario existe en la base de datos
const userResult = await this.authUserRepo.getUserByEmail(email, transaction); const userResult = await this.authUserRepo.getUserByEmail(email, transaction);
if (userResult.isFailure) { if (userResult.isFailure) {

View File

@ -1,4 +1,5 @@
import { Result, UniqueID } from "@common/domain"; import { UniqueID } from "@common/domain";
import { Result } from "@common/helpers";
import { TabContext } from "../entities"; import { TabContext } from "../entities";
export interface ITabContextService { export interface ITabContextService {

View File

@ -1,4 +1,5 @@
import { Result, UniqueID } from "@common/domain"; import { UniqueID } from "@common/domain";
import { Result } from "@common/helpers";
import { TabContext } from "../entities"; import { TabContext } from "../entities";
import { ITabContextRepository } from "../repositories"; import { ITabContextRepository } from "../repositories";
import { ITabContextService } from "./tab-context-service.interface"; import { ITabContextService } from "./tab-context-service.interface";

View File

@ -1,4 +1,5 @@
import { Result, UniqueID } from "@common/domain"; import { UniqueID } from "@common/domain";
import { Result } from "@common/helpers";
import { User } from "../aggregates"; import { User } from "../aggregates";
export interface IUserService { export interface IUserService {

View File

@ -1,4 +1,5 @@
import { Result, UniqueID } from "@common/domain"; import { UniqueID } from "@common/domain";
import { Result } from "@common/helpers";
import { IUserRepository, User } from ".."; import { IUserRepository, User } from "..";
import { IUserService } from "./user-service.interface"; import { IUserService } from "./user-service.interface";
@ -12,7 +13,7 @@ export class UserService implements IUserService {
} }
// Solo devolver usuarios activos // Solo devolver usuarios activos
const activeUsers = usersOrError.data.filter((user) => user /*.isActive*/); const activeUsers = usersOrError.data.filter((user) => user.isActive);
return Result.ok(activeUsers); return Result.ok(activeUsers);
} }

View File

@ -1,14 +1,19 @@
import { Result, ValueObject } from "@common/domain"; import { ValueObject } from "@common/domain";
import { Result } from "@common/helpers";
import { z } from "zod"; import { z } from "zod";
const RoleSchema = z.enum(["Admin", "User", "Manager", "Editor"]); const RoleSchema = z.enum(["Admin", "User", "Manager", "Editor"]);
export class UserRoles extends ValueObject<string[]> { interface UserRolesProps {
value: string[];
}
export class UserRoles extends ValueObject<UserRolesProps> {
static create(roles: string[]): Result<UserRoles, Error> { static create(roles: string[]): Result<UserRoles, Error> {
const result = UserRoles.validate(roles); const result = UserRoles.validate(roles);
return result.success return result.success
? Result.ok(new UserRoles(result.data)) ? Result.ok(new UserRoles({ value: result.data }))
: Result.fail(new Error("Invalid user roles")); : Result.fail(new Error("Invalid user roles"));
} }
@ -17,6 +22,10 @@ export class UserRoles extends ValueObject<string[]> {
} }
hasRole(role: string): boolean { hasRole(role: string): boolean {
return this.props.includes(role); return this.props.value.includes(role);
}
getValue() {
return this.props.value;
} }
} }

View File

@ -1,4 +1,5 @@
import { Result, ValueObject } from "@common/domain"; import { ValueObject } from "@common/domain";
import { Result } from "@common/helpers";
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import { z } from "zod"; import { z } from "zod";

View File

@ -1,4 +1,5 @@
import { Result, ValueObject } from "@common/domain"; import { ValueObject } from "@common/domain";
import { Result } from "@common/helpers";
import { z } from "zod"; import { z } from "zod";
interface PlainPasswordProps { interface PlainPasswordProps {

View File

@ -1,4 +1,5 @@
import { Result, ValueObject } from "@common/domain"; import { ValueObject } from "@common/domain";
import { Result } from "@common/helpers";
import { z } from "zod"; import { z } from "zod";
interface TokenProps { interface TokenProps {

View File

@ -1,4 +1,5 @@
import { Result, ValueObject } from "@common/domain"; import { ValueObject } from "@common/domain";
import { Result } from "@common/helpers";
import { z } from "zod"; import { z } from "zod";
interface UsernameProps { interface UsernameProps {

View File

@ -1,26 +1,25 @@
import { EmailAddress, Result, UniqueID } from "@common/domain"; import { EmailAddress, UniqueID } from "@common/domain";
import { Result } from "@common/helpers";
import { ISequelizeMapper, MapperParamsType, SequelizeMapper } from "@common/infrastructure";
import { AuthenticatedUser, HashPassword, Username } from "@contexts/auth/domain"; import { AuthenticatedUser, HashPassword, Username } from "@contexts/auth/domain";
import { AuthUserModel } from "../sequelize"; import { AuthUserCreationAttributes, AuthUserModel } from "../sequelize";
export interface IAuthenticatedUserMapper { export interface IAuthenticatedUserMapper
toDomain(entity: AuthUserModel): Result<AuthenticatedUser, Error>; extends ISequelizeMapper<AuthUserModel, AuthUserCreationAttributes, AuthenticatedUser> {}
toPersistence(aggregate: AuthenticatedUser): AuthUserModel;
}
class AuthenticatedUserMapper implements IAuthenticatedUserMapper {
/**
* 🔹 Convierte una entidad de la base de datos en un agregado de dominio `AuthenticatedUser`
*/
toDomain(entity: AuthUserModel): Result<AuthenticatedUser, Error> {
if (!entity) {
return Result.fail(new Error("Entity not found"));
}
export class AuthenticatedUserMapper
extends SequelizeMapper<AuthUserModel, AuthUserCreationAttributes, AuthenticatedUser>
implements IAuthenticatedUserMapper
{
public mapToDomain(
source: AuthUserModel,
params?: MapperParamsType
): Result<AuthenticatedUser, Error> {
// Crear Value Objects asegurando que sean válidos // Crear Value Objects asegurando que sean válidos
const uniqueIdResult = UniqueID.create(entity.id); const uniqueIdResult = UniqueID.create(source.id);
const usernameResult = Username.create(entity.username); const usernameResult = Username.create(source.username);
const passwordHashResult = HashPassword.createFromHash(entity.hash_password); const passwordHashResult = HashPassword.createFromHash(source.hash_password);
const emailResult = EmailAddress.create(entity.email); const emailResult = EmailAddress.create(source.email);
// Validar que no haya errores en la creación de los Value Objects // Validar que no haya errores en la creación de los Value Objects
const okOrError = Result.combine([ const okOrError = Result.combine([
@ -39,17 +38,25 @@ class AuthenticatedUserMapper implements IAuthenticatedUserMapper {
username: usernameResult.data!, username: usernameResult.data!,
email: emailResult.data!, email: emailResult.data!,
hashPassword: passwordHashResult.data!, hashPassword: passwordHashResult.data!,
roles: entity.roles || [], roles: source.roles || [],
}, },
uniqueIdResult.data! uniqueIdResult.data!
); );
} }
/** public mapToPersistence(
* 🔹 Convierte un agregado `AuthenticatedUser` en un objeto listo para persistencia source: AuthenticatedUser,
*/ params?: MapperParamsType
toPersistence(authenticatedUser: AuthenticatedUser): AuthUserModel { ): Result<AuthUserCreationAttributes, Error> {
return authenticatedUser.toPersistenceData(); return Result.ok({
id: source.id.toString(),
username: source.username.toString(),
email: source.email.toString(),
hash_password: source.hashPassword.toString(),
roles: source.getRoles().map((role) => role.toString()),
//access_token: source.accessToken,
//refresh_token: source.refreshToken,
});
} }
} }

View File

@ -1,22 +1,24 @@
import { Result, UniqueID } from "@common/domain"; import { UniqueID } from "@common/domain";
import { Result } from "@common/helpers";
import { ISequelizeMapper, MapperParamsType, SequelizeMapper } from "@common/infrastructure";
import { TabContext } from "@contexts/auth/domain"; import { TabContext } from "@contexts/auth/domain";
import { TabContextModel } from "../sequelize"; import { TabContextCreationAttributes, TabContextModel } from "../sequelize";
export interface ITabContextMapper { export interface ITabContextMapper
toDomain(entity: TabContextModel): Result<TabContext, Error>; extends ISequelizeMapper<TabContextModel, TabContextCreationAttributes, TabContext> {}
toPersistence(aggregate: TabContext): TabContextModel;
}
class TabContextMapper implements ITabContextMapper {
toDomain(entity: TabContextModel): Result<TabContext, Error> {
if (!entity) {
return Result.fail(new Error("Entity not found"));
}
export class TabContextMapper
extends SequelizeMapper<TabContextModel, TabContextCreationAttributes, TabContext>
implements ITabContextMapper
{
public mapToDomain(
source: TabContextModel,
params?: MapperParamsType
): Result<TabContext, Error> {
// Crear Value Objects asegurando que sean válidos // Crear Value Objects asegurando que sean válidos
const uniqueIdResult = UniqueID.create(entity.id); const uniqueIdResult = UniqueID.create(source.id);
const tabIdResult = UniqueID.create(entity.tab_id); const tabIdResult = UniqueID.create(source.tab_id);
const userIdResult = UniqueID.create(entity.user_id); const userIdResult = UniqueID.create(source.user_id);
//const companyIdResult = UniqueID.create(entity.company_id, false); //const companyIdResult = UniqueID.create(entity.company_id, false);
//const brachIdResult = UniqueID.create(entity.branch_id, false); //const brachIdResult = UniqueID.create(entity.branch_id, false);
@ -44,8 +46,15 @@ class TabContextMapper implements ITabContextMapper {
); );
} }
toPersistence(tabContext: TabContext): TabContextModel { public mapToPersistence(
return tabContext.toPersistenceData(); source: TabContext,
params?: MapperParamsType
): Result<TabContextCreationAttributes, Error> {
return Result.ok({
id: source.id.toString(),
tab_id: source.tabId.toString(),
user_id: source.userId.toString(),
});
} }
} }

View File

@ -1,28 +1,24 @@
import { EmailAddress, Result, UniqueID } from "@common/domain"; import { EmailAddress, UniqueID } from "@common/domain";
import { Result } from "@common/helpers";
import {
ISequelizeMapper,
MapperParamsType,
SequelizeMapper,
} from "@common/infrastructure/sequelize/sequelize-mapper";
import { User, Username } from "@contexts/auth/domain"; import { User, Username } from "@contexts/auth/domain";
import { UserModel } from "../sequelize"; import { UserCreationAttributes, UserModel } from "../sequelize";
export interface IUserMapper { export interface IUserMapper extends ISequelizeMapper<UserModel, UserCreationAttributes, User> {}
toDomain(entity: UserModel): Result<User, Error>;
toDomainArray(entities: UserModel[]): Result<User[], Error>;
toPersistence(aggregate: User): UserModel;
toPersistenceArray(users: User[]): UserModel[];
}
class UserMapper implements IUserMapper {
/**
* 🔹 Convierte una entidad de la base de datos en un agregado de dominio `User`
*/
toDomain(entity: UserModel): Result<User, Error> {
if (!entity) {
return Result.fail(new Error("Entity not found"));
}
class UserMapper
extends SequelizeMapper<UserModel, UserCreationAttributes, User>
implements IUserMapper
{
public mapToDomain(source: UserModel, params?: MapperParamsType): Result<User, Error> {
// Crear Value Objects asegurando que sean válidos // Crear Value Objects asegurando que sean válidos
const uniqueIdResult = UniqueID.create(entity.id); const uniqueIdResult = UniqueID.create(source.id);
const usernameResult = Username.create(entity.username); const usernameResult = Username.create(source.username);
const emailResult = EmailAddress.create(entity.email); const emailResult = EmailAddress.create(source.email);
// Validar que no haya errores en la creación de los Value Objects // Validar que no haya errores en la creación de los Value Objects
const okOrError = Result.combine([uniqueIdResult, usernameResult, emailResult]); const okOrError = Result.combine([uniqueIdResult, usernameResult, emailResult]);
@ -42,41 +38,16 @@ class UserMapper implements IUserMapper {
); );
} }
/** public mapToPersistence(
* 🔹 Convierte un array de entidades de la base de datos en un array de agregados de dominio `User` source: User,
*/ params?: MapperParamsType
toDomainArray(entities: UserModel[]): Result<User[], Error> { ): Result<UserCreationAttributes, Error> {
if (!Array.isArray(entities) || entities.length === 0) { return Result.ok({
return Result.fail(new Error("Entities array is empty or invalid")); id: source.id.toString(),
} username: source.username.toString(),
email: source.email.toString(),
const usersResults = entities.map(this.toDomain); //roles: source.getRoles().map((role) => role.toString()),
});
const okOrError = Result.combine(usersResults);
if (okOrError.isFailure) {
return Result.fail(okOrError.error);
}
const result = usersResults.map((result) => result.data!);
return Result.ok(result);
}
/**
* 🔹 Convierte un agregado `User` en un objeto listo para persistencia
*/
toPersistence(user: User): UserModel {
return user.toPersistenceData();
}
/**
* 🔹 Convierte un array de agregados `User` en un array de objetos listos para persistencia
*/
toPersistenceArray(users: User[]): UserModel[] {
if (!Array.isArray(users) || users.length === 0) {
return [];
}
return users.map(this.toPersistence);
} }
} }

View File

@ -1,9 +1,10 @@
import { NextFunction, Response } from "express"; import { NextFunction, Response } from "express";
import { Result, UniqueID } from "@common/domain"; import { EmailAddress, UniqueID } from "@common/domain";
import { Result } from "@common/helpers";
import { ITransactionManager } from "@common/infrastructure/database"; import { ITransactionManager } from "@common/infrastructure/database";
import { logger } from "@common/infrastructure/logger"; import { logger } from "@common/infrastructure/logger";
import { EmailAddress, TabContext } from "@contexts/auth/domain"; import { TabContext } from "@contexts/auth/domain";
import { IAuthService, ITabContextService } from "@contexts/auth/domain/services"; import { IAuthService, ITabContextService } from "@contexts/auth/domain/services";
import passport from "passport"; import passport from "passport";
import { ExtractJwt, Strategy as JwtStrategy } from "passport-jwt"; import { ExtractJwt, Strategy as JwtStrategy } from "passport-jwt";

View File

@ -1,11 +1,7 @@
import { Result } from "@common/domain"; import { EmailAddress } from "@common/domain";
import { Result } from "@common/helpers";
import { SequelizeRepository } from "@common/infrastructure"; import { SequelizeRepository } from "@common/infrastructure";
import { import { AuthenticatedUser, IAuthenticatedUserRepository, Username } from "@contexts/auth/domain";
AuthenticatedUser,
EmailAddress,
IAuthenticatedUserRepository,
Username,
} from "@contexts/auth/domain";
import { Transaction } from "sequelize"; import { Transaction } from "sequelize";
import { authenticatedUserMapper, IAuthenticatedUserMapper } from "../mappers"; import { authenticatedUserMapper, IAuthenticatedUserMapper } from "../mappers";
import { AuthUserModel } from "./auth-user.model"; import { AuthUserModel } from "./auth-user.model";
@ -75,7 +71,7 @@ class AuthenticatedUserRepository
return Result.fail(new Error("User with email not exists")); return Result.fail(new Error("User with email not exists"));
} }
return this._mapper.toDomain(rawUser); return this._mapper.mapToDomain(rawUser);
} catch (error: any) { } catch (error: any) {
return this._handleDatabaseError(error, this._customErrorMapper); return this._handleDatabaseError(error, this._customErrorMapper);
} }
@ -86,8 +82,8 @@ class AuthenticatedUserRepository
transaction?: Transaction transaction?: Transaction
): Promise<Result<void, Error>> { ): Promise<Result<void, Error>> {
try { try {
const persistenceData = this._mapper.toPersistence(user); const persistenceData = this._mapper.mapToPersistence(user);
await AuthUserModel.create(persistenceData, { transaction }); await AuthUserModel.create(persistenceData.data, { transaction });
return Result.ok(); return Result.ok();
} catch (error: any) { } catch (error: any) {
return this._handleDatabaseError(error, this._customErrorMapper); return this._handleDatabaseError(error, this._customErrorMapper);

View File

@ -1,4 +1,5 @@
import { Result, UniqueID } from "@common/domain"; import { UniqueID } from "@common/domain";
import { Result } from "@common/helpers";
import { SequelizeRepository } from "@common/infrastructure"; import { SequelizeRepository } from "@common/infrastructure";
import { ITabContextRepository, TabContext } from "@contexts/auth/domain/"; import { ITabContextRepository, TabContext } from "@contexts/auth/domain/";
import { Op, Transaction } from "sequelize"; import { Op, Transaction } from "sequelize";
@ -44,7 +45,7 @@ class TabContextRepository
return Result.fail(new Error("Tab context not exists")); return Result.fail(new Error("Tab context not exists"));
} }
return this._mapper.toDomain(rawContext); return this._mapper.mapToDomain(rawContext);
} catch (error: any) { } catch (error: any) {
return this._handleDatabaseError(error, this._customErrorMapper); return this._handleDatabaseError(error, this._customErrorMapper);
} }
@ -78,17 +79,17 @@ class TabContextRepository
): Promise<Result<void, Error>> { ): Promise<Result<void, Error>> {
try { try {
const { userId, tabId } = context; const { userId, tabId } = context;
const data = this._mapper.toPersistence(context); const persistenceData = this._mapper.mapToPersistence(context);
// Si existe el contexto de ese tabId, lo actualizo. // Si existe el contexto de ese tabId, lo actualizo.
if (await this._exists(TabContextModel, "tab_id", tabId.toString())) { if (await this._exists(TabContextModel, "tab_id", tabId.toString())) {
await TabContextModel.update(data, { await TabContextModel.update(persistenceData.data, {
where: { [Op.and]: [{ tab_id: tabId.toString() }, { user_id: userId.toString() }] }, where: { [Op.and]: [{ tab_id: tabId.toString() }, { user_id: userId.toString() }] },
transaction, transaction,
}); });
} else { } else {
await TabContextModel.create(data, { await TabContextModel.create(persistenceData.data, {
include: [{ all: true }], include: [{ all: true }],
transaction, transaction,
}); });

View File

@ -1,4 +1,5 @@
import { EmailAddress, Result, UniqueID } from "@common/domain"; import { EmailAddress, UniqueID } from "@common/domain";
import { Collection, Result } from "@common/helpers";
import { SequelizeRepository } from "@common/infrastructure"; import { SequelizeRepository } from "@common/infrastructure";
import { IUserRepository, User } from "@contexts/auth/domain"; import { IUserRepository, User } from "@contexts/auth/domain";
import { Transaction } from "sequelize"; import { Transaction } from "sequelize";
@ -24,7 +25,7 @@ class UserRepository extends SequelizeRepository<User> implements IUserRepositor
this._mapper = mapper; this._mapper = mapper;
} }
async findAll(transaction?: Transaction): Promise<Result<User[], Error>> { async findAll(transaction?: Transaction): Promise<Result<Collection<User>, Error>> {
try { try {
const rawUsers: any = await this._findAll(UserModel, {}, transaction); const rawUsers: any = await this._findAll(UserModel, {}, transaction);
@ -32,7 +33,7 @@ class UserRepository extends SequelizeRepository<User> implements IUserRepositor
return Result.fail(new Error("User with email not exists")); return Result.fail(new Error("User with email not exists"));
} }
return this._mapper.toDomainArray(rawUsers); return this._mapper.mapArrayToDomain(rawUsers);
} catch (error: any) { } catch (error: any) {
return this._handleDatabaseError(error, this._customErrorMapper); return this._handleDatabaseError(error, this._customErrorMapper);
} }
@ -46,7 +47,7 @@ class UserRepository extends SequelizeRepository<User> implements IUserRepositor
return Result.fail(new Error(`User with id ${id.toString()} not exists`)); return Result.fail(new Error(`User with id ${id.toString()} not exists`));
} }
return this._mapper.toDomain(rawUser); return this._mapper.mapToDomain(rawUser);
} catch (error: any) { } catch (error: any) {
return this._handleDatabaseError(error, this._customErrorMapper); return this._handleDatabaseError(error, this._customErrorMapper);
} }
@ -60,7 +61,7 @@ class UserRepository extends SequelizeRepository<User> implements IUserRepositor
return Result.fail(new Error(`User with email ${email.toString()} not exists`)); return Result.fail(new Error(`User with email ${email.toString()} not exists`));
} }
return this._mapper.toDomain(rawUser); return this._mapper.mapToDomain(rawUser);
} catch (error: any) { } catch (error: any) {
return this._handleDatabaseError(error, this._customErrorMapper); return this._handleDatabaseError(error, this._customErrorMapper);
} }

View File

@ -1,3 +1,4 @@
import { ensureString } from "@common/helpers";
import { User } from "@contexts/auth/domain"; import { User } from "@contexts/auth/domain";
import { IListUsersResponseDTO } from "../../dto"; import { IListUsersResponseDTO } from "../../dto";
@ -8,8 +9,8 @@ export interface IListUsersPresenter {
export const listUsersPresenter: IListUsersPresenter = { export const listUsersPresenter: IListUsersPresenter = {
toDTO: (users: User[]): IListUsersResponseDTO[] => toDTO: (users: User[]): IListUsersResponseDTO[] =>
users.map((user) => ({ users.map((user) => ({
id: user.id.toString(), id: ensureString(user.id.toString()),
email: user.email.toString(), email: ensureString(user.email.toString()),
username: user.username.toString(), username: ensureString(user.username.toString()),
})), })),
}; };

View File

@ -27,9 +27,18 @@ export const loginPresenter: ILoginPresenter = {
tokens: { accessToken, refreshToken }, tokens: { accessToken, refreshToken },
} = data; } = data;
const userData = user.toPersistenceData(); const userData = {
id: user.id.toString(),
username: user.username.toString(),
email: user.email.toString(),
//roles: user.getRoles().map((role) => role.toString()),
};
const tabContextData = tabContext.toPersistenceData(); const tabContextData = {
id: tabContext.id.toString(),
tab_id: tabContext.tabId.toString(),
user_id: tabContext.userId.toString(),
};
return { return {
user: { user: {

View File

@ -1,5 +1,5 @@
import { validateRequestDTO } from "@common/presentation"; import { validateRequestDTO } from "@common/presentation";
import { checkTabContext, checkUserIsAdmin } from "@contexts/auth/infraestructure"; import { checkTabContext } from "@contexts/auth/infraestructure";
import { listUsersController, ListUsersSchema } from "@contexts/auth/presentation"; import { listUsersController, ListUsersSchema } from "@contexts/auth/presentation";
import { NextFunction, Request, Response, Router } from "express"; import { NextFunction, Request, Response, Router } from "express";
@ -10,7 +10,7 @@ export const userRouter = (appRouter: Router) => {
"/", "/",
validateRequestDTO(ListUsersSchema), validateRequestDTO(ListUsersSchema),
checkTabContext, checkTabContext,
checkUserIsAdmin, //checkUserIsAdmin,
(req: Request, res: Response, next: NextFunction) => { (req: Request, res: Response, next: NextFunction) => {
listUsersController().execute(req, res, next); listUsersController().execute(req, res, next);
} }