.
This commit is contained in:
parent
ad12c3a67a
commit
a8c72b1d64
@ -9,6 +9,11 @@
|
||||
"date": 1738578708264,
|
||||
"name": "debug-2025-02-03.log",
|
||||
"hash": "48ca17f819e391cb5ae1909a6ee0fa4d9c8cdb6667ef893547acb004f4a79737"
|
||||
},
|
||||
{
|
||||
"date": 1738664864746,
|
||||
"name": "debug-2025-02-04.log",
|
||||
"hash": "7f1ecce0e9a97fbb99865ac9bfc3591897975a2fd9562164c9be52aabc47f47f"
|
||||
}
|
||||
],
|
||||
"hashType": "sha256"
|
||||
|
||||
@ -9,6 +9,11 @@
|
||||
"date": 1738578708262,
|
||||
"name": "error-2025-02-03.log",
|
||||
"hash": "a21f154f5c386a75eee98a35c2b100da7df1b8002cf99851b90bd12810f1fe8a"
|
||||
},
|
||||
{
|
||||
"date": 1738664864743,
|
||||
"name": "error-2025-02-04.log",
|
||||
"hash": "dfb19c1e5b9c2039572425939e77f4d4ab3285df0fcded1edfba3e7c4cc2a94d"
|
||||
}
|
||||
],
|
||||
"hashType": "sha256"
|
||||
|
||||
@ -15,6 +15,36 @@ export abstract class SequelizeRepository<T> implements IAggregateRootRepository
|
||||
});
|
||||
}
|
||||
|
||||
protected async _getBy(
|
||||
model: ModelDefined<any, any>,
|
||||
field: string,
|
||||
value: any,
|
||||
params: any = {},
|
||||
transaction?: Transaction
|
||||
): Promise<any> {
|
||||
const where: { [key: string]: any } = {};
|
||||
|
||||
where[field] = value;
|
||||
|
||||
return model.findOne({
|
||||
where,
|
||||
transaction,
|
||||
...params,
|
||||
});
|
||||
}
|
||||
|
||||
protected async _getById(
|
||||
model: ModelDefined<any, any>,
|
||||
id: UniqueID | string,
|
||||
params: any = {},
|
||||
transaction?: Transaction
|
||||
): Promise<any> {
|
||||
return model.findByPk(id.toString(), {
|
||||
transaction,
|
||||
...params,
|
||||
});
|
||||
}
|
||||
|
||||
protected async _exists(
|
||||
model: ModelDefined<any, any>,
|
||||
field: string,
|
||||
|
||||
@ -51,8 +51,9 @@ export abstract class ExpressController {
|
||||
return ExpressController.errorResponse(
|
||||
new ApiError({
|
||||
status: 401,
|
||||
title: "Unauthorized",
|
||||
detail: message ?? "You are not authorized to access this resource.",
|
||||
title: httpStatus["401"],
|
||||
name: httpStatus["401_NAME"],
|
||||
detail: message ?? httpStatus["401_MESSAGE"],
|
||||
}),
|
||||
this.res
|
||||
);
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import { Result, UniqueID } from "@common/domain";
|
||||
import { TabContext } from "../domain";
|
||||
|
||||
export interface ITabContextService {
|
||||
getByTabId(tabId: UniqueID): Promise<Result<TabContext, Error>>;
|
||||
createContext(tabId: UniqueID, userId: UniqueID): Promise<Result<TabContext, Error>>;
|
||||
assignCompany(tabId: UniqueID, companyId: UniqueID): Promise<Result<void, Error>>;
|
||||
removeContext(tabId: UniqueID): Promise<Result<void, Error>>;
|
||||
}
|
||||
108
apps/server/src/contexts/auth/application/tab-context.service.ts
Normal file
108
apps/server/src/contexts/auth/application/tab-context.service.ts
Normal file
@ -0,0 +1,108 @@
|
||||
import { Result, UniqueID } from "@common/domain";
|
||||
import { ITransactionManager } from "@common/infrastructure/database";
|
||||
import { TabContext } from "../domain";
|
||||
import { ITabContextRepository } from "../domain/repositories/tab-context-repository.interface";
|
||||
import { ITabContextService } from "./tab-context-service.interface";
|
||||
|
||||
export class TabContextService implements ITabContextService {
|
||||
private readonly _respository!: ITabContextRepository;
|
||||
private readonly _transactionManager!: ITransactionManager;
|
||||
|
||||
constructor(repository: ITabContextRepository, transactionManager: ITransactionManager) {
|
||||
this._respository = repository;
|
||||
this._transactionManager = transactionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene el contexto de una pestaña por su ID
|
||||
*/
|
||||
async getByTabId(tabId: UniqueID): Promise<Result<TabContext, Error>> {
|
||||
try {
|
||||
return await this._transactionManager.complete(async (transaction) => {
|
||||
// Verificar si la pestaña existe
|
||||
const tabContextOrError = await this._respository.getContextByTabId(tabId, transaction);
|
||||
if (tabContextOrError.isSuccess && !tabContextOrError.data) {
|
||||
return Result.fail(new Error("Invalid or expired Tab ID"));
|
||||
}
|
||||
|
||||
if (tabContextOrError.isFailure) {
|
||||
return Result.fail(tabContextOrError.error);
|
||||
}
|
||||
|
||||
return Result.ok(tabContextOrError.data);
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
return Result.fail(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registra un nuevo contexto de pestaña para un usuario
|
||||
*/
|
||||
async createContext(tabId: UniqueID, userId: UniqueID): Promise<Result<TabContext, Error>> {
|
||||
if (!userId || !tabId) {
|
||||
return Result.fail(new Error("User ID and Tab ID are required"));
|
||||
}
|
||||
|
||||
try {
|
||||
return await this._transactionManager.complete(async (transaction) => {
|
||||
const contextOrError = TabContext.create(
|
||||
{
|
||||
userId,
|
||||
tabId,
|
||||
},
|
||||
UniqueID.generateNewID().data
|
||||
);
|
||||
|
||||
if (contextOrError.isFailure) {
|
||||
return Result.fail(contextOrError.error);
|
||||
}
|
||||
|
||||
await this._respository.createContext(contextOrError.data, transaction);
|
||||
|
||||
return Result.ok(contextOrError.data);
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
return Result.fail(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asigna una empresa activa a un contexto de pestaña
|
||||
*/
|
||||
async assignCompany(tabId: UniqueID, companyId: UniqueID): Promise<Result<void, Error>> {
|
||||
if (!companyId || !tabId) {
|
||||
return Result.fail(new Error("Tab ID and Company ID are required"));
|
||||
}
|
||||
|
||||
try {
|
||||
return await this._transactionManager.complete(async (transaction) => {
|
||||
// Verificar si la pestaña existe
|
||||
const tabContextOrError = await this._respository.contextExists(tabId, transaction);
|
||||
if (tabContextOrError.isFailure || !tabContextOrError.data) {
|
||||
return Result.fail(new Error("Invalid or expired Tab ID"));
|
||||
}
|
||||
|
||||
return await this._respository.updateCompanyByTabId(tabId, companyId, transaction);
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
return Result.fail(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Elimina un contexto de pestaña por su ID
|
||||
*/
|
||||
async removeContext(tabId: UniqueID): Promise<Result<void, Error>> {
|
||||
if (!tabId) {
|
||||
return Result.fail(new Error("Tab ID is required"));
|
||||
}
|
||||
try {
|
||||
return await this._transactionManager.complete(async (transaction) => {
|
||||
return await this._respository.deleteContextByTabId(tabId, transaction);
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
return Result.fail(error as Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
1
apps/server/src/contexts/auth/domain/entities/index.ts
Normal file
1
apps/server/src/contexts/auth/domain/entities/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./tab-context";
|
||||
81
apps/server/src/contexts/auth/domain/entities/tab-context.ts
Normal file
81
apps/server/src/contexts/auth/domain/entities/tab-context.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { DomainEntity, Result, UniqueID } from "@common/domain";
|
||||
|
||||
export interface ITabContextProps {
|
||||
tabId: UniqueID;
|
||||
userId: UniqueID;
|
||||
companyId?: UniqueID;
|
||||
branchId?: UniqueID;
|
||||
}
|
||||
|
||||
export interface ITabContext {
|
||||
tabId: UniqueID;
|
||||
userId: UniqueID;
|
||||
companyId?: UniqueID;
|
||||
branchId?: UniqueID;
|
||||
|
||||
assignCompany(companyId: UniqueID): void;
|
||||
assignBranch(branchId: UniqueID): void;
|
||||
|
||||
toPersistenceData(): any;
|
||||
}
|
||||
|
||||
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> {
|
||||
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 {
|
||||
return this._props.tabId;
|
||||
}
|
||||
|
||||
get userId(): UniqueID {
|
||||
return this._props.userId;
|
||||
}
|
||||
|
||||
get companyId(): UniqueID | undefined {
|
||||
return this._companyId;
|
||||
}
|
||||
|
||||
get branchId(): UniqueID | undefined {
|
||||
return this._branchId;
|
||||
}
|
||||
|
||||
hasCompanyAssigned(): boolean {
|
||||
return this._companyId !== undefined;
|
||||
}
|
||||
|
||||
assignCompany(companyId: UniqueID): void {
|
||||
this._companyId = companyId;
|
||||
}
|
||||
|
||||
hasBranchAssigned(): boolean {
|
||||
return this._branchId !== undefined;
|
||||
}
|
||||
|
||||
assignBranch(branchId: UniqueID): void {
|
||||
this._branchId = branchId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔹 Devuelve una representación lista para persistencia
|
||||
*/
|
||||
toPersistenceData(): any {
|
||||
return {
|
||||
id: this._id.toString(),
|
||||
user_id: this.userId.toString(),
|
||||
company_id: this._companyId ? this._companyId.toString() : undefined,
|
||||
branchId: this._branchId ? this._branchId.toString() : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
export * from "./aggregates/authenticated-user";
|
||||
export * from "./events/user-authenticated.event";
|
||||
export * from "./aggregates";
|
||||
export * from "./entities";
|
||||
export * from "./events";
|
||||
export * from "./repositories";
|
||||
export * from "./value-objects";
|
||||
|
||||
@ -6,7 +6,7 @@ export interface IAuthenticatedUserRepository {
|
||||
findUserByEmail(
|
||||
email: EmailAddress,
|
||||
transaction?: any
|
||||
): Promise<Result<AuthenticatedUser | null, Error>>;
|
||||
|
||||
): Promise<Result<AuthenticatedUser, Error>>;
|
||||
userExists(email: EmailAddress, transaction?: any): Promise<Result<boolean, Error>>;
|
||||
createUser(user: AuthenticatedUser, transaction?: any): Promise<Result<void, Error>>;
|
||||
}
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
import { Result, UniqueID } from "@common/domain";
|
||||
import { Transaction } from "sequelize";
|
||||
import { TabContext } from "../entities";
|
||||
|
||||
export interface ITabContextRepository {
|
||||
getContextByTabId(tabId: UniqueID, transaction?: any): Promise<Result<TabContext, Error>>;
|
||||
createContext(context: TabContext, transaction?: Transaction): Promise<Result<void, Error>>;
|
||||
contextExists(tabId: UniqueID, transaction?: any): Promise<Result<boolean, Error>>;
|
||||
updateCompanyByTabId(
|
||||
tabId: UniqueID,
|
||||
companyId: UniqueID,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<void, Error>>;
|
||||
deleteContextByTabId(tabId: UniqueID, transaction?: any): Promise<Result<void, Error>>;
|
||||
}
|
||||
@ -2,13 +2,6 @@ import { Result } from "@common/domain";
|
||||
import { AuthenticatedUser } from "@contexts/auth/domain";
|
||||
|
||||
export interface IAuthenticatedUserMapper {
|
||||
/**
|
||||
* 🔹 Convierte una entidad de la base de datos en un agregado de dominio `AuthenticatedUser`
|
||||
*/
|
||||
toDomain(entity: any): Result<AuthenticatedUser, Error>;
|
||||
|
||||
/**
|
||||
* 🔹 Convierte un agregado `AuthenticatedUser` en un objeto listo para persistencia
|
||||
*/
|
||||
toPersistence(aggregate: AuthenticatedUser): any;
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ export class AuthenticatedUserMapper implements IAuthenticatedUserMapper {
|
||||
const usernameResult = Username.create(entity.username);
|
||||
const passwordHashResult = PasswordHash.create(entity.passwordHash);
|
||||
const emailResult = EmailAddress.create(entity.email);
|
||||
1;
|
||||
|
||||
// Validar que no haya errores en la creación de los Value Objects
|
||||
const okOrError = Result.combine([
|
||||
uniqueIdResult,
|
||||
@ -35,7 +35,6 @@ export class AuthenticatedUserMapper implements IAuthenticatedUserMapper {
|
||||
email: emailResult.data!,
|
||||
passwordHash: passwordHashResult.data!,
|
||||
roles: entity.roles || [],
|
||||
token: entity.token,
|
||||
},
|
||||
uniqueIdResult.data!
|
||||
);
|
||||
|
||||
@ -1,2 +1,4 @@
|
||||
export * from "./authenticated-user-mapper.interface";
|
||||
export * from "./authenticated-user.mapper";
|
||||
export * from "./tab-context-mapper.interface";
|
||||
export * from "./tab-context.mapper";
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
import { Result } from "@common/domain";
|
||||
import { TabContext } from "@contexts/auth/domain";
|
||||
|
||||
export interface ITabContextMapper {
|
||||
toDomain(entity: any): Result<TabContext, Error>;
|
||||
toPersistence(aggregate: TabContext): any;
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
import { Result, UniqueID } from "@common/domain";
|
||||
import { EmailAddress, PasswordHash, TabContext, Username } from "@contexts/auth/domain";
|
||||
import { ITabContextMapper } from "./tab-context-mapper.interface";
|
||||
|
||||
export class TabContextMapper implements ITabContextMapper {
|
||||
toDomain(entity: any): Result<TabContext, Error> {
|
||||
if (!entity) {
|
||||
return Result.fail(new Error("Entity not found"));
|
||||
}
|
||||
|
||||
// Crear Value Objects asegurando que sean válidos
|
||||
const uniqueIdResult = UniqueID.create(entity.id);
|
||||
const userIdResult = Username.create(entity.user_id);
|
||||
const companyIdResult = PasswordHash.create(entity.passwordHash);
|
||||
const brachIdResult = EmailAddress.create(entity.email);
|
||||
|
||||
// Validar que no haya errores en la creación de los Value Objects
|
||||
const okOrError = Result.combine([
|
||||
uniqueIdResult,
|
||||
usernameResult,
|
||||
companyIdResult,
|
||||
emailResult,
|
||||
]);
|
||||
if (okOrError.isFailure) {
|
||||
return Result.fail(okOrError.error.message);
|
||||
}
|
||||
|
||||
// Crear el agregado de dominio
|
||||
return TabContext.create(
|
||||
{
|
||||
username: usernameResult.data!,
|
||||
email: emailResult.data!,
|
||||
passwordHash: companyIdResult.data!,
|
||||
roles: entity.roles || [],
|
||||
token: entity.token,
|
||||
},
|
||||
uniqueIdResult.data!
|
||||
);
|
||||
}
|
||||
|
||||
toPersistence(tabContext: TabContext): any {
|
||||
return tabContext.toPersistenceData();
|
||||
}
|
||||
}
|
||||
|
||||
export const createTabContextMapper = (): ITabContextMapper => new TabContextMapper();
|
||||
@ -1,6 +1,19 @@
|
||||
import { DataTypes, InferAttributes, InferCreationAttributes, Model, Sequelize } from "sequelize";
|
||||
import {
|
||||
DataTypes,
|
||||
InferAttributes,
|
||||
InferCreationAttributes,
|
||||
Model,
|
||||
NonAttribute,
|
||||
Sequelize,
|
||||
} from "sequelize";
|
||||
import { TabContextCreationAttributes, TabContextModel } from "./tab-context.model";
|
||||
|
||||
export type AuthUserCreationAttributes = InferCreationAttributes<AuthUserModel>;
|
||||
export type AuthUserCreationAttributes = InferCreationAttributes<
|
||||
AuthUserModel,
|
||||
{ omit: "contexts" }
|
||||
> & {
|
||||
contexts: TabContextCreationAttributes[];
|
||||
};
|
||||
|
||||
export class AuthUserModel extends Model<
|
||||
InferAttributes<AuthUserModel>,
|
||||
@ -11,13 +24,23 @@ export class AuthUserModel extends Model<
|
||||
return Promise.resolve();
|
||||
}*/
|
||||
|
||||
static associate(connection: Sequelize) {}
|
||||
static associate(connection: Sequelize) {
|
||||
const { TabContextModel } = connection.models;
|
||||
|
||||
AuthUserModel.hasMany(TabContextModel, {
|
||||
as: "contexts",
|
||||
foreignKey: "user_id",
|
||||
onDelete: "CASCADE",
|
||||
});
|
||||
}
|
||||
declare id: string;
|
||||
declare username: string;
|
||||
declare email: string;
|
||||
declare password: string;
|
||||
declare roles: string[];
|
||||
declare isActive: boolean;
|
||||
|
||||
declare contexts: NonAttribute<TabContextModel[]>;
|
||||
}
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
@ -68,6 +91,12 @@ export default (sequelize: Sequelize) => {
|
||||
deletedAt: "deleted_at",
|
||||
|
||||
indexes: [{ name: "email_idx", fields: ["email"], unique: true }],
|
||||
|
||||
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
|
||||
|
||||
defaultScope: {},
|
||||
|
||||
scopes: {},
|
||||
}
|
||||
);
|
||||
return AuthUserModel;
|
||||
|
||||
@ -52,7 +52,7 @@ export class AuthenticatedUserRepository
|
||||
async findUserByEmail(
|
||||
email: EmailAddress,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<AuthenticatedUser | null, Error>> {
|
||||
): Promise<Result<AuthenticatedUser, Error>> {
|
||||
try {
|
||||
const rawUser: any = await this._findById(
|
||||
AuthUserModel,
|
||||
@ -62,7 +62,7 @@ export class AuthenticatedUserRepository
|
||||
);
|
||||
|
||||
if (!rawUser === true) {
|
||||
return Result.ok(null);
|
||||
return Result.fail(new Error("User with email not exists"));
|
||||
}
|
||||
|
||||
return this._mapper.toDomain(rawUser);
|
||||
|
||||
@ -1,2 +1,4 @@
|
||||
export * from "./auth-user.model";
|
||||
export * from "./authenticated-user.repository";
|
||||
export * from "./tab-context.model";
|
||||
export * from "./tab-context.repository";
|
||||
|
||||
@ -0,0 +1,87 @@
|
||||
import {
|
||||
DataTypes,
|
||||
InferAttributes,
|
||||
InferCreationAttributes,
|
||||
Model,
|
||||
NonAttribute,
|
||||
Sequelize,
|
||||
} from "sequelize";
|
||||
import { AuthUserModel } from "./auth-user.model";
|
||||
|
||||
export type TabContextCreationAttributes = InferCreationAttributes<
|
||||
TabContextModel,
|
||||
{ omit: "user" }
|
||||
>;
|
||||
|
||||
export class TabContextModel extends Model<
|
||||
InferAttributes<TabContextModel, { omit: "user" }>,
|
||||
InferCreationAttributes<TabContextModel, { omit: "user" }>
|
||||
> {
|
||||
// To avoid table creation
|
||||
/*static async sync(): Promise<any> {
|
||||
return Promise.resolve();
|
||||
}*/
|
||||
static associate(connection: Sequelize) {
|
||||
const { AuthUserModel } = connection.models;
|
||||
|
||||
TabContextModel.belongsTo(AuthUserModel, {
|
||||
as: "user",
|
||||
foreignKey: "user_id",
|
||||
onDelete: "CASCADE",
|
||||
});
|
||||
}
|
||||
|
||||
declare id: string;
|
||||
declare tab_id: string;
|
||||
declare user_id: string;
|
||||
declare company_id: string;
|
||||
declare branch_id: string;
|
||||
|
||||
declare user: NonAttribute<AuthUserModel>;
|
||||
}
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
TabContextModel.init(
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
primaryKey: true,
|
||||
},
|
||||
user_id: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
},
|
||||
tab_id: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
},
|
||||
company_id: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
},
|
||||
branch_id: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
tableName: "user_tab_contexts",
|
||||
paranoid: true, // softs deletes
|
||||
timestamps: true,
|
||||
|
||||
createdAt: "created_at",
|
||||
updatedAt: "updated_at",
|
||||
deletedAt: "deleted_at",
|
||||
|
||||
indexes: [{ name: "tab_id_idx", fields: ["tab_id"], unique: true }],
|
||||
|
||||
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
|
||||
|
||||
defaultScope: {},
|
||||
|
||||
scopes: {},
|
||||
}
|
||||
);
|
||||
return TabContextModel;
|
||||
};
|
||||
@ -0,0 +1,128 @@
|
||||
import { Result, UniqueID } from "@common/domain";
|
||||
import { SequelizeRepository } from "@common/infrastructure";
|
||||
import { TabContext } from "@contexts/auth/domain/";
|
||||
import { ITabContextRepository } from "@contexts/auth/domain/repositories/tab-context-repository.interface";
|
||||
import { Transaction } from "sequelize";
|
||||
import { ITabContextMapper } from "../mappers";
|
||||
import { TabContextModel } from "./tab-context.model";
|
||||
|
||||
export class TabContextRepository
|
||||
extends SequelizeRepository<TabContext>
|
||||
implements ITabContextRepository
|
||||
{
|
||||
private readonly _mapper!: ITabContextMapper;
|
||||
|
||||
/**
|
||||
* 🔹 Función personalizada para mapear errores de unicidad en autenticación
|
||||
*/
|
||||
private _customErrorMapper(error: Error): string | null {
|
||||
if (error.name === "SequelizeUniqueConstraintError") {
|
||||
return "Tab context already exists";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
constructor(mapper: ITabContextMapper) {
|
||||
super();
|
||||
this._mapper = mapper;
|
||||
}
|
||||
|
||||
async getContextByTabId(
|
||||
tabId: UniqueID,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<TabContext, Error>> {
|
||||
try {
|
||||
const rawContext = await this._getBy(
|
||||
TabContextModel,
|
||||
"tab_id",
|
||||
tabId.toString(),
|
||||
{},
|
||||
transaction
|
||||
);
|
||||
|
||||
if (!rawContext === true) {
|
||||
return Result.fail(new Error("Tab context not exists"));
|
||||
}
|
||||
|
||||
return this._mapper.toDomain(rawContext);
|
||||
} catch (error: any) {
|
||||
return this._handleDatabaseError(error, this._customErrorMapper);
|
||||
}
|
||||
}
|
||||
|
||||
async contextExists(tabId: UniqueID, transaction?: any): Promise<Result<boolean, Error>> {
|
||||
try {
|
||||
const result: any = await this._exists(
|
||||
TabContextModel,
|
||||
"tab_id",
|
||||
tabId.toString(),
|
||||
transaction
|
||||
);
|
||||
|
||||
return Result.ok(Boolean(result));
|
||||
} catch (error: any) {
|
||||
return this._handleDatabaseError(error, this._customErrorMapper);
|
||||
}
|
||||
}
|
||||
|
||||
async createContext(
|
||||
context: TabContext,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<void, Error>> {
|
||||
try {
|
||||
const { id } = context;
|
||||
const persistenceData = this._mapper.toPersistence(context);
|
||||
await TabContextModel.create(
|
||||
{
|
||||
...persistenceData,
|
||||
id: id.toString(),
|
||||
},
|
||||
{
|
||||
include: [{ all: true }],
|
||||
transaction,
|
||||
}
|
||||
);
|
||||
return Result.ok();
|
||||
} catch (error: any) {
|
||||
return this._handleDatabaseError(error, this._customErrorMapper);
|
||||
}
|
||||
}
|
||||
|
||||
async updateCompanyByTabId(
|
||||
tabId: UniqueID,
|
||||
companyId: UniqueID,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<void, Error>> {
|
||||
try {
|
||||
await TabContextModel.update(
|
||||
{ company_id: companyId.toString() },
|
||||
{
|
||||
where: {
|
||||
tab_id: tabId.toString(),
|
||||
},
|
||||
transaction,
|
||||
}
|
||||
);
|
||||
|
||||
return Result.ok();
|
||||
} catch (error: any) {
|
||||
return this._handleDatabaseError(error, this._customErrorMapper);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteContextByTabId(tabId: UniqueID, transaction?: any): Promise<Result<void, Error>> {
|
||||
try {
|
||||
await TabContextModel.destroy({
|
||||
where: {
|
||||
tab_id: tabId.toString(),
|
||||
},
|
||||
transaction,
|
||||
force: false,
|
||||
});
|
||||
return Result.ok();
|
||||
} catch (error: any) {
|
||||
return this._handleDatabaseError(error, this._customErrorMapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7,7 +7,12 @@ export interface IRegisterUserResponseDTO {
|
||||
export interface ILoginUserResponseDTO {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
user_id: string;
|
||||
user: {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
};
|
||||
tab_id: string;
|
||||
}
|
||||
|
||||
export interface ILogoutResponseDTO {
|
||||
|
||||
@ -1 +1,2 @@
|
||||
export * from "./passport-auth.middleware";
|
||||
export * from "./tab-context.middleware";
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
import { ApiError, ExpressController } from "@common/presentation";
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import httpStatus from "http-status";
|
||||
|
||||
export const validateTabHeader = (req: Request, res: Response, next: NextFunction) => {
|
||||
const tabId = req.headers["x-tab-id"];
|
||||
if (!tabId) {
|
||||
return ExpressController.errorResponse(
|
||||
new ApiError({
|
||||
status: 401,
|
||||
title: httpStatus["401"],
|
||||
name: httpStatus["401_NAME"],
|
||||
detail: "Tab ID is required",
|
||||
}),
|
||||
res
|
||||
);
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
export const validateTabContext = async (req: Request, res: Response, next: NextFunction) => {
|
||||
const tabId = req.headers["x-tab-id"];
|
||||
if (!tabId) {
|
||||
return ExpressController.errorResponse(
|
||||
new ApiError({
|
||||
status: 401,
|
||||
title: httpStatus["401"],
|
||||
name: httpStatus["401_NAME"],
|
||||
detail: "Tab ID is required",
|
||||
}),
|
||||
res
|
||||
);
|
||||
}
|
||||
|
||||
const contextOrError = await TabContextRepository.getByTabId(tabId);
|
||||
if (contextOrError.isFailure) {
|
||||
return ExpressController.errorResponse(
|
||||
new ApiError({
|
||||
status: 401,
|
||||
title: httpStatus["401"],
|
||||
name: httpStatus["401_NAME"],
|
||||
detail: "Invalid or expired Tab ID",
|
||||
}),
|
||||
res
|
||||
);
|
||||
}
|
||||
|
||||
const context = contextOrError.data;
|
||||
|
||||
req.user = { id: context.user_id, company_id: context.company_id };
|
||||
next();
|
||||
};
|
||||
@ -1,4 +1,5 @@
|
||||
import { validateRequest } from "@common/presentation";
|
||||
import { validateTabHeader } from "@contexts/auth/presentation";
|
||||
import {
|
||||
createLoginController,
|
||||
createRegisterController,
|
||||
@ -41,9 +42,14 @@ export const authRouter = (appRouter: Router) => {
|
||||
*
|
||||
* @apiError (401) {String} message Invalid email or password.
|
||||
*/
|
||||
authRoutes.post("/login", validateRequest(LoginUserSchema), (req, res, next) => {
|
||||
createLoginController().execute(req, res, next);
|
||||
});
|
||||
authRoutes.post(
|
||||
"/login",
|
||||
validateRequest(LoginUserSchema),
|
||||
validateTabHeader,
|
||||
(req, res, next) => {
|
||||
createLoginController().execute(req, res, next);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* @api {post} /api/auth/select-company Select an active company
|
||||
|
||||
Loading…
Reference in New Issue
Block a user