From 3568e7e4381258552f37990ad3e1793aa45c788b Mon Sep 17 00:00:00 2001 From: david Date: Thu, 30 Jan 2025 11:45:31 +0100 Subject: [PATCH] . --- apps/server/src/config/index.ts | 1 + .../contexts/auth/application/auth.service.ts | 114 ++++++++++++++++++ .../src/contexts/auth/application/index.ts | 1 + .../auth/domain/auth-user.model.ts | 2 +- apps/server/src/contexts/auth/domain/index.ts | 2 + .../domain/value-objects/auth-user-roles.ts | 2 +- .../value-objects/email-address.spec.ts | 0 .../domain/value-objects/email-address.ts | 2 +- .../auth/domain/value-objects/index.ts | 0 .../value-objects/password-hash.spec.ts | 0 .../domain/value-objects/password-hash.ts | 2 +- .../auth/domain/value-objects/username.ts | 2 +- .../sequelize/auth-user-repository.spec.ts | 72 +++++++++++ .../sequelize/auth-user.model.ts | 62 ++++++++++ .../sequelize/auth-user.repository.ts | 42 +++++++ .../auth/infraestructure/sequelize/index.ts | 2 + .../src/{ => contexts}/common/domain/index.ts | 1 + .../src/contexts/common/domain/result.spec.ts | 45 +++++++ .../{ => contexts}/common/domain/result.ts | 2 +- .../contexts/common/domain/unique-id.spec.ts | 35 ++++++ .../{ => contexts}/common/domain/unique-id.ts | 0 .../common/domain/value-object.ts | 0 .../contexts/common/infraestructure/index.ts | 1 + .../common/infraestructure/sequelize/index.ts | 1 + .../sequelize/sequelize-repository.ts | 68 +++++++++++ apps/server/src/index.ts | 2 +- .../src/{config => infrastructure}/app.ts | 13 +- .../src/infrastructure/express/auth.routes.ts | 13 ++ .../src/infrastructure/express/index.ts | 1 + apps/server/src/infrastructure/index.ts | 1 + apps/server/tsconfig.json | 5 +- 31 files changed, 478 insertions(+), 16 deletions(-) create mode 100644 apps/server/src/config/index.ts create mode 100644 apps/server/src/contexts/auth/application/auth.service.ts create mode 100644 apps/server/src/contexts/auth/application/index.ts rename apps/server/src/{ => contexts}/auth/domain/auth-user.model.ts (96%) create mode 100644 apps/server/src/contexts/auth/domain/index.ts rename apps/server/src/{ => contexts}/auth/domain/value-objects/auth-user-roles.ts (90%) rename apps/server/src/{ => contexts}/auth/domain/value-objects/email-address.spec.ts (100%) rename apps/server/src/{ => contexts}/auth/domain/value-objects/email-address.ts (91%) rename apps/server/src/{ => contexts}/auth/domain/value-objects/index.ts (100%) rename apps/server/src/{ => contexts}/auth/domain/value-objects/password-hash.spec.ts (100%) rename apps/server/src/{ => contexts}/auth/domain/value-objects/password-hash.ts (94%) rename apps/server/src/{ => contexts}/auth/domain/value-objects/username.ts (92%) create mode 100644 apps/server/src/contexts/auth/infraestructure/sequelize/auth-user-repository.spec.ts create mode 100644 apps/server/src/contexts/auth/infraestructure/sequelize/auth-user.model.ts create mode 100644 apps/server/src/contexts/auth/infraestructure/sequelize/auth-user.repository.ts create mode 100644 apps/server/src/contexts/auth/infraestructure/sequelize/index.ts rename apps/server/src/{ => contexts}/common/domain/index.ts (66%) create mode 100644 apps/server/src/contexts/common/domain/result.spec.ts rename apps/server/src/{ => contexts}/common/domain/result.ts (97%) create mode 100644 apps/server/src/contexts/common/domain/unique-id.spec.ts rename apps/server/src/{ => contexts}/common/domain/unique-id.ts (100%) rename apps/server/src/{ => contexts}/common/domain/value-object.ts (100%) create mode 100644 apps/server/src/contexts/common/infraestructure/index.ts create mode 100644 apps/server/src/contexts/common/infraestructure/sequelize/index.ts create mode 100644 apps/server/src/contexts/common/infraestructure/sequelize/sequelize-repository.ts rename apps/server/src/{config => infrastructure}/app.ts (81%) create mode 100644 apps/server/src/infrastructure/express/auth.routes.ts create mode 100644 apps/server/src/infrastructure/express/index.ts create mode 100644 apps/server/src/infrastructure/index.ts diff --git a/apps/server/src/config/index.ts b/apps/server/src/config/index.ts new file mode 100644 index 00000000..c30cd664 --- /dev/null +++ b/apps/server/src/config/index.ts @@ -0,0 +1 @@ +export * from "./database"; diff --git a/apps/server/src/contexts/auth/application/auth.service.ts b/apps/server/src/contexts/auth/application/auth.service.ts new file mode 100644 index 00000000..06831003 --- /dev/null +++ b/apps/server/src/contexts/auth/application/auth.service.ts @@ -0,0 +1,114 @@ +import { EmailAddress, PasswordHash, Username } from "contexts/auth/domain"; +import { authUserRepository } from "contexts/auth/infraestructure/sequelize"; +import { Result, UniqueID } from "contexts/common/domain"; + +export class AuthService { + /** + * 馃敼 `registerUser` + * Registra un nuevo usuario en la base de datos bajo transacci贸n. + */ + static async registerUser( + username: string, + email: string, + password: string + ): Promise> { + return await authUserRepository.executeTransaction(async (transaction) => { + const userIdResult = UniqueID.generateNewID(); + const usernameResult = Username.create(username); + const emailResult = EmailAddress.create(email); + const passwordResult = await PasswordHash.create(password); + + const combined = Result.combine([userIdResult, usernameResult, emailResult, passwordResult]); + if (combined.isError()) { + return Result.fail(combined.error); + } + + // Verificar si el usuario ya existe + const userExists = await authUserRepository.userExists( + emailResult.data.getValue(), + transaction + ); + if (userExists) { + return Result.fail(new Error("Email is already registered")); + } + + const user = await authUserRepository.createUser( + { + id: userIdResult.data.getValue(), + username: usernameResult.data.getValue(), + email: emailResult.data.getValue(), + password: passwordResult.data.getValue(), + isActive: true, + }, + transaction + ); + + return Result.ok({ userId: user.id }); + }); + } + + /** + * 馃敼 `login` + * Autentica un usuario y genera un token JWT bajo transacci贸n. + */ + static async login( + email: string, + password: string + ): Promise> { + return await authUserRepository.executeTransaction(async (transaction) => { + const emailResult = EmailAddress.create(email); + if (emailResult.isError()) { + return Result.fail(emailResult.error); + } + + const user = await authUserRepository.findByEmail(emailResult.data.getValue(), transaction); + if (user.isError()) { + return Result.fail(new Error("Invalid email or password")); + } + + const isValidPassword = await user.data.validatePassword(password); + if (!isValidPassword) { + return Result.fail(new Error("Invalid email or password")); + } + + const token = JwtHelper.generateToken({ userId: user.data.getUserID() }); + + return Result.ok({ token, userId: user.data.getUserID() }); + }); + } + + /** + * 馃敼 `selectCompany` + * Permite a un usuario seleccionar una empresa activa en la sesi贸n bajo transacci贸n. + */ + static async selectCompany( + userId: string, + companyId: string + ): Promise> { + return await authUserRepository.executeTransaction(async (transaction) => { + const user = await authUserRepository.findById(userId, transaction); + if (user.isError()) { + 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" }); + } +} diff --git a/apps/server/src/contexts/auth/application/index.ts b/apps/server/src/contexts/auth/application/index.ts new file mode 100644 index 00000000..2ab33e86 --- /dev/null +++ b/apps/server/src/contexts/auth/application/index.ts @@ -0,0 +1 @@ +export * from "./auth.service"; diff --git a/apps/server/src/auth/domain/auth-user.model.ts b/apps/server/src/contexts/auth/domain/auth-user.model.ts similarity index 96% rename from apps/server/src/auth/domain/auth-user.model.ts rename to apps/server/src/contexts/auth/domain/auth-user.model.ts index 3647270e..05ba9671 100644 --- a/apps/server/src/auth/domain/auth-user.model.ts +++ b/apps/server/src/contexts/auth/domain/auth-user.model.ts @@ -1,4 +1,4 @@ -import { Result } from "@common/domain"; +import { Result } from "contexts/common/domain"; import { EmailAddress, PasswordHash, Username, UserRoles } from "./value-objects"; export class AuthUser { diff --git a/apps/server/src/contexts/auth/domain/index.ts b/apps/server/src/contexts/auth/domain/index.ts new file mode 100644 index 00000000..360112c0 --- /dev/null +++ b/apps/server/src/contexts/auth/domain/index.ts @@ -0,0 +1,2 @@ +export * from "./auth-user.model"; +export * from "./value-objects"; diff --git a/apps/server/src/auth/domain/value-objects/auth-user-roles.ts b/apps/server/src/contexts/auth/domain/value-objects/auth-user-roles.ts similarity index 90% rename from apps/server/src/auth/domain/value-objects/auth-user-roles.ts rename to apps/server/src/contexts/auth/domain/value-objects/auth-user-roles.ts index 435ba6b4..7039ab97 100644 --- a/apps/server/src/auth/domain/value-objects/auth-user-roles.ts +++ b/apps/server/src/contexts/auth/domain/value-objects/auth-user-roles.ts @@ -1,4 +1,4 @@ -import { Result, ValueObject } from "@common/domain"; +import { Result, ValueObject } from "contexts/common/domain"; import { z } from "zod"; const RoleSchema = z.enum(["Admin", "User", "Manager", "Editor"]); diff --git a/apps/server/src/auth/domain/value-objects/email-address.spec.ts b/apps/server/src/contexts/auth/domain/value-objects/email-address.spec.ts similarity index 100% rename from apps/server/src/auth/domain/value-objects/email-address.spec.ts rename to apps/server/src/contexts/auth/domain/value-objects/email-address.spec.ts diff --git a/apps/server/src/auth/domain/value-objects/email-address.ts b/apps/server/src/contexts/auth/domain/value-objects/email-address.ts similarity index 91% rename from apps/server/src/auth/domain/value-objects/email-address.ts rename to apps/server/src/contexts/auth/domain/value-objects/email-address.ts index ed583e37..e288ef07 100644 --- a/apps/server/src/auth/domain/value-objects/email-address.ts +++ b/apps/server/src/contexts/auth/domain/value-objects/email-address.ts @@ -1,4 +1,4 @@ -import { Result, ValueObject } from "@common/domain"; +import { Result, ValueObject } from "contexts/common/domain"; import { z } from "zod"; export class EmailAddress extends ValueObject { diff --git a/apps/server/src/auth/domain/value-objects/index.ts b/apps/server/src/contexts/auth/domain/value-objects/index.ts similarity index 100% rename from apps/server/src/auth/domain/value-objects/index.ts rename to apps/server/src/contexts/auth/domain/value-objects/index.ts diff --git a/apps/server/src/auth/domain/value-objects/password-hash.spec.ts b/apps/server/src/contexts/auth/domain/value-objects/password-hash.spec.ts similarity index 100% rename from apps/server/src/auth/domain/value-objects/password-hash.spec.ts rename to apps/server/src/contexts/auth/domain/value-objects/password-hash.spec.ts diff --git a/apps/server/src/auth/domain/value-objects/password-hash.ts b/apps/server/src/contexts/auth/domain/value-objects/password-hash.ts similarity index 94% rename from apps/server/src/auth/domain/value-objects/password-hash.ts rename to apps/server/src/contexts/auth/domain/value-objects/password-hash.ts index 9e9402d5..de3208fb 100644 --- a/apps/server/src/auth/domain/value-objects/password-hash.ts +++ b/apps/server/src/contexts/auth/domain/value-objects/password-hash.ts @@ -1,5 +1,5 @@ -import { Result, ValueObject } from "@common/domain"; import bcrypt from "bcrypt"; +import { Result, ValueObject } from "contexts/common/domain"; import { z } from "zod"; const PasswordSchema = z diff --git a/apps/server/src/auth/domain/value-objects/username.ts b/apps/server/src/contexts/auth/domain/value-objects/username.ts similarity index 92% rename from apps/server/src/auth/domain/value-objects/username.ts rename to apps/server/src/contexts/auth/domain/value-objects/username.ts index 9e47e740..d9ba26e6 100644 --- a/apps/server/src/auth/domain/value-objects/username.ts +++ b/apps/server/src/contexts/auth/domain/value-objects/username.ts @@ -1,4 +1,4 @@ -import { Result, ValueObject } from "@common/domain"; +import { Result, ValueObject } from "contexts/common/domain"; import { z } from "zod"; export class Username extends ValueObject { diff --git a/apps/server/src/contexts/auth/infraestructure/sequelize/auth-user-repository.spec.ts b/apps/server/src/contexts/auth/infraestructure/sequelize/auth-user-repository.spec.ts new file mode 100644 index 00000000..79588368 --- /dev/null +++ b/apps/server/src/contexts/auth/infraestructure/sequelize/auth-user-repository.spec.ts @@ -0,0 +1,72 @@ +import { authUserRepository } from "./auth-user.repository"; + +describe("authUserRepository", () => { + beforeEach(() => { + // Resetear la base de datos antes de cada prueba + }); + + it("should create a user successfully", async () => { + const result = await authUserRepository.createUser({ + id: "user-uuid", + username: "testUser", + email: "user@example.com", + password: "hashed-password", + isActive: true, + }); + + expect(result).toHaveProperty("id"); + expect(result.email).toBe("user@example.com"); + }); + + /*it("should find a user by email", async () => { + await authUserRepository.createUser({ + id: "user-uuid", + username: "testUser", + email: "user@example.com", + password: "hashed-password", + isActive: true, + }); + + const user = await authUserRepository.findByEmail("user@example.com"); + expect(user.isOk()).toBe(true); + expect(user.data?.getUserID()).toBe("user-uuid"); + }); + + it("should return an error when user is not found", async () => { + const user = await authUserRepository.findByEmail("notfound@example.com"); + expect(user.isError()).toBe(true); + expect(user.error.message).toBe("User not found"); + });*/ + + it("should check if a user exists", async () => { + await authUserRepository.createUser({ + id: "user-uuid", + username: "testUser", + email: "exists@example.com", + password: "hashed-password", + isActive: true, + }); + + expect(await authUserRepository.userExists("exists@example.com")).toBe(true); + expect(await authUserRepository.userExists("notfound@example.com")).toBe(false); + }); + + it("should count active users", async () => { + await authUserRepository.createUser({ + id: "1", + username: "user1", + email: "user1@example.com", + password: "pass", + isActive: true, + }); + await authUserRepository.createUser({ + id: "2", + username: "user2", + email: "user2@example.com", + password: "pass", + isActive: false, + }); + + expect(await authUserRepository.countActiveUsers()).toBe(1); + }); +}); diff --git a/apps/server/src/contexts/auth/infraestructure/sequelize/auth-user.model.ts b/apps/server/src/contexts/auth/infraestructure/sequelize/auth-user.model.ts new file mode 100644 index 00000000..3f6fb567 --- /dev/null +++ b/apps/server/src/contexts/auth/infraestructure/sequelize/auth-user.model.ts @@ -0,0 +1,62 @@ +import { DataTypes, InferAttributes, InferCreationAttributes, Model } from "sequelize"; +import { sequelize } from "../../../../config/database"; + +export type AuthUserCreationAttributes = InferCreationAttributes; + +class AuthUserModel extends Model< + InferAttributes, + InferCreationAttributes +> { + public id!: string; + public username!: string; + public email!: string; + public password!: string; + public roles!: string[]; + public isActive!: boolean; +} + +AuthUserModel.init( + { + id: { + type: DataTypes.UUID, + primaryKey: true, + allowNull: false, + }, + username: { + type: DataTypes.STRING, + allowNull: false, + }, + email: { + type: DataTypes.STRING, + allowNull: false, + unique: true, + }, + password: { + type: DataTypes.STRING, + allowNull: false, + }, + roles: { + type: DataTypes.ARRAY(DataTypes.STRING), + allowNull: false, + defaultValue: [], + }, + isActive: { + type: DataTypes.BOOLEAN, + defaultValue: true, + }, + }, + { + sequelize, + tableName: "users", + paranoid: true, // softs deletes + timestamps: true, + + createdAt: "created_at", + updatedAt: "updated_at", + deletedAt: "deleted_at", + + indexes: [{ name: "email_idx", fields: ["email"], unique: true }], + } +); + +export { AuthUserModel }; diff --git a/apps/server/src/contexts/auth/infraestructure/sequelize/auth-user.repository.ts b/apps/server/src/contexts/auth/infraestructure/sequelize/auth-user.repository.ts new file mode 100644 index 00000000..23258b03 --- /dev/null +++ b/apps/server/src/contexts/auth/infraestructure/sequelize/auth-user.repository.ts @@ -0,0 +1,42 @@ +import { SequelizeRepository } from "contexts/common/infraestructure"; +import { Transaction } from "sequelize"; +import { AuthUserModel } from "./auth-user.model"; + +class AuthUserRepository extends SequelizeRepository { + constructor() { + super(AuthUserModel); + } + + async createUser( + data: { id: string; username: string; email: string; password: string; isActive: boolean }, + transaction?: Transaction + ): Promise { + return await this.create(data, transaction); + } + + async findAllUsers(): Promise { + return await this.findAll(); + } + + async isUserAssociatedWithCompany( + userId: string, + companyId: string, + transaction?: Transaction + ): Promise { + const association = await AuthUserModel.findOne({ + where: { id: userId, companyId }, + transaction, + }); + return !!association; + } + + async userExists(email: string, transaction?: Transaction): Promise { + return await this.exists("email", email, transaction); + } + + async countActiveUsers(transaction?: Transaction): Promise { + return await this.count({ where: { isActive: true }, transaction }); + } +} + +export const authUserRepository = new AuthUserRepository(); diff --git a/apps/server/src/contexts/auth/infraestructure/sequelize/index.ts b/apps/server/src/contexts/auth/infraestructure/sequelize/index.ts new file mode 100644 index 00000000..4d692f7c --- /dev/null +++ b/apps/server/src/contexts/auth/infraestructure/sequelize/index.ts @@ -0,0 +1,2 @@ +export * from "./auth-user.model"; +export * from "./auth-user.repository"; diff --git a/apps/server/src/common/domain/index.ts b/apps/server/src/contexts/common/domain/index.ts similarity index 66% rename from apps/server/src/common/domain/index.ts rename to apps/server/src/contexts/common/domain/index.ts index 361eb538..f2052b13 100644 --- a/apps/server/src/common/domain/index.ts +++ b/apps/server/src/contexts/common/domain/index.ts @@ -1,2 +1,3 @@ export * from "./result"; +export * from "./unique-id"; export * from "./value-object"; diff --git a/apps/server/src/contexts/common/domain/result.spec.ts b/apps/server/src/contexts/common/domain/result.spec.ts new file mode 100644 index 00000000..2a26f8e1 --- /dev/null +++ b/apps/server/src/contexts/common/domain/result.spec.ts @@ -0,0 +1,45 @@ +import { Result } from "./result"; + +describe("Result", () => { + it("should create a successful result", () => { + const result = Result.ok("Success Data"); + + expect(result.isOk()).toBe(true); + expect(result.isError()).toBe(false); + expect(result.data).toBe("Success Data"); + }); + + it("should create a failed result", () => { + const error = new Error("Test error"); + const result = Result.fail(error); + + expect(result.isOk()).toBe(false); + expect(result.isError()).toBe(true); + expect(result.error).toBe(error); + }); + + it("should getOrElse return default value if result is a failure", () => { + const error = new Error("Test error"); + const result = Result.fail(error); + + expect(result.getOrElse("Default")).toBe("Default"); + }); + + it("should match execute correct function based on success or failure", () => { + const successResult = Result.ok("Success"); + const failureResult = Result.fail(new Error("Failure")); + + expect( + successResult.match( + (data) => `OK: ${data}`, + (error) => null + ) + ).toBe("OK: Success"); + expect( + failureResult.match( + (data) => null, + (error) => `ERROR: ${error.message}` + ) + ).toBe("ERROR: Failure"); + }); +}); diff --git a/apps/server/src/common/domain/result.ts b/apps/server/src/contexts/common/domain/result.ts similarity index 97% rename from apps/server/src/common/domain/result.ts rename to apps/server/src/contexts/common/domain/result.ts index 31ce41bb..d48f1288 100644 --- a/apps/server/src/common/domain/result.ts +++ b/apps/server/src/contexts/common/domain/result.ts @@ -64,7 +64,7 @@ export class Result { * 馃敼 `getOrElse(defaultValue: T): T` * Si el `Result` es un `ok`, devuelve `data`, de lo contrario, devuelve `defaultValue`. */ - getOrElse(defaultValue: T): T { + getOrElse(defaultValue: any): T | any { return this.isSuccess ? this.data : defaultValue; } diff --git a/apps/server/src/contexts/common/domain/unique-id.spec.ts b/apps/server/src/contexts/common/domain/unique-id.spec.ts new file mode 100644 index 00000000..811ace92 --- /dev/null +++ b/apps/server/src/contexts/common/domain/unique-id.spec.ts @@ -0,0 +1,35 @@ +import { UniqueID } from "./unique-id"; + +describe("UniqueID Value Object", () => { + it("should generate a new UUID using generateNewID()", () => { + const result = UniqueID.generateNewID(); + + expect(result.isOk()).toBe(true); + expect(result.data.getValue()).toMatch( + /^[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", () => { + const result = UniqueID.create("invalid-uuid"); + + expect(result.isError()).toBe(true); + expect(result.error.message).toBe("Invalid UUID format"); + }); + + it("should create a valid UniqueID from an existing UUID", () => { + const validUUID = "550e8400-e29b-41d4-a716-446655440000"; + const result = UniqueID.create(validUUID); + + expect(result.isOk()).toBe(true); + expect(result.data.getValue()).toBe(validUUID); + }); + + it("should correctly convert UniqueID to string", () => { + const validUUID = "550e8400-e29b-41d4-a716-446655440000"; + const result = UniqueID.create(validUUID); + + expect(result.isOk()).toBe(true); + expect(result.data.toString()).toBe(validUUID); + }); +}); diff --git a/apps/server/src/common/domain/unique-id.ts b/apps/server/src/contexts/common/domain/unique-id.ts similarity index 100% rename from apps/server/src/common/domain/unique-id.ts rename to apps/server/src/contexts/common/domain/unique-id.ts diff --git a/apps/server/src/common/domain/value-object.ts b/apps/server/src/contexts/common/domain/value-object.ts similarity index 100% rename from apps/server/src/common/domain/value-object.ts rename to apps/server/src/contexts/common/domain/value-object.ts diff --git a/apps/server/src/contexts/common/infraestructure/index.ts b/apps/server/src/contexts/common/infraestructure/index.ts new file mode 100644 index 00000000..62f8ac11 --- /dev/null +++ b/apps/server/src/contexts/common/infraestructure/index.ts @@ -0,0 +1 @@ +export * from "./sequelize"; diff --git a/apps/server/src/contexts/common/infraestructure/sequelize/index.ts b/apps/server/src/contexts/common/infraestructure/sequelize/index.ts new file mode 100644 index 00000000..74dfc4bc --- /dev/null +++ b/apps/server/src/contexts/common/infraestructure/sequelize/index.ts @@ -0,0 +1 @@ +export * from "./sequelize-repository"; diff --git a/apps/server/src/contexts/common/infraestructure/sequelize/sequelize-repository.ts b/apps/server/src/contexts/common/infraestructure/sequelize/sequelize-repository.ts new file mode 100644 index 00000000..fe8d7692 --- /dev/null +++ b/apps/server/src/contexts/common/infraestructure/sequelize/sequelize-repository.ts @@ -0,0 +1,68 @@ +import { sequelize } from "@config/database"; +import { FindOptions, Model, ModelDefined, Transaction } from "sequelize"; + +export abstract class SequelizeRepository { + protected readonly model: ModelDefined; + + protected constructor(model: ModelDefined) { + this.model = model; + } + + async findById(id: string, transaction?: Transaction): Promise { + return await this.model.findByPk(id, { transaction }); + } + + async findOneByField(field: string, value: any, transaction?: Transaction): Promise { + return await this.model.findOne({ where: { [field]: value }, transaction }); + } + + async findAll(filter?: FindOptions, transaction?: Transaction): Promise { + return await this.model.findAll({ ...filter, transaction }); + } + + async create(data: Partial, transaction?: Transaction): Promise { + return await this.model.create(data as any, { transaction }); + } + + async update(id: string, data: Partial, transaction?: Transaction): Promise<[number, T[]]> { + return await this.model.update(data as any, { where: { id }, returning: true, transaction }); + } + + async delete(id: string, transaction?: Transaction): Promise { + const deleted = await this.model.destroy({ where: { id }, transaction }); + return deleted > 0; + } + + /** + * 馃敼 `exists` + * Verifica si un registro existe en la base de datos basado en un campo y valor. + */ + async exists(field: string, value: any, transaction?: Transaction): Promise { + const count = await this.model.count({ where: { [field]: value }, transaction }); + return count > 0; + } + + /** + * 馃敼 `count` + * Cuenta el n煤mero de registros que cumplen con una condici贸n. + */ + async count(filter?: FindOptions, transaction?: Transaction): Promise { + return await this.model.count({ ...filter, transaction }); + } + + /** + * 馃敼 `executeTransaction` + * Ejecuta una funci贸n dentro de una transacci贸n de Sequelize. + */ + async executeTransaction(operation: (transaction: Transaction) => Promise): Promise { + const transaction = await sequelize.transaction(); + try { + const result = await operation(transaction); + await transaction.commit(); + return result; + } catch (error) { + await transaction.rollback(); + throw error; + } + } +} diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index eb41f3c9..3c36556f 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -1,5 +1,5 @@ -import { createApp } from "./config/app" import { connectToDatabase } from "./config/database"; +import { createApp } from "./infrastructure/app"; const PORT = process.env.PORT || 3000; diff --git a/apps/server/src/config/app.ts b/apps/server/src/infrastructure/app.ts similarity index 81% rename from apps/server/src/config/app.ts rename to apps/server/src/infrastructure/app.ts index fe9f45f4..99375305 100644 --- a/apps/server/src/config/app.ts +++ b/apps/server/src/infrastructure/app.ts @@ -1,7 +1,8 @@ -import express, { Application } from "express"; -import responseTime from "response-time"; -import helmet from "helmet"; import dotenv from "dotenv"; +import express, { Application } from "express"; +import helmet from "helmet"; +import responseTime from "response-time"; +import { authRoutes } from "./express"; dotenv.config(); @@ -22,10 +23,8 @@ export function createApp(): Application { app.set("port", process.env.PORT ?? 3002); - // Rutas (placeholder para bounded contexts) - app.get("/", (req, res) => { - res.json({ message: "隆Servidor funcionando!" }); - }); + // Registrar rutas del m贸dulo de autenticaci贸n + app.use("/api/auth", authRoutes); return app; } diff --git a/apps/server/src/infrastructure/express/auth.routes.ts b/apps/server/src/infrastructure/express/auth.routes.ts new file mode 100644 index 00000000..435d959f --- /dev/null +++ b/apps/server/src/infrastructure/express/auth.routes.ts @@ -0,0 +1,13 @@ +import { Router } from "express"; +import * as authController from "../../application/auth/auth.controller"; + +export const authRoutes = () => { + const router = Router(); + + router.post("/register", authController.register); + router.post("/login", authController.login); + router.post("/select-company", authController.selectCompany); + router.post("/logout", authController.logout); + + return router; +}; diff --git a/apps/server/src/infrastructure/express/index.ts b/apps/server/src/infrastructure/express/index.ts new file mode 100644 index 00000000..0a2a2585 --- /dev/null +++ b/apps/server/src/infrastructure/express/index.ts @@ -0,0 +1 @@ +export * from "./auth.routes"; diff --git a/apps/server/src/infrastructure/index.ts b/apps/server/src/infrastructure/index.ts new file mode 100644 index 00000000..ac5307de --- /dev/null +++ b/apps/server/src/infrastructure/index.ts @@ -0,0 +1 @@ +export * from "./app"; diff --git a/apps/server/tsconfig.json b/apps/server/tsconfig.json index 248bb68d..12d8bc4f 100644 --- a/apps/server/tsconfig.json +++ b/apps/server/tsconfig.json @@ -8,8 +8,9 @@ "allowSyntheticDefaultImports": true, "baseUrl": "src", "paths": { - "@shared/*": ["shared/*"], - "@common/*": ["common/*"] + "@shared/*": ["../../packages/shared/*"], + "@common/*": ["common/*"], + "@config/*": ["config/*"] } },