This commit is contained in:
David Arranz 2025-02-04 19:25:10 +01:00
parent a8c72b1d64
commit 8ead5a62da
18 changed files with 250 additions and 164 deletions

View File

@ -46,19 +46,27 @@ export class Result<T, E extends Error = Error> {
} }
get data(): T { get data(): T {
if (!this._isSuccess) { return this.getData();
throw new Error("Cannot get value data from a failed result.");
}
return this._data as T;
} }
get error(): E { get error(): E {
return this.getError();
}
getError(): E {
if (this._isSuccess) { if (this._isSuccess) {
throw new Error("Cannot get error from a successful result."); throw new Error("Cannot get error from a successful result.");
} }
return this._error as E; return this._error as E;
} }
getData(): T {
if (!this._isSuccess) {
throw new Error("Cannot get value data from a failed result.");
}
return this._data as T;
}
/** /**
* 🔹 `getOrElse(defaultValue: T): T` * 🔹 `getOrElse(defaultValue: T): T`
* Si el `Result` es un `ok`, devuelve `data`, de lo contrario, devuelve `defaultValue`. * Si el `Result` es un `ok`, devuelve `data`, de lo contrario, devuelve `defaultValue`.

View File

@ -1,35 +1,54 @@
import { UniqueID } from "./value-objects/unique-id"; import { UniqueID } from "./unique-id";
describe("UniqueID Value Object", () => { // Mock UUID generation to ensure predictable tests
it("should generate a new UUID using generateNewID()", () => { jest.mock("uuid", () => ({ v4: () => "123e4567-e89b-12d3-a456-426614174000" }));
const result = UniqueID.generateNewID();
describe("UniqueID", () => {
test("should create a UniqueID with a valid UUID", () => {
const id = "123e4567-e89b-12d3-a456-426614174000";
const result = UniqueID.create(id);
expect(result.isSuccess).toBe(true); expect(result.isSuccess).toBe(true);
expect(result.data.getValue()).toMatch( expect(result.data?.isDefined()).toBe(true);
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
);
}); });
it("should return an error for an invalid UUID", () => { test("should fail to create UniqueID with an invalid UUID", () => {
const result = UniqueID.create("invalid-uuid"); const result = UniqueID.create("invalid-uuid");
expect(result.isSuccess).toBe(true); expect(result.isFailure).toBe(true);
expect(result.error.message).toBe("Invalid UUID format");
}); });
it("should create a valid UniqueID from an existing UUID", () => { test("should create an undefined UniqueID when id is undefined and generateOnEmpty is false", () => {
const validUUID = "550e8400-e29b-41d4-a716-446655440000"; const result = UniqueID.create(undefined, false);
const result = UniqueID.create(validUUID);
expect(result.isSuccess).toBe(true); expect(result.isSuccess).toBe(true);
expect(result.data.getValue()).toBe(validUUID); expect(result.data?.isDefined()).toBe(false);
}); });
it("should correctly convert UniqueID to string", () => { test("should generate a new UUID when id is undefined and generateOnEmpty is true", () => {
const validUUID = "550e8400-e29b-41d4-a716-446655440000"; const result = UniqueID.create(undefined, true);
const result = UniqueID.create(validUUID);
expect(result.isSuccess).toBe(true); expect(result.isSuccess).toBe(true);
expect(result.data.toString()).toBe(validUUID); expect(result.data?.isDefined()).toBe(true);
});
test("should fail when id is null", () => {
const result = UniqueID.create(null as any);
expect(result.isFailure).toBe(true);
});
test("should create a UniqueID when id is an empty string and generateOnEmpty is true", () => {
const result = UniqueID.create(" ", true);
expect(result.isSuccess).toBe(true);
expect(result.data?.isDefined()).toBe(true);
});
test("should create an undefined UniqueID when id is an empty string and generateOnEmpty is false", () => {
const result = UniqueID.create(" ", false);
expect(result.isSuccess).toBe(true);
expect(result.data?.isDefined()).toBe(false);
}); });
}); });

View File

@ -3,17 +3,27 @@ import { z } from "zod";
import { Result } from "../result"; import { Result } from "../result";
import { ValueObject } from "./value-object"; import { ValueObject } from "./value-object";
const UUIDSchema = z.string().uuid({ message: "Invalid UUID format" }); export const UNDEFINED_ID = undefined;
export class UniqueID extends ValueObject<string | undefined> {
protected readonly _hasId!: boolean;
protected constructor(id?: string) {
super(id);
this._hasId = id != UNDEFINED_ID;
}
export class UniqueID extends ValueObject<string> {
static create(id?: string, generateOnEmpty: boolean = false): Result<UniqueID, Error> { static create(id?: string, generateOnEmpty: boolean = false): Result<UniqueID, Error> {
if (!id) { if (id === null) {
return generateOnEmpty return Result.fail(new Error("ID cannot be null"));
? UniqueID.generateNewID()
: Result.fail(new Error("ID is null or empty"));
} }
const result = UniqueID.validate(id.trim()); const trimmedId = id?.trim();
if (!trimmedId) {
return generateOnEmpty ? UniqueID.generateNewID() : Result.ok(new UniqueID(UNDEFINED_ID));
}
const result = UniqueID.validate(trimmedId);
return result.success return result.success
? Result.ok(new UniqueID(result.data)) ? Result.ok(new UniqueID(result.data))
@ -32,4 +42,12 @@ export class UniqueID extends ValueObject<string> {
static generateNewID(): Result<UniqueID, never> { static generateNewID(): Result<UniqueID, never> {
return Result.ok(new UniqueID(uuidv4())); return Result.ok(new UniqueID(uuidv4()));
} }
static generateUndefinedID(): Result<UniqueID, never> {
return Result.ok(new UniqueID(UNDEFINED_ID));
}
isDefined(): boolean {
return this._hasId;
}
} }

View File

@ -5,8 +5,6 @@ export abstract class ValueObject<T> {
protected constructor(value: T) { protected constructor(value: T) {
this._value = typeof value === "object" && value !== null ? Object.freeze(value) : value; this._value = typeof value === "object" && value !== null ? Object.freeze(value) : value;
Object.freeze(this);
} }
equals(other: ValueObject<T>): boolean { equals(other: ValueObject<T>): boolean {

View File

@ -1,4 +1,4 @@
import { Result } from "@common/domain"; import { Result, UniqueID } from "@common/domain";
import { AuthenticatedUser, EmailAddress, PasswordHash, Username } from "../domain"; import { AuthenticatedUser, EmailAddress, PasswordHash, Username } from "../domain";
export interface IAuthService { export interface IAuthService {
@ -8,8 +8,16 @@ export interface IAuthService {
passwordHash: PasswordHash; passwordHash: PasswordHash;
}): Promise<Result<AuthenticatedUser, Error>>; }): Promise<Result<AuthenticatedUser, Error>>;
loginUser(params: { loginUser(params: { email: EmailAddress; passwordHash: PasswordHash; tabId: UniqueID }): Promise<
email: EmailAddress; Result<
passwordHash: PasswordHash; {
}): Promise<Result<AuthenticatedUser, Error>>; user: AuthenticatedUser;
tokens: {
accessToken: string;
refreshToken: string;
};
},
Error
>
>;
} }

View File

@ -5,22 +5,27 @@ import {
EmailAddress, EmailAddress,
IAuthenticatedUserRepository, IAuthenticatedUserRepository,
PasswordHash, PasswordHash,
TabContext,
Username, Username,
} from "@contexts/auth/domain"; } from "../domain";
import { ITabContextRepository } from "../domain/repositories/tab-context-repository.interface";
import { IAuthProvider } from "./auth-provider.interface"; import { IAuthProvider } from "./auth-provider.interface";
import { IAuthService } from "./auth-service.interface"; import { IAuthService } from "./auth-service.interface";
export class AuthService implements IAuthService { export class AuthService implements IAuthService {
private readonly _respository!: IAuthenticatedUserRepository; private readonly _userRepo!: IAuthenticatedUserRepository;
private readonly _tabContactRepo!: ITabContextRepository;
private readonly _transactionManager!: ITransactionManager; private readonly _transactionManager!: ITransactionManager;
private readonly _authProvider: IAuthProvider; private readonly _authProvider: IAuthProvider;
constructor( constructor(
repository: IAuthenticatedUserRepository, userRepo: IAuthenticatedUserRepository,
tabContextRepo: ITabContextRepository,
transactionManager: ITransactionManager, transactionManager: ITransactionManager,
authProvider: IAuthProvider authProvider: IAuthProvider
) { ) {
this._respository = repository; this._userRepo = userRepo;
this._tabContactRepo = tabContextRepo;
this._transactionManager = transactionManager; this._transactionManager = transactionManager;
this._authProvider = authProvider; this._authProvider = authProvider;
} }
@ -39,7 +44,7 @@ export class AuthService implements IAuthService {
const { username, email, passwordHash } = params; const { username, email, passwordHash } = params;
// Verificar si el usuario ya existe // Verificar si el usuario ya existe
const userExists = await this._respository.findUserByEmail(email, transaction); const userExists = await this._userRepo.findUserByEmail(email, transaction);
if (userExists.isSuccess && userExists.data) { if (userExists.isSuccess && userExists.data) {
return Result.fail(new Error("Email is already registered")); return Result.fail(new Error("Email is already registered"));
} }
@ -64,7 +69,7 @@ export class AuthService implements IAuthService {
return Result.fail(userOrError.error); return Result.fail(userOrError.error);
} }
const createdResult = await this._respository.createUser(userOrError.data, transaction); const createdResult = await this._userRepo.createUser(userOrError.data, transaction);
if (createdResult.isFailure) { if (createdResult.isFailure) {
return Result.fail(createdResult.error); return Result.fail(createdResult.error);
@ -84,21 +89,35 @@ export class AuthService implements IAuthService {
async loginUser(params: { async loginUser(params: {
email: EmailAddress; email: EmailAddress;
passwordHash: PasswordHash; passwordHash: PasswordHash;
}): Promise<Result<AuthenticatedUser, Error>> { tabId: UniqueID;
}): Promise<
Result<
{
user: AuthenticatedUser;
tokens: {
accessToken: string;
refreshToken: string;
};
},
Error
>
> {
try { try {
return await this._transactionManager.complete(async (transaction) => { return await this._transactionManager.complete(async (transaction) => {
const { email, passwordHash } = params; const { email, passwordHash, 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._respository.findUserByEmail(email, transaction); const userResult = await this._userRepo.findUserByEmail(email, transaction);
if (userResult.isFailure) { if (userResult.isFailure) {
return Result.fail(new Error("Invalid email or password")); return Result.fail(new Error("Invalid email or password"));
} }
const user = userResult.data; const user = userResult.data;
if (!user) {
return Result.fail(new Error("Invalid email or password"));
}
// 🔹 Verificar que la contraseña sea correcta // 🔹 Verificar que la contraseña sea correcta
const isValidPassword = await user.comparePassword(passwordHash); const isValidPassword = await user.comparePassword(passwordHash);
@ -106,56 +125,42 @@ export class AuthService implements IAuthService {
return Result.fail(new Error("Invalid email or password")); return Result.fail(new Error("Invalid email or password"));
} }
// Registrar o actualizar el contexto de ese tab ID
const contextOrError = TabContext.create({
userId: user.id,
tabId: tabId,
companyId: UniqueID.generateUndefinedID().data,
branchId: UniqueID.generateUndefinedID().data,
});
if (contextOrError.isFailure) {
return Result.fail(new Error("Error creating user context"));
}
await this._tabContactRepo.registerContext(contextOrError.data, transaction);
// 🔹 Generar Access Token y Refresh Token // 🔹 Generar Access Token y Refresh Token
user.accessToken = this._authProvider.generateAccessToken({ const accessToken = this._authProvider.generateAccessToken({
userId: user.id.toString(), userId: user.id.toString(),
email: email.toString(), email: email.toString(),
tabId: tabId.toString(),
roles: ["USER"], roles: ["USER"],
}); });
user.refreshToken = this._authProvider.generateRefreshToken({ const refreshToken = this._authProvider.generateRefreshToken({
userId: user.id.toString(), userId: user.id.toString(),
}); });
return Result.ok(user); return Result.ok({
user,
tokens: {
accessToken,
refreshToken,
},
});
}); });
} catch (error: unknown) { } catch (error: unknown) {
return Result.fail(error as Error); return Result.fail(error as Error);
} }
} }
/**
* 🔹 `selectCompany`
* Permite a un usuario seleccionar una empresa activa en la sesión bajo transacción.
*/
/*static async selectCompany(
userId: string,
companyId: string
): Promise<Result<{ message: string }, Error>> {
return await authUserRepository.executeTransaction(async (transaction) => {
const user = await authUserRepository.findById(userId, transaction);
if (user.isFailure) {
return Result.fail(new Error("User not found"));
}
const isAssociated = await authUserRepository.isUserAssociatedWithCompany(
userId,
companyId,
transaction
);
if (!isAssociated) {
return Result.fail(new Error("User does not have access to this company"));
}
return Result.ok({ message: "Company selected successfully" });
});
}*/
/**
* 🔹 `logout`
* Simula el cierre de sesión de un usuario. No requiere transacción.
*/
/*static logout(): Result<{ message: string }, never> {
return Result.ok({ message: "Logged out successfully" });
}*/
} }

View File

@ -1,6 +1,6 @@
import { createSequelizeTransactionManager } from "@common/infrastructure"; import { createSequelizeTransactionManager } from "@common/infrastructure";
import { createAuthenticatedUserRepository } from "../infraestructure"; import { createAuthenticatedUserRepository, createTabContextRepository } from "../infraestructure";
import { createPassportAuthProvider } from "../infraestructure/passport/passport-auth-provider"; import { createPassportAuthProvider } from "../infraestructure/passport/passport-auth-provider";
import { IAuthProvider } from "./auth-provider.interface"; import { IAuthProvider } from "./auth-provider.interface";
import { IAuthService } from "./auth-service.interface"; import { IAuthService } from "./auth-service.interface";
@ -12,10 +12,16 @@ export * from "./auth-service.interface";
export const createAuthService = (): IAuthService => { export const createAuthService = (): IAuthService => {
const transactionManager = createSequelizeTransactionManager(); const transactionManager = createSequelizeTransactionManager();
const authenticatedUserRepository = createAuthenticatedUserRepository(); const authenticatedUserRepository = createAuthenticatedUserRepository();
const tabContextRepository = createTabContextRepository();
const authProvider: IAuthProvider = createPassportAuthProvider( const authProvider: IAuthProvider = createPassportAuthProvider(
authenticatedUserRepository, authenticatedUserRepository,
transactionManager transactionManager
); );
return new AuthService(authenticatedUserRepository, transactionManager, authProvider); return new AuthService(
authenticatedUserRepository,
tabContextRepository,
transactionManager,
authProvider
);
}; };

View File

@ -3,7 +3,12 @@ import { TabContext } from "../domain";
export interface ITabContextService { export interface ITabContextService {
getByTabId(tabId: UniqueID): Promise<Result<TabContext, Error>>; getByTabId(tabId: UniqueID): Promise<Result<TabContext, Error>>;
createContext(tabId: UniqueID, userId: UniqueID): Promise<Result<TabContext, Error>>; createContext(params: {
tabId: UniqueID;
userId: UniqueID;
companyId: UniqueID;
branchId: UniqueID;
}): Promise<Result<TabContext, Error>>;
assignCompany(tabId: UniqueID, companyId: UniqueID): Promise<Result<void, Error>>; assignCompany(tabId: UniqueID, companyId: UniqueID): Promise<Result<void, Error>>;
removeContext(tabId: UniqueID): Promise<Result<void, Error>>; removeContext(tabId: UniqueID): Promise<Result<void, Error>>;
} }

View File

@ -39,7 +39,14 @@ export class TabContextService implements ITabContextService {
/** /**
* Registra un nuevo contexto de pestaña para un usuario * Registra un nuevo contexto de pestaña para un usuario
*/ */
async createContext(tabId: UniqueID, userId: UniqueID): Promise<Result<TabContext, Error>> { async createContext(params: {
tabId: UniqueID;
userId: UniqueID;
companyId: UniqueID;
branchId: UniqueID;
}): Promise<Result<TabContext, Error>> {
const { tabId, userId, companyId, branchId } = params;
if (!userId || !tabId) { if (!userId || !tabId) {
return Result.fail(new Error("User ID and Tab ID are required")); return Result.fail(new Error("User ID and Tab ID are required"));
} }
@ -50,6 +57,8 @@ export class TabContextService implements ITabContextService {
{ {
userId, userId,
tabId, tabId,
companyId,
branchId,
}, },
UniqueID.generateNewID().data UniqueID.generateNewID().data
); );
@ -58,7 +67,7 @@ export class TabContextService implements ITabContextService {
return Result.fail(contextOrError.error); return Result.fail(contextOrError.error);
} }
await this._respository.createContext(contextOrError.data, transaction); await this._respository.registerContext(contextOrError.data, transaction);
return Result.ok(contextOrError.data); return Result.ok(contextOrError.data);
}); });

View File

@ -3,38 +3,27 @@ import { DomainEntity, Result, UniqueID } from "@common/domain";
export interface ITabContextProps { export interface ITabContextProps {
tabId: UniqueID; tabId: UniqueID;
userId: UniqueID; userId: UniqueID;
companyId?: UniqueID; companyId: UniqueID;
branchId?: UniqueID; branchId: UniqueID;
} }
export interface ITabContext { export interface ITabContext {
tabId: UniqueID; tabId: UniqueID;
userId: UniqueID; userId: UniqueID;
companyId?: UniqueID; companyId: UniqueID;
branchId?: UniqueID; branchId: UniqueID;
assignCompany(companyId: UniqueID): void; hasCompanyAssigned(): boolean;
assignBranch(branchId: UniqueID): void; hasBranchAssigned(): boolean;
toPersistenceData(): any; toPersistenceData(): any;
} }
export class TabContext extends DomainEntity<ITabContextProps> implements ITabContext { export class TabContext extends DomainEntity<ITabContextProps> implements ITabContext {
private _companyId: UniqueID | undefined;
private _branchId: UniqueID | undefined;
static create(props: ITabContextProps, id?: UniqueID): Result<TabContext, Error> { static create(props: ITabContextProps, id?: UniqueID): Result<TabContext, Error> {
return Result.ok(new this(props, id)); return Result.ok(new this(props, id));
} }
protected constructor(props: ITabContextProps, id?: UniqueID) {
const { tabId, userId, companyId, branchId } = props;
super({ tabId, userId }, id);
this._companyId = companyId;
this._branchId = branchId;
}
get tabId(): UniqueID { get tabId(): UniqueID {
return this._props.tabId; return this._props.tabId;
} }
@ -43,28 +32,20 @@ export class TabContext extends DomainEntity<ITabContextProps> implements ITabCo
return this._props.userId; return this._props.userId;
} }
get companyId(): UniqueID | undefined { get companyId(): UniqueID {
return this._companyId; return this._props.companyId;
} }
get branchId(): UniqueID | undefined { get branchId(): UniqueID {
return this._branchId; return this._props.branchId;
} }
hasCompanyAssigned(): boolean { hasCompanyAssigned(): boolean {
return this._companyId !== undefined; return this._props.companyId.isDefined();
}
assignCompany(companyId: UniqueID): void {
this._companyId = companyId;
} }
hasBranchAssigned(): boolean { hasBranchAssigned(): boolean {
return this._branchId !== undefined; return this._props.branchId.isDefined();
}
assignBranch(branchId: UniqueID): void {
this._branchId = branchId;
} }
/** /**
@ -74,8 +55,8 @@ export class TabContext extends DomainEntity<ITabContextProps> implements ITabCo
return { return {
id: this._id.toString(), id: this._id.toString(),
user_id: this.userId.toString(), user_id: this.userId.toString(),
company_id: this._companyId ? this._companyId.toString() : undefined, company_id: this.companyId.toString(),
branchId: this._branchId ? this._branchId.toString() : undefined, branchId: this.branchId.toString(),
}; };
} }
} }

View File

@ -4,7 +4,7 @@ import { TabContext } from "../entities";
export interface ITabContextRepository { export interface ITabContextRepository {
getContextByTabId(tabId: UniqueID, transaction?: any): Promise<Result<TabContext, Error>>; getContextByTabId(tabId: UniqueID, transaction?: any): Promise<Result<TabContext, Error>>;
createContext(context: TabContext, transaction?: Transaction): Promise<Result<void, Error>>; registerContext(context: TabContext, transaction?: Transaction): Promise<Result<void, Error>>;
contextExists(tabId: UniqueID, transaction?: any): Promise<Result<boolean, Error>>; contextExists(tabId: UniqueID, transaction?: any): Promise<Result<boolean, Error>>;
updateCompanyByTabId( updateCompanyByTabId(
tabId: UniqueID, tabId: UniqueID,

View File

@ -1,5 +1,5 @@
import { Result, UniqueID } from "@common/domain"; import { Result, UniqueID } from "@common/domain";
import { EmailAddress, PasswordHash, TabContext, Username } from "@contexts/auth/domain"; import { TabContext } from "@contexts/auth/domain";
import { ITabContextMapper } from "./tab-context-mapper.interface"; import { ITabContextMapper } from "./tab-context-mapper.interface";
export class TabContextMapper implements ITabContextMapper { export class TabContextMapper implements ITabContextMapper {
@ -10,16 +10,18 @@ export class TabContextMapper implements ITabContextMapper {
// 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(entity.id);
const userIdResult = Username.create(entity.user_id); const tabIdResult = UniqueID.create(entity.tab_id);
const companyIdResult = PasswordHash.create(entity.passwordHash); const userIdResult = UniqueID.create(entity.user_id);
const brachIdResult = EmailAddress.create(entity.email); const companyIdResult = UniqueID.create(entity.company_id, false);
const brachIdResult = UniqueID.create(entity.branch_id, false);
// 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([
uniqueIdResult, uniqueIdResult,
usernameResult, tabIdResult,
userIdResult,
companyIdResult, companyIdResult,
emailResult, brachIdResult,
]); ]);
if (okOrError.isFailure) { if (okOrError.isFailure) {
return Result.fail(okOrError.error.message); return Result.fail(okOrError.error.message);
@ -28,11 +30,10 @@ export class TabContextMapper implements ITabContextMapper {
// Crear el agregado de dominio // Crear el agregado de dominio
return TabContext.create( return TabContext.create(
{ {
username: usernameResult.data!, tabId: tabIdResult.data!,
email: emailResult.data!, userId: userIdResult.data!,
passwordHash: companyIdResult.data!, companyId: companyIdResult.data,
roles: entity.roles || [], branchId: brachIdResult.data,
token: entity.token,
}, },
uniqueIdResult.data! uniqueIdResult.data!
); );

View File

@ -3,7 +3,7 @@ import { SequelizeRepository } from "@common/infrastructure";
import { TabContext } from "@contexts/auth/domain/"; import { TabContext } from "@contexts/auth/domain/";
import { ITabContextRepository } from "@contexts/auth/domain/repositories/tab-context-repository.interface"; import { ITabContextRepository } from "@contexts/auth/domain/repositories/tab-context-repository.interface";
import { Transaction } from "sequelize"; import { Transaction } from "sequelize";
import { ITabContextMapper } from "../mappers"; import { createTabContextMapper, ITabContextMapper } from "../mappers";
import { TabContextModel } from "./tab-context.model"; import { TabContextModel } from "./tab-context.model";
export class TabContextRepository export class TabContextRepository
@ -66,23 +66,22 @@ export class TabContextRepository
} }
} }
async createContext( /**
* Crea un contexto para un tab id o actualiza si ya existe
* @param context
* @param transaction
* @returns
*/
async registerContext(
context: TabContext, context: TabContext,
transaction?: Transaction transaction?: Transaction
): Promise<Result<void, Error>> { ): Promise<Result<void, Error>> {
try { try {
const { id } = context; const { id } = context;
const persistenceData = this._mapper.toPersistence(context); const persistenceData = this._mapper.toPersistence(context);
await TabContextModel.create(
{ await this._save(TabContextModel, id, persistenceData, {}, transaction);
...persistenceData,
id: id.toString(),
},
{
include: [{ all: true }],
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);
@ -126,3 +125,8 @@ export class TabContextRepository
} }
} }
} }
export const createTabContextRepository = (): ITabContextRepository => {
const tabContextMapper = createTabContextMapper();
return new TabContextRepository(tabContextMapper);
};

View File

@ -1,3 +1,4 @@
import { UniqueID } from "@common/domain";
import { ExpressController } from "@common/presentation"; import { ExpressController } from "@common/presentation";
import { createAuthService, IAuthService } from "@contexts/auth/application"; import { createAuthService, IAuthService } from "@contexts/auth/application";
import { EmailAddress, PasswordHash } from "@contexts/auth/domain"; import { EmailAddress, PasswordHash } from "@contexts/auth/domain";
@ -14,16 +15,19 @@ class LoginController extends ExpressController {
} }
async executeImpl() { async executeImpl() {
const tabId = this.req.headers["x-tab-id"];
const emailVO = EmailAddress.create(this.req.body.email); const emailVO = EmailAddress.create(this.req.body.email);
const passwordHashVO = PasswordHash.create(this.req.body.password); const passwordHashVO = PasswordHash.create(this.req.body.password);
const tabIdVO = UniqueID.create(String(tabId));
if ([emailVO, passwordHashVO].some((r) => r.isFailure)) { if ([emailVO, passwordHashVO, tabIdVO].some((r) => r.isFailure)) {
return this.clientError("Invalid input data"); return this.clientError("Invalid input data");
} }
const userOrError = await this._authService.loginUser({ const userOrError = await this._authService.loginUser({
email: emailVO.data, email: emailVO.data,
passwordHash: passwordHashVO.data, passwordHash: passwordHashVO.data,
tabId: tabIdVO.data,
}); });
if (userOrError.isFailure) { if (userOrError.isFailure) {

View File

@ -2,20 +2,40 @@ import { AuthenticatedUser } from "@contexts/auth/domain";
import { ILoginUserResponseDTO } from "../../dto"; import { ILoginUserResponseDTO } from "../../dto";
export interface ILoginPresenter { export interface ILoginPresenter {
map: (user: AuthenticatedUser) => ILoginUserResponseDTO; map: (data: {
user: AuthenticatedUser;
tokens: {
accessToken: string;
refreshToken: string;
};
}) => ILoginUserResponseDTO;
} }
export const LoginPresenter: ILoginPresenter = { export const LoginPresenter: ILoginPresenter = {
map: (user: AuthenticatedUser): ILoginUserResponseDTO => { map: (data: {
//const { user, token, refreshToken } = loginUser; user: AuthenticatedUser;
//const roles = user.getRoles()?.map((rol) => rol.toString()) || []; tokens: {
accessToken: string;
refreshToken: string;
};
}): ILoginUserResponseDTO => {
const {
user,
tokens: { accessToken, refreshToken },
} = data;
const userData = user.toPersistenceData(); const userData = user.toPersistenceData();
return { return {
user_id: userData, user: {
access_token: userData.accessToken, id: userData.id,
refresh_token: userData.refreshToken, email: userData.email,
username: userData.username,
},
tokens: {
access_token: accessToken,
refresh_token: refreshToken,
},
}; };
}, },
}; };

View File

@ -5,14 +5,16 @@ export interface IRegisterUserResponseDTO {
} }
export interface ILoginUserResponseDTO { export interface ILoginUserResponseDTO {
access_token: string;
refresh_token: string;
user: { user: {
id: string; id: string;
username: string; username: string;
email: string; email: string;
}; };
tab_id: string; tokens: {
access_token: string;
refresh_token: string;
};
//tab_id: string;
} }
export interface ILogoutResponseDTO { export interface ILogoutResponseDTO {

View File

@ -32,7 +32,7 @@ export const validateTabContext = async (req: Request, res: Response, next: Next
); );
} }
const contextOrError = await TabContextRepository.getByTabId(tabId); const contextOrError = await TabContextRepository.getContextByTabId(tabId);
if (contextOrError.isFailure) { if (contextOrError.isFailure) {
return ExpressController.errorResponse( return ExpressController.errorResponse(
new ApiError({ new ApiError({

View File

@ -1,9 +1,7 @@
import { validateRequest } from "@common/presentation"; import { validateRequest } from "@common/presentation";
import { validateTabHeader } from "@contexts/auth/presentation"; import { validateTabHeader } from "@contexts/auth/presentation";
import { import { createLoginController } from "@contexts/auth/presentation/controllers";
createLoginController, import { createRegisterController } from "@contexts/auth/presentation/controllers/register/register.controller";
createRegisterController,
} from "@contexts/auth/presentation/controllers";
import { LoginUserSchema, RegisterUserSchema } from "@contexts/auth/presentation/dto"; import { LoginUserSchema, RegisterUserSchema } from "@contexts/auth/presentation/dto";
import { Router } from "express"; import { Router } from "express";