Limpieza
This commit is contained in:
parent
c91455ffc2
commit
4beb7aa207
@ -1,99 +0,0 @@
|
||||
import {
|
||||
EmailAddress,
|
||||
PhoneNumber,
|
||||
PostalAddress,
|
||||
TINNumber,
|
||||
UniqueID,
|
||||
} from "@/core/common/domain";
|
||||
|
||||
import {
|
||||
type Account,
|
||||
AccountStatus,
|
||||
type IAccountProps,
|
||||
type IAccountService,
|
||||
} from "@/contexts/accounts/domain";
|
||||
import { ITransactionManager } from "@/core/common/infrastructure/database";
|
||||
import { logger } from "@/core/logger";
|
||||
import { Maybe, Result } from "@repo/rdx-utils";
|
||||
import { ICreateAccountRequestDTO } from "../presentation";
|
||||
|
||||
export class CreateAccountUseCase {
|
||||
constructor(
|
||||
private readonly accountService: IAccountService,
|
||||
private readonly transactionManager: ITransactionManager
|
||||
) {}
|
||||
|
||||
public execute(
|
||||
accountID: UniqueID,
|
||||
dto: ICreateAccountRequestDTO
|
||||
): Promise<Result<Account, Error>> {
|
||||
return this.transactionManager.complete(async (transaction) => {
|
||||
try {
|
||||
const validOrErrors = this.validateAccountData(dto);
|
||||
if (validOrErrors.isFailure) {
|
||||
return Result.fail(validOrErrors.error);
|
||||
}
|
||||
|
||||
const data = validOrErrors.data;
|
||||
|
||||
// Update account with dto
|
||||
return await this.accountService.createAccount(accountID, data, transaction);
|
||||
} catch (error: unknown) {
|
||||
logger.error(error as Error);
|
||||
return Result.fail(error as Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private validateAccountData(dto: ICreateAccountRequestDTO): Result<IAccountProps, Error> {
|
||||
const errors: Error[] = [];
|
||||
|
||||
const tinOrError = TINNumber.create(dto.tin);
|
||||
const emailOrError = EmailAddress.create(dto.email);
|
||||
const phoneOrError = PhoneNumber.create(dto.phone);
|
||||
const faxOrError = PhoneNumber.createNullable(dto.fax);
|
||||
const postalAddressOrError = PostalAddress.create({
|
||||
street: dto.street,
|
||||
city: dto.city,
|
||||
state: dto.state,
|
||||
postalCode: dto.postal_code,
|
||||
country: dto.country,
|
||||
});
|
||||
|
||||
const result = Result.combine([
|
||||
tinOrError,
|
||||
emailOrError,
|
||||
phoneOrError,
|
||||
faxOrError,
|
||||
postalAddressOrError,
|
||||
]);
|
||||
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
}
|
||||
|
||||
const validatedData: IAccountProps = {
|
||||
status: AccountStatus.createInactive(),
|
||||
isFreelancer: dto.is_companyr,
|
||||
name: dto.name,
|
||||
tradeName: dto.trade_name ? Maybe.some(dto.trade_name) : Maybe.none(),
|
||||
tin: tinOrError.data,
|
||||
address: postalAddressOrError.data,
|
||||
email: emailOrError.data,
|
||||
phone: phoneOrError.data,
|
||||
fax: faxOrError.data,
|
||||
website: dto.website ? Maybe.some(dto.website) : Maybe.none(),
|
||||
legalRecord: dto.legal_record,
|
||||
defaultTax: dto.default_tax,
|
||||
langCode: dto.language_code,
|
||||
currencyCode: dto.currency_code,
|
||||
logo: dto.logo ? Maybe.some(dto.logo) : Maybe.none(),
|
||||
};
|
||||
|
||||
if (errors.length > 0) {
|
||||
const message = errors.map((err) => err.message).toString();
|
||||
return Result.fail(new Error(message));
|
||||
}
|
||||
return Result.ok(validatedData);
|
||||
}
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
import { Account, IAccountService } from "@/contexts/accounts/domain";
|
||||
import { UniqueID } from "@/core/common/domain";
|
||||
import { ITransactionManager } from "@/core/common/infrastructure/database";
|
||||
import { logger } from "@/core/logger";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
export class GetAccountUseCase {
|
||||
constructor(
|
||||
private readonly accountService: IAccountService,
|
||||
private readonly transactionManager: ITransactionManager
|
||||
) {}
|
||||
|
||||
public execute(accountID: UniqueID): Promise<Result<Account, Error>> {
|
||||
return this.transactionManager.complete(async (transaction) => {
|
||||
try {
|
||||
return await this.accountService.findAccountById(accountID, transaction);
|
||||
} catch (error: unknown) {
|
||||
logger.error(error as Error);
|
||||
return Result.fail(error as Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
export * from "./create-account.use-case";
|
||||
export * from "./get-account.use-case";
|
||||
export * from "./list-accounts.use-case";
|
||||
export * from "./update-account.use-case";
|
||||
@ -1,21 +0,0 @@
|
||||
import { Account, IAccountService } from "@/contexts/accounts/domain";
|
||||
import { ITransactionManager } from "@/core/common/infrastructure/database";
|
||||
import { Collection, Result } from "@repo/rdx-utils";
|
||||
|
||||
export class ListAccountsUseCase {
|
||||
constructor(
|
||||
private readonly accountService: IAccountService,
|
||||
private readonly transactionManager: ITransactionManager
|
||||
) {}
|
||||
|
||||
public execute(): Promise<Result<Collection<Account>, Error>> {
|
||||
return this.transactionManager.complete(async (transaction) => {
|
||||
try {
|
||||
return await this.accountService.findAccounts(transaction);
|
||||
} catch (error: unknown) {
|
||||
logger.error(error as Error);
|
||||
return Result.fail(error as Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
import { UniqueID } from "@/core/common/domain";
|
||||
import { TransactionManager } from "@/core/common/infrastructure/database";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { AccountService } from "../domain";
|
||||
import { UpdateAccountUseCase } from "./update-account.use-case";
|
||||
|
||||
const mockAccountService: AccountService = {
|
||||
updateAccountById: jest.fn(),
|
||||
} as unknown as AccountService;
|
||||
|
||||
const mockTransactionManager: TransactionManager = {
|
||||
complete(work: (transaction: any) => Promise<any>): void {
|
||||
jest.fn();
|
||||
},
|
||||
} as unknown as TransactionManager;
|
||||
|
||||
const id = UniqueID.create("", true).data;
|
||||
|
||||
describe("UpdateAccountUseCase", () => {
|
||||
let updateAccountUseCase: UpdateAccountUseCase;
|
||||
|
||||
beforeEach(() => {
|
||||
updateAccountUseCase = new UpdateAccountUseCase(mockAccountService, mockTransactionManager);
|
||||
});
|
||||
|
||||
it("debería actualizar una cuenta y retornar un DTO", async () => {
|
||||
const mockUpdatedAccount = { id: "123", name: "Nuevo Nombre" };
|
||||
(mockAccountService.updateAccountById as jest.Mock).mockResolvedValue(
|
||||
Result.ok(mockUpdatedAccount)
|
||||
);
|
||||
|
||||
const result = await updateAccountUseCase.execute(id, { name: "Nuevo Nombre" });
|
||||
expect(result.isSuccess).toBe(true);
|
||||
expect(result.data.name).toBe("Nuevo Nombre");
|
||||
});
|
||||
|
||||
it("debería retornar error si la actualización falla", async () => {
|
||||
(mockAccountService.updateAccountById as jest.Mock).mockResolvedValue(
|
||||
Result.fail(new Error("Account not found"))
|
||||
);
|
||||
|
||||
const result = await updateAccountUseCase.execute(id, { name: "Nuevo Nombre" });
|
||||
expect(result.isFailure).toBe(true);
|
||||
expect(result.error.message).toBe("Account not found");
|
||||
});
|
||||
});
|
||||
@ -1,127 +0,0 @@
|
||||
import {
|
||||
EmailAddress,
|
||||
PhoneNumber,
|
||||
PostalAddress,
|
||||
TINNumber,
|
||||
UniqueID,
|
||||
} from "@/core/common/domain";
|
||||
|
||||
import { Account, IAccountProps, IAccountService } from "@/contexts/accounts/domain";
|
||||
import { ITransactionManager } from "@/core/common/infrastructure/database";
|
||||
import { logger } from "@/core/logger";
|
||||
import { Maybe, Result } from "@repo/rdx-utils";
|
||||
import { IUpdateAccountRequestDTO } from "../presentation";
|
||||
|
||||
export class UpdateAccountUseCase {
|
||||
constructor(
|
||||
private readonly accountService: IAccountService,
|
||||
private readonly transactionManager: ITransactionManager
|
||||
) {}
|
||||
|
||||
public execute(
|
||||
accountID: UniqueID,
|
||||
dto: Partial<IUpdateAccountRequestDTO>
|
||||
): Promise<Result<Account, Error>> {
|
||||
return this.transactionManager.complete(async (transaction) => {
|
||||
try {
|
||||
const validOrErrors = this.validateAccountData(dto);
|
||||
if (validOrErrors.isFailure) {
|
||||
return Result.fail(validOrErrors.error);
|
||||
}
|
||||
|
||||
const data = validOrErrors.data;
|
||||
|
||||
// Update account with dto
|
||||
return await this.accountService.updateAccountById(accountID, data, transaction);
|
||||
} catch (error: unknown) {
|
||||
logger.error(error as Error);
|
||||
return Result.fail(error as Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private validateAccountData(
|
||||
dto: Partial<IUpdateAccountRequestDTO>
|
||||
): Result<Partial<IAccountProps>, Error> {
|
||||
const errors: Error[] = [];
|
||||
const validatedData: Partial<IAccountProps> = {};
|
||||
|
||||
if (dto.is_companyr) {
|
||||
validatedData.isFreelancer = dto.is_companyr;
|
||||
}
|
||||
|
||||
if (dto.name) {
|
||||
validatedData.name = dto.name;
|
||||
}
|
||||
|
||||
if (dto.trade_name) {
|
||||
validatedData.tradeName = Maybe.some(dto.trade_name);
|
||||
}
|
||||
|
||||
if (dto.tin) {
|
||||
const tinOrError = TINNumber.create(dto.tin);
|
||||
if (tinOrError.isFailure) errors.push(tinOrError.error);
|
||||
else validatedData.tin = tinOrError.data;
|
||||
}
|
||||
|
||||
if (dto.email) {
|
||||
const emailOrError = EmailAddress.create(dto.email);
|
||||
if (emailOrError.isFailure) errors.push(emailOrError.error);
|
||||
else validatedData.email = emailOrError.data;
|
||||
}
|
||||
|
||||
if (dto.phone) {
|
||||
const phoneOrError = PhoneNumber.create(dto.phone);
|
||||
if (phoneOrError.isFailure) errors.push(phoneOrError.error);
|
||||
else validatedData.phone = phoneOrError.data;
|
||||
}
|
||||
|
||||
if (dto.fax) {
|
||||
const faxOrError = PhoneNumber.create(dto.fax);
|
||||
if (faxOrError.isFailure) errors.push(faxOrError.error);
|
||||
else validatedData.fax = Maybe.some(faxOrError.data);
|
||||
}
|
||||
|
||||
if (dto.street || dto.city || dto.state || dto.postal_code || dto.country) {
|
||||
const postalAddressOrError = PostalAddress.create({
|
||||
street: dto.street ?? "",
|
||||
city: dto.city ?? "",
|
||||
state: dto.state ?? "",
|
||||
postalCode: dto.postal_code ?? "",
|
||||
country: dto.country ?? "",
|
||||
});
|
||||
if (postalAddressOrError.isFailure) errors.push(postalAddressOrError.error);
|
||||
else validatedData.address = postalAddressOrError.data;
|
||||
}
|
||||
|
||||
if (dto.website) {
|
||||
validatedData.website = Maybe.some(dto.website);
|
||||
}
|
||||
|
||||
if (dto.legal_record) {
|
||||
validatedData.legalRecord = dto.legal_record;
|
||||
}
|
||||
|
||||
if (dto.default_tax) {
|
||||
validatedData.defaultTax = dto.default_tax;
|
||||
}
|
||||
|
||||
if (dto.language_code) {
|
||||
validatedData.langCode = dto.language_code;
|
||||
}
|
||||
|
||||
if (dto.currency_code) {
|
||||
validatedData.currencyCode = dto.currency_code;
|
||||
}
|
||||
|
||||
if (dto.logo) {
|
||||
validatedData.logo = Maybe.some(dto.logo);
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
const message = errors.map((err) => err.message).toString();
|
||||
return Result.fail(new Error(message));
|
||||
}
|
||||
return Result.ok(validatedData);
|
||||
}
|
||||
}
|
||||
@ -1,188 +0,0 @@
|
||||
import {
|
||||
AggregateRoot,
|
||||
type EmailAddress,
|
||||
type PhoneNumber,
|
||||
PostalAddress,
|
||||
type TINNumber,
|
||||
UniqueID,
|
||||
} from "@/core";
|
||||
import { Maybe, Result } from "@repo/rdx-utils";
|
||||
import { AccountStatus } from "../value-objects";
|
||||
|
||||
export interface IAccountProps {
|
||||
status: AccountStatus;
|
||||
|
||||
isFreelancer: boolean;
|
||||
name: string;
|
||||
tin: TINNumber;
|
||||
address: PostalAddress;
|
||||
email: EmailAddress;
|
||||
phone: PhoneNumber;
|
||||
legalRecord: string;
|
||||
defaultTax: number;
|
||||
|
||||
langCode: string;
|
||||
currencyCode: string;
|
||||
|
||||
tradeName: Maybe<string>;
|
||||
website: Maybe<string>;
|
||||
fax: Maybe<PhoneNumber>;
|
||||
logo: Maybe<string>;
|
||||
}
|
||||
|
||||
export interface IAccount {
|
||||
id: UniqueID;
|
||||
status: AccountStatus;
|
||||
name: string;
|
||||
tin: TINNumber;
|
||||
address: PostalAddress;
|
||||
email: EmailAddress;
|
||||
phone: PhoneNumber;
|
||||
legalRecord: string;
|
||||
defaultTax: number;
|
||||
|
||||
langCode: string;
|
||||
currencyCode: string;
|
||||
|
||||
tradeName: Maybe<string>;
|
||||
fax: Maybe<PhoneNumber>;
|
||||
website: Maybe<string>;
|
||||
logo: Maybe<string>;
|
||||
|
||||
isAccount: boolean;
|
||||
isFreelancer: boolean;
|
||||
isActive: boolean;
|
||||
|
||||
activate(): boolean;
|
||||
deactivate(): boolean;
|
||||
}
|
||||
|
||||
export class Account extends AggregateRoot<IAccountProps> implements IAccount {
|
||||
id: UniqueID;
|
||||
static create(props: IAccountProps, id?: UniqueID): Result<Account, Error> {
|
||||
const account = new Account(props, id);
|
||||
|
||||
// Reglas de negocio / validaciones
|
||||
// ...
|
||||
// ...
|
||||
|
||||
// 🔹 Disparar evento de dominio "AccountAuthenticatedEvent"
|
||||
//const { account } = props;
|
||||
//user.addDomainEvent(new AccountAuthenticatedEvent(id, account.toString()));
|
||||
|
||||
return Result.ok(account);
|
||||
}
|
||||
|
||||
static update(oldAccount: Account, data: Partial<IAccountProps>): Result<Account, Error> {
|
||||
const updatedPostalAddress = PostalAddress.update(oldAccount.address, data.address ?? {}).data;
|
||||
|
||||
return Account.create(
|
||||
{
|
||||
isFreelancer: data.isFreelancer ?? oldAccount.isFreelancer,
|
||||
|
||||
name: data.name ?? oldAccount.name,
|
||||
tin: data.tin ?? oldAccount.tin,
|
||||
|
||||
address: updatedPostalAddress,
|
||||
email: data.email ?? oldAccount.email,
|
||||
phone: data.phone ?? oldAccount.phone,
|
||||
|
||||
legalRecord: data.legalRecord ?? oldAccount.legalRecord,
|
||||
defaultTax: data.defaultTax ?? oldAccount.defaultTax,
|
||||
status: oldAccount.props.status,
|
||||
langCode: data.langCode ?? oldAccount.langCode,
|
||||
currencyCode: data.currencyCode ?? oldAccount.currencyCode,
|
||||
|
||||
tradeName: data.tradeName ?? oldAccount.tradeName,
|
||||
website: data.website ?? oldAccount.website,
|
||||
fax: data.fax ?? oldAccount.fax,
|
||||
logo: data.logo ?? oldAccount.logo,
|
||||
},
|
||||
oldAccount.id
|
||||
).getOrElse(this);
|
||||
}
|
||||
|
||||
activate() {
|
||||
if (!this.props.status.canTransitionTo("active")) {
|
||||
return false;
|
||||
}
|
||||
this.props.status = AccountStatus.createActive();
|
||||
return true;
|
||||
}
|
||||
|
||||
deactivate() {
|
||||
if (!this.props.status.canTransitionTo("inactive")) {
|
||||
return false;
|
||||
}
|
||||
this.props.status = AccountStatus.createInactive();
|
||||
return true;
|
||||
}
|
||||
|
||||
get status() {
|
||||
return this.props.status;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.props.name;
|
||||
}
|
||||
|
||||
get tradeName() {
|
||||
return this.props.tradeName;
|
||||
}
|
||||
|
||||
get tin(): TINNumber {
|
||||
return this.props.tin;
|
||||
}
|
||||
|
||||
get address(): PostalAddress {
|
||||
return this.props.address;
|
||||
}
|
||||
|
||||
get email(): EmailAddress {
|
||||
return this.props.email;
|
||||
}
|
||||
|
||||
get phone(): PhoneNumber {
|
||||
return this.props.phone;
|
||||
}
|
||||
|
||||
get fax(): Maybe<PhoneNumber> {
|
||||
return this.props.fax;
|
||||
}
|
||||
|
||||
get website() {
|
||||
return this.props.website;
|
||||
}
|
||||
|
||||
get legalRecord() {
|
||||
return this.props.legalRecord;
|
||||
}
|
||||
|
||||
get defaultTax() {
|
||||
return this.props.defaultTax;
|
||||
}
|
||||
|
||||
get langCode() {
|
||||
return this.props.langCode;
|
||||
}
|
||||
|
||||
get currencyCode() {
|
||||
return this.props.currencyCode;
|
||||
}
|
||||
|
||||
get logo() {
|
||||
return this.props.logo;
|
||||
}
|
||||
|
||||
get isAccount(): boolean {
|
||||
return !this.props.isFreelancer;
|
||||
}
|
||||
|
||||
get isFreelancer(): boolean {
|
||||
return this.props.isFreelancer;
|
||||
}
|
||||
|
||||
get isActive(): boolean {
|
||||
return this.props.status.equals(AccountStatus.createActive());
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from "./account";
|
||||
@ -1,4 +0,0 @@
|
||||
export * from "./aggregates";
|
||||
export * from "./repositories";
|
||||
export * from "./services";
|
||||
export * from "./value-objects";
|
||||
@ -1,13 +0,0 @@
|
||||
import { EmailAddress, UniqueID } from "@/core/common/domain";
|
||||
import { Collection, Result } from "@repo/rdx-utils";
|
||||
import { Account } from "../aggregates";
|
||||
|
||||
export interface IAccountRepository {
|
||||
accountExists(id: UniqueID, transaction?: any): Promise<Result<boolean, Error>>;
|
||||
findAll(transaction?: any): Promise<Result<Collection<Account>, Error>>;
|
||||
findById(id: UniqueID, transaction?: any): Promise<Result<Account, Error>>;
|
||||
findByEmail(email: EmailAddress, transaction?: any): Promise<Result<Account, Error>>;
|
||||
|
||||
create(account: Account, transaction?: any): Promise<void>;
|
||||
update(account: Account, transaction?: any): Promise<void>;
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from "./account-repository.interface";
|
||||
@ -1,153 +0,0 @@
|
||||
import { Maybe, Result } from "@repo/rdx-utils";
|
||||
|
||||
import {
|
||||
EmailAddress,
|
||||
PhoneNumber,
|
||||
PostalAddress,
|
||||
TINNumber,
|
||||
UniqueID,
|
||||
} from "@/core/common/domain";
|
||||
|
||||
import { Account, type IAccountProps } from "../aggregates";
|
||||
import type { IAccountRepository } from "../repositories";
|
||||
import { AccountStatus } from "../value-objects";
|
||||
|
||||
import { AccountService } from "./account.service";
|
||||
|
||||
const mockAccountRepository: IAccountRepository = {
|
||||
accountExists: jest.fn(),
|
||||
findAll: jest.fn(),
|
||||
findByEmail: jest.fn(),
|
||||
findById: jest.fn(),
|
||||
create: jest.fn(),
|
||||
update: jest.fn(),
|
||||
};
|
||||
|
||||
const sampleAccountPrimitives = {
|
||||
id: "c5743279-e1cf-4dd5-baae-6698c8c6183c",
|
||||
is_companyr: false,
|
||||
name: "Empresa XYZ",
|
||||
trade_name: "XYZ Trading",
|
||||
tin: "123456789",
|
||||
street: "Calle Principal 123",
|
||||
city: "Madrid",
|
||||
state: "Madrid",
|
||||
postal_code: "28001",
|
||||
country: "España",
|
||||
email: "contacto@xyz.com",
|
||||
phone: "+34 600 123 456",
|
||||
fax: "+34 600 654 321",
|
||||
website: "https://xyz.com",
|
||||
legal_record: "Registro Mercantil XYZ",
|
||||
default_tax: 21,
|
||||
status: "active",
|
||||
language_code: "es",
|
||||
currency_code: "EUR",
|
||||
logo: "https://xyz.com/logo.png",
|
||||
};
|
||||
|
||||
const accountBuilder = (accountData: any) => {
|
||||
const idOrError = UniqueID.create(sampleAccountPrimitives.id);
|
||||
const tinOrError = TINNumber.create(sampleAccountPrimitives.tin);
|
||||
const emailOrError = EmailAddress.create(sampleAccountPrimitives.email);
|
||||
const phoneOrError = PhoneNumber.create(sampleAccountPrimitives.phone);
|
||||
const faxOrError = PhoneNumber.createNullable(sampleAccountPrimitives.fax);
|
||||
const postalAddressOrError = PostalAddress.create({
|
||||
street: sampleAccountPrimitives.street,
|
||||
city: sampleAccountPrimitives.city,
|
||||
state: sampleAccountPrimitives.state,
|
||||
postalCode: sampleAccountPrimitives.postal_code,
|
||||
country: sampleAccountPrimitives.country,
|
||||
});
|
||||
|
||||
const result = Result.combine([
|
||||
idOrError,
|
||||
tinOrError,
|
||||
emailOrError,
|
||||
phoneOrError,
|
||||
faxOrError,
|
||||
postalAddressOrError,
|
||||
]);
|
||||
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
}
|
||||
|
||||
const validatedData: IAccountProps = {
|
||||
status: AccountStatus.createInactive(),
|
||||
isFreelancer: sampleAccountPrimitives.is_companyr,
|
||||
name: sampleAccountPrimitives.name,
|
||||
tradeName: sampleAccountPrimitives.trade_name
|
||||
? Maybe.some(sampleAccountPrimitives.trade_name)
|
||||
: Maybe.none(),
|
||||
tin: tinOrError.data,
|
||||
address: postalAddressOrError.data,
|
||||
email: emailOrError.data,
|
||||
phone: phoneOrError.data,
|
||||
fax: faxOrError.data,
|
||||
website: sampleAccountPrimitives.website
|
||||
? Maybe.some(sampleAccountPrimitives.website)
|
||||
: Maybe.none(),
|
||||
legalRecord: sampleAccountPrimitives.legal_record,
|
||||
defaultTax: sampleAccountPrimitives.default_tax,
|
||||
langCode: sampleAccountPrimitives.language_code,
|
||||
currencyCode: sampleAccountPrimitives.currency_code,
|
||||
logo: sampleAccountPrimitives.logo ? Maybe.some(sampleAccountPrimitives.logo) : Maybe.none(),
|
||||
};
|
||||
|
||||
return Account.create(validatedData, idOrError.data);
|
||||
};
|
||||
|
||||
describe("AccountService - Integración", () => {
|
||||
let accountService: AccountService;
|
||||
let activeSampleAccount: Account;
|
||||
let inactiveSampleAccount: Account;
|
||||
let accountId: UniqueID;
|
||||
|
||||
beforeEach(() => {
|
||||
accountService = new AccountService(mockAccountRepository);
|
||||
inactiveSampleAccount = accountBuilder(sampleAccountPrimitives).data;
|
||||
});
|
||||
|
||||
it("debería activar una cuenta existente", async () => {
|
||||
const existingAccount = inactiveSampleAccount;
|
||||
(mockAccountRepository.findById as jest.Mock).mockResolvedValue(Result.ok(existingAccount));
|
||||
(mockAccountRepository.update as jest.Mock).mockResolvedValue(undefined);
|
||||
|
||||
const result = await accountService.activateAccount(existingAccount.id);
|
||||
|
||||
expect(result.isSuccess).toBe(true);
|
||||
expect(result.data.isActive).toBeTruthy();
|
||||
expect(mockAccountRepository.update).toHaveBeenCalledWith(result.data);
|
||||
});
|
||||
|
||||
it("debería desactivar una cuenta existente", async () => {
|
||||
const existingAccount = Account.update(inactiveSampleAccount, {
|
||||
status: AccountStatus.createActive(),
|
||||
}).data;
|
||||
|
||||
(mockAccountRepository.findById as jest.Mock).mockResolvedValue(Result.ok(existingAccount));
|
||||
(mockAccountRepository.update as jest.Mock).mockResolvedValue(undefined);
|
||||
|
||||
const result = await accountService.deactivateAccount(existingAccount.id);
|
||||
expect(result.isSuccess).toBe(true);
|
||||
expect(result.data.isActive).toBeFalsy();
|
||||
expect(mockAccountRepository.update).toHaveBeenCalledWith(result.data);
|
||||
});
|
||||
|
||||
it("debería retornar error si la cuenta no existe al activar", async () => {
|
||||
(mockAccountRepository.findById as jest.Mock).mockResolvedValue(null);
|
||||
|
||||
const result = await accountService.activateAccount(UniqueID.create("", true).data);
|
||||
expect(result.isFailure).toBe(true);
|
||||
expect(result.error.message).toBe("Account not found");
|
||||
});
|
||||
|
||||
it("debería retornar error si la cuenta no existe al desactivar", async () => {
|
||||
(mockAccountRepository.findById as jest.Mock).mockResolvedValue(null);
|
||||
|
||||
const result = await accountService.deactivateAccount(UniqueID.create("", true).data);
|
||||
expect(result.isFailure).toBe(true);
|
||||
expect(result.error.message).toBe("Account not found");
|
||||
});
|
||||
});
|
||||
@ -1,20 +0,0 @@
|
||||
import { UniqueID } from "@/core/common/domain";
|
||||
import { Collection, Result } from "@repo/rdx-utils";
|
||||
import { Account, IAccountProps } from "../aggregates";
|
||||
|
||||
export interface IAccountService {
|
||||
findAccounts(transaction?: any): Promise<Result<Collection<Account>, Error>>;
|
||||
findAccountById(accountId: UniqueID, transaction?: any): Promise<Result<Account>>;
|
||||
|
||||
updateAccountById(
|
||||
accountId: UniqueID,
|
||||
data: Partial<IAccountProps>,
|
||||
transaction?: any
|
||||
): Promise<Result<Account, Error>>;
|
||||
|
||||
createAccount(
|
||||
accountId: UniqueID,
|
||||
data: IAccountProps,
|
||||
transaction?: any
|
||||
): Promise<Result<Account, Error>>;
|
||||
}
|
||||
@ -1,70 +0,0 @@
|
||||
import { UniqueID } from "@/core/common/domain";
|
||||
import { Account } from "../aggregates";
|
||||
import { IAccountRepository } from "../repositories";
|
||||
import { AccountService } from "./account.service";
|
||||
|
||||
const mockAccountRepository: IAccountRepository = {
|
||||
accountExists: jest.fn(),
|
||||
findAll: jest.fn(),
|
||||
findByEmail: jest.fn(),
|
||||
findById: jest.fn(),
|
||||
create: jest.fn(),
|
||||
update: jest.fn(),
|
||||
};
|
||||
|
||||
const sampleAccount = {
|
||||
id: "c5743279-e1cf-4dd5-baae-6698c8c6183c",
|
||||
is_companyr: false,
|
||||
name: "Empresa XYZ",
|
||||
trade_name: "XYZ Trading",
|
||||
tin: "123456789",
|
||||
street: "Calle Principal 123",
|
||||
city: "Madrid",
|
||||
state: "Madrid",
|
||||
postal_code: "28001",
|
||||
country: "España",
|
||||
email: "contacto@xyz.com",
|
||||
phone: "+34 600 123 456",
|
||||
fax: "+34 600 654 321",
|
||||
website: "https://xyz.com",
|
||||
legal_record: "Registro Mercantil XYZ",
|
||||
default_tax: 21,
|
||||
status: "active",
|
||||
language_code: "es",
|
||||
currency_code: "EUR",
|
||||
logo: "https://xyz.com/logo.png",
|
||||
};
|
||||
|
||||
const accountId = UniqueID.create(sampleAccount.id).data;
|
||||
|
||||
describe("AccountService", () => {
|
||||
let accountService: AccountService;
|
||||
|
||||
beforeEach(() => {
|
||||
accountService = new AccountService(mockAccountRepository);
|
||||
});
|
||||
|
||||
it("debería actualizar una cuenta existente", async () => {
|
||||
const existingAccount = new Account(
|
||||
{
|
||||
/* datos simulados */
|
||||
},
|
||||
"123"
|
||||
);
|
||||
(mockAccountRepository.findById as jest.Mock).mockResolvedValue(existingAccount);
|
||||
(mockAccountRepository.create as jest.Mock).mockResolvedValue(undefined);
|
||||
|
||||
const result = await accountService.updateAccountById(accountId, { name: "Nuevo Nombre" });
|
||||
expect(result.isSuccess).toBe(true);
|
||||
expect(result.data.name).toBe("Nuevo Nombre");
|
||||
expect(mockAccountRepository.save).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("debería retornar error si la cuenta no existe", async () => {
|
||||
(mockAccountRepository.findById as jest.Mock).mockResolvedValue(null);
|
||||
|
||||
const result = await accountService.updateAccountById(accountId, { name: "Nuevo Nombre" });
|
||||
expect(result.isFailure).toBe(true);
|
||||
expect(result.error.message).toBe("Account not found");
|
||||
});
|
||||
});
|
||||
@ -1,105 +0,0 @@
|
||||
import { UniqueID } from "@/core/common/domain";
|
||||
import { Collection, Result } from "@repo/rdx-utils";
|
||||
import { Transaction } from "sequelize";
|
||||
import { Account, IAccountProps } from "../aggregates";
|
||||
import { IAccountRepository } from "../repositories";
|
||||
import { IAccountService } from "./account-service.interface";
|
||||
|
||||
export class AccountService implements IAccountService {
|
||||
constructor(private readonly repo: IAccountRepository) {}
|
||||
|
||||
async findAccounts(transaction?: Transaction): Promise<Result<Collection<Account>, Error>> {
|
||||
const accountsOrError = await this.repo.findAll(transaction);
|
||||
if (accountsOrError.isFailure) {
|
||||
return Result.fail(accountsOrError.error);
|
||||
}
|
||||
|
||||
// Solo devolver usuarios activos
|
||||
//const allAccounts = accountsOrError.data.filter((account) => account.isActive);
|
||||
//return Result.ok(new Collection(allAccounts));
|
||||
|
||||
return accountsOrError;
|
||||
}
|
||||
|
||||
async findAccountById(accountId: UniqueID, transaction?: Transaction): Promise<Result<Account>> {
|
||||
return await this.repo.findById(accountId, transaction);
|
||||
}
|
||||
|
||||
async updateAccountById(
|
||||
accountId: UniqueID,
|
||||
data: Partial<IAccountProps>,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<Account, Error>> {
|
||||
// Verificar si la cuenta existe
|
||||
const accountOrError = await this.repo.findById(accountId, transaction);
|
||||
if (accountOrError.isFailure) {
|
||||
return Result.fail(new Error("Account not found"));
|
||||
}
|
||||
|
||||
const updatedAccountOrError = Account.update(accountOrError.data, data);
|
||||
if (updatedAccountOrError.isFailure) {
|
||||
return Result.fail(
|
||||
new Error(`Error updating account: ${updatedAccountOrError.error.message}`)
|
||||
);
|
||||
}
|
||||
|
||||
const updateAccount = updatedAccountOrError.data;
|
||||
|
||||
await this.repo.update(updateAccount, transaction);
|
||||
return Result.ok(updateAccount);
|
||||
}
|
||||
|
||||
async createAccount(
|
||||
accountId: UniqueID,
|
||||
data: IAccountProps,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<Account, Error>> {
|
||||
// Verificar si la cuenta existe
|
||||
const accountOrError = await this.repo.findById(accountId, transaction);
|
||||
if (accountOrError.isSuccess) {
|
||||
return Result.fail(new Error("Account exists"));
|
||||
}
|
||||
|
||||
const newAccountOrError = Account.create(data, accountId);
|
||||
if (newAccountOrError.isFailure) {
|
||||
return Result.fail(new Error(`Error creating account: ${newAccountOrError.error.message}`));
|
||||
}
|
||||
|
||||
const newAccount = newAccountOrError.data;
|
||||
|
||||
await this.repo.create(newAccount, transaction);
|
||||
return Result.ok(newAccount);
|
||||
}
|
||||
|
||||
async activateAccount(id: UniqueID, transaction?: Transaction): Promise<Result<Account, Error>> {
|
||||
const accountOrError = await this.repo.findById(id, transaction);
|
||||
|
||||
if (accountOrError.isFailure) {
|
||||
return Result.fail(new Error("Account not found"));
|
||||
}
|
||||
|
||||
const account = accountOrError.data;
|
||||
if (account.activate()) {
|
||||
await this.repo.update(account, transaction);
|
||||
return Result.ok();
|
||||
}
|
||||
return Result.fail(new Error("Error activating account"));
|
||||
}
|
||||
|
||||
async deactivateAccount(
|
||||
id: UniqueID,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<Account, Error>> {
|
||||
const accountOrError = await this.repo.findById(id, transaction);
|
||||
if (accountOrError.isFailure) {
|
||||
return Result.fail(new Error("Account not found"));
|
||||
}
|
||||
|
||||
const account = accountOrError.data;
|
||||
if (account.deactivate()) {
|
||||
await this.repo.update(account, transaction);
|
||||
return Result.ok();
|
||||
}
|
||||
return Result.fail(new Error("Error deactivating account"));
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
export * from "./account-service.interface";
|
||||
export * from "./account.service";
|
||||
@ -1,72 +0,0 @@
|
||||
import { ACCOUNT_STATUS, AccountStatus } from "./account-status";
|
||||
|
||||
describe("AccountStatus Value Object", () => {
|
||||
describe("Creación de estados válidos", () => {
|
||||
it("debería crear un estado inactivo correctamente", () => {
|
||||
const status = AccountStatus.createInactive();
|
||||
expect(status.getValue()).toBe(ACCOUNT_STATUS.INACTIVE);
|
||||
});
|
||||
|
||||
it("debería crear un estado activo correctamente", () => {
|
||||
const status = AccountStatus.createActive();
|
||||
expect(status.getValue()).toBe(ACCOUNT_STATUS.ACTIVE);
|
||||
});
|
||||
|
||||
it("debería crear un estado válido usando el método create", () => {
|
||||
const result = AccountStatus.create(ACCOUNT_STATUS.ACTIVE);
|
||||
expect(result.isSuccess).toBe(true);
|
||||
expect(result.data.getValue()).toBe(ACCOUNT_STATUS.ACTIVE);
|
||||
});
|
||||
|
||||
it("debería fallar al crear un estado inválido", () => {
|
||||
const result = AccountStatus.create("invalid-status");
|
||||
expect(result.isFailure).toBe(true);
|
||||
expect(result.error.message).toBe("Estado de la cuenta no válido: invalid-status");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Verificación de transiciones", () => {
|
||||
it("debería permitir la transición de 'inactive' a 'active'", () => {
|
||||
const status = AccountStatus.createInactive();
|
||||
expect(status.canTransitionTo(ACCOUNT_STATUS.ACTIVE)).toBe(true);
|
||||
});
|
||||
|
||||
it("debería permitir la transición de 'active' a 'inactive'", () => {
|
||||
const status = AccountStatus.createActive();
|
||||
expect(status.canTransitionTo(ACCOUNT_STATUS.INACTIVE)).toBe(true);
|
||||
});
|
||||
|
||||
it("debería impedir transiciones no permitidas", () => {
|
||||
const status = AccountStatus.createInactive();
|
||||
expect(status.canTransitionTo("invalid")).toBe(false);
|
||||
});
|
||||
|
||||
it("debería realizar una transición válida correctamente", () => {
|
||||
const inactiveStatus = AccountStatus.createInactive();
|
||||
const transitionResult = inactiveStatus.transitionTo(ACCOUNT_STATUS.ACTIVE);
|
||||
|
||||
expect(transitionResult.isSuccess).toBe(true);
|
||||
expect(transitionResult.data.getValue()).toBe(ACCOUNT_STATUS.ACTIVE);
|
||||
});
|
||||
|
||||
it("debería fallar en una transición inválida", () => {
|
||||
const status = AccountStatus.createInactive();
|
||||
const transitionResult = status.transitionTo("invalid");
|
||||
|
||||
expect(transitionResult.isFailure).toBe(true);
|
||||
expect(transitionResult.error.message).toBe("Transición no permitida de inactive a invalid");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Métodos auxiliares", () => {
|
||||
it("debería devolver correctamente el valor con getValue()", () => {
|
||||
const status = AccountStatus.createInactive();
|
||||
expect(status.getValue()).toBe(ACCOUNT_STATUS.INACTIVE);
|
||||
});
|
||||
|
||||
it("debería devolver correctamente el valor con toString()", () => {
|
||||
const status = AccountStatus.createActive();
|
||||
expect(status.toString()).toBe(ACCOUNT_STATUS.ACTIVE);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,59 +0,0 @@
|
||||
import { ValueObject } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
interface IAccountStatusProps {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export enum ACCOUNT_STATUS {
|
||||
INACTIVE = "inactive",
|
||||
ACTIVE = "active",
|
||||
}
|
||||
|
||||
export class AccountStatus extends ValueObject<IAccountStatusProps> {
|
||||
private static readonly ALLOWED_STATUSES = ["inactive", "active"];
|
||||
|
||||
private static readonly TRANSITIONS: Record<string, string[]> = {
|
||||
inactive: [ACCOUNT_STATUS.ACTIVE],
|
||||
active: [ACCOUNT_STATUS.INACTIVE],
|
||||
};
|
||||
|
||||
static create(value: string): Result<AccountStatus, Error> {
|
||||
if (!this.ALLOWED_STATUSES.includes(value)) {
|
||||
return Result.fail(new Error(`Estado de la cuenta no válido: ${value}`));
|
||||
}
|
||||
|
||||
return Result.ok(
|
||||
value === "active" ? AccountStatus.createActive() : AccountStatus.createInactive()
|
||||
);
|
||||
}
|
||||
|
||||
public static createInactive(): AccountStatus {
|
||||
return new AccountStatus({ value: ACCOUNT_STATUS.INACTIVE });
|
||||
}
|
||||
|
||||
public static createActive(): AccountStatus {
|
||||
return new AccountStatus({ value: ACCOUNT_STATUS.ACTIVE });
|
||||
}
|
||||
|
||||
getValue(): string {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
canTransitionTo(nextStatus: string): boolean {
|
||||
return AccountStatus.TRANSITIONS[this.props.value].includes(nextStatus);
|
||||
}
|
||||
|
||||
transitionTo(nextStatus: string): Result<AccountStatus, Error> {
|
||||
if (!this.canTransitionTo(nextStatus)) {
|
||||
return Result.fail(
|
||||
new Error(`Transición no permitida de ${this.props.value} a ${nextStatus}`)
|
||||
);
|
||||
}
|
||||
return AccountStatus.create(nextStatus);
|
||||
}
|
||||
|
||||
toPrimitive(): string {
|
||||
return this.getValue();
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from "./account-status";
|
||||
@ -1,2 +0,0 @@
|
||||
export * from "./mappers";
|
||||
export * from "./sequelize";
|
||||
@ -1,107 +0,0 @@
|
||||
import { Maybe, Result } from "@repo/rdx-utils";
|
||||
|
||||
import { Account, AccountStatus } from "@/contexts/accounts/domain/";
|
||||
import {
|
||||
EmailAddress,
|
||||
PhoneNumber,
|
||||
PostalAddress,
|
||||
TINNumber,
|
||||
UniqueID,
|
||||
} from "@/core/common/domain";
|
||||
import {
|
||||
type ISequelizeMapper,
|
||||
type MapperParamsType,
|
||||
SequelizeMapper,
|
||||
} from "@/core/common/infrastructure/sequelize/sequelize-mapper";
|
||||
|
||||
import type { AccountCreationAttributes, AccountModel } from "../sequelize/account.model";
|
||||
|
||||
export interface IAccountMapper
|
||||
extends ISequelizeMapper<AccountModel, AccountCreationAttributes, Account> {}
|
||||
|
||||
export class AccountMapper
|
||||
extends SequelizeMapper<AccountModel, AccountCreationAttributes, Account>
|
||||
implements IAccountMapper
|
||||
{
|
||||
public mapToDomain(source: AccountModel, params?: MapperParamsType): Result<Account, Error> {
|
||||
const idOrError = UniqueID.create(source.id);
|
||||
const statusOrError = AccountStatus.create(source.status);
|
||||
const tinOrError = TINNumber.create(source.tin);
|
||||
const emailOrError = EmailAddress.create(source.email);
|
||||
const phoneOrError = PhoneNumber.create(source.phone);
|
||||
const faxOrError = PhoneNumber.createNullable(source.fax);
|
||||
const postalAddressOrError = PostalAddress.create({
|
||||
street: source.street,
|
||||
city: source.city,
|
||||
state: source.state,
|
||||
postalCode: source.postal_code,
|
||||
country: source.country,
|
||||
});
|
||||
|
||||
const result = Result.combine([
|
||||
idOrError,
|
||||
statusOrError,
|
||||
tinOrError,
|
||||
emailOrError,
|
||||
phoneOrError,
|
||||
faxOrError,
|
||||
postalAddressOrError,
|
||||
]);
|
||||
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
}
|
||||
|
||||
return Account.create(
|
||||
{
|
||||
status: statusOrError.data,
|
||||
isFreelancer: source.is_companyr,
|
||||
name: source.name,
|
||||
tradeName: source.trade_name ? Maybe.some(source.trade_name) : Maybe.none(),
|
||||
tin: tinOrError.data,
|
||||
address: postalAddressOrError.data,
|
||||
email: emailOrError.data,
|
||||
phone: phoneOrError.data,
|
||||
fax: faxOrError.data,
|
||||
website: source.website ? Maybe.some(source.website) : Maybe.none(),
|
||||
legalRecord: source.legal_record,
|
||||
defaultTax: source.default_tax,
|
||||
langCode: source.language_code,
|
||||
currencyCode: source.currency_code,
|
||||
logo: source.logo ? Maybe.some(source.logo) : Maybe.none(),
|
||||
},
|
||||
idOrError.data
|
||||
);
|
||||
}
|
||||
|
||||
public mapToPersistence(source: Account, params?: MapperParamsType): AccountCreationAttributes {
|
||||
return {
|
||||
id: source.id.toPrimitive(),
|
||||
is_companyr: source.isFreelancer,
|
||||
name: source.name,
|
||||
trade_name: source.tradeName.getOrUndefined(),
|
||||
tin: source.tin.toPrimitive(),
|
||||
|
||||
street: source.address.street,
|
||||
city: source.address.city,
|
||||
state: source.address.state,
|
||||
postal_code: source.address.postalCode,
|
||||
country: source.address.country,
|
||||
|
||||
email: source.email.toPrimitive(),
|
||||
phone: source.phone.toPrimitive(),
|
||||
fax: source.fax.isSome() ? source.fax.getOrUndefined()?.toPrimitive() : undefined,
|
||||
website: source.website.getOrUndefined(),
|
||||
|
||||
legal_record: source.legalRecord,
|
||||
default_tax: source.defaultTax,
|
||||
status: source.isActive ? "active" : "inactive",
|
||||
language_code: source.langCode,
|
||||
currency_code: source.currencyCode,
|
||||
logo: source.logo.getOrUndefined(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const accountMapper: AccountMapper = new AccountMapper();
|
||||
export { accountMapper };
|
||||
@ -1 +0,0 @@
|
||||
export * from "./account.mapper";
|
||||
@ -1,171 +0,0 @@
|
||||
import {
|
||||
CreationOptional,
|
||||
DataTypes,
|
||||
InferAttributes,
|
||||
InferCreationAttributes,
|
||||
Model,
|
||||
Sequelize,
|
||||
} from "sequelize";
|
||||
|
||||
export type AccountCreationAttributes = InferCreationAttributes<AccountModel, {}> & {};
|
||||
|
||||
export class AccountModel extends Model<InferAttributes<AccountModel>, AccountCreationAttributes> {
|
||||
// To avoid table creation
|
||||
/*static async sync(): Promise<any> {
|
||||
return Promise.resolve();
|
||||
}*/
|
||||
|
||||
declare id: string;
|
||||
|
||||
declare is_companyr: boolean;
|
||||
declare name: string;
|
||||
declare trade_name: CreationOptional<string>;
|
||||
declare tin: string;
|
||||
|
||||
declare street: string;
|
||||
declare city: string;
|
||||
declare state: string;
|
||||
declare postal_code: string;
|
||||
declare country: string;
|
||||
|
||||
declare email: string;
|
||||
declare phone: string;
|
||||
declare fax: CreationOptional<string>;
|
||||
declare website: CreationOptional<string>;
|
||||
|
||||
declare legal_record: string;
|
||||
|
||||
declare default_tax: number;
|
||||
declare status: string;
|
||||
declare language_code: string;
|
||||
declare currency_code: string;
|
||||
declare logo: CreationOptional<string>;
|
||||
}
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
AccountModel.init(
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
primaryKey: true,
|
||||
},
|
||||
is_companyr: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
trade_name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
tin: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
street: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
city: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
state: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
postal_code: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
country: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
email: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
isEmail: true,
|
||||
},
|
||||
},
|
||||
phone: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
fax: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
website: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
validate: {
|
||||
isUrl: true,
|
||||
},
|
||||
},
|
||||
legal_record: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
default_tax: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
defaultValue: 2100,
|
||||
},
|
||||
|
||||
logo: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
language_code: {
|
||||
type: DataTypes.STRING(2),
|
||||
allowNull: false,
|
||||
defaultValue: "es",
|
||||
},
|
||||
|
||||
currency_code: {
|
||||
type: new DataTypes.STRING(3),
|
||||
allowNull: false,
|
||||
defaultValue: "EUR",
|
||||
},
|
||||
|
||||
status: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
defaultValue: "active",
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
tableName: "accounts",
|
||||
|
||||
paranoid: true, // softs deletes
|
||||
timestamps: true,
|
||||
|
||||
createdAt: "created_at",
|
||||
updatedAt: "updated_at",
|
||||
deletedAt: "deleted_at",
|
||||
|
||||
indexes: [{ name: "email_idx", fields: ["email"], unique: true }],
|
||||
|
||||
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
|
||||
|
||||
defaultScope: {},
|
||||
|
||||
scopes: {},
|
||||
}
|
||||
);
|
||||
return AccountModel;
|
||||
};
|
||||
@ -1,102 +0,0 @@
|
||||
import { Account } from "@/contexts/accounts/domain";
|
||||
import { IAccountRepository } from "@/contexts/accounts/domain/repositories/account-repository.interface";
|
||||
import { EmailAddress, UniqueID } from "@/core/common/domain";
|
||||
import { SequelizeRepository } from "@/core/common/infrastructure";
|
||||
import { Collection, Result } from "@repo/rdx-utils";
|
||||
import { Transaction } from "sequelize";
|
||||
import { IAccountMapper, accountMapper } from "../mappers/account.mapper";
|
||||
import { AccountModel } from "./account.model";
|
||||
|
||||
class AccountRepository extends SequelizeRepository<Account> implements IAccountRepository {
|
||||
private readonly _mapper!: IAccountMapper;
|
||||
|
||||
/**
|
||||
* 🔹 Función personalizada para mapear errores de unicidad en autenticación
|
||||
*/
|
||||
private _customErrorMapper(error: Error): string | null {
|
||||
if (error.name === "SequelizeUniqueConstraintError") {
|
||||
return "Account with this email already exists";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
constructor(mapper: IAccountMapper) {
|
||||
super();
|
||||
this._mapper = mapper;
|
||||
}
|
||||
|
||||
async accountExists(id: UniqueID, transaction?: Transaction): Promise<Result<boolean, Error>> {
|
||||
try {
|
||||
const _account = await this._getById(AccountModel, id, {}, transaction);
|
||||
|
||||
return Result.ok(Boolean(id.equals(_account.id)));
|
||||
} catch (error: any) {
|
||||
return this._handleDatabaseError(error, this._customErrorMapper);
|
||||
}
|
||||
}
|
||||
|
||||
async findAll(transaction?: Transaction): Promise<Result<Collection<Account>, Error>> {
|
||||
try {
|
||||
const rawAccounts: any = await this._findAll(AccountModel, {}, transaction);
|
||||
|
||||
if (!rawAccounts === true) {
|
||||
return Result.fail(new Error("Account with email not exists"));
|
||||
}
|
||||
|
||||
return this._mapper.mapArrayToDomain(rawAccounts);
|
||||
} catch (error: any) {
|
||||
return this._handleDatabaseError(error, this._customErrorMapper);
|
||||
}
|
||||
}
|
||||
|
||||
async findById(id: UniqueID, transaction?: Transaction): Promise<Result<Account, Error>> {
|
||||
try {
|
||||
const rawAccount: any = await this._getById(AccountModel, id, {}, transaction);
|
||||
|
||||
if (!rawAccount === true) {
|
||||
return Result.fail(new Error(`Account with id ${id.toString()} not exists`));
|
||||
}
|
||||
|
||||
return this._mapper.mapToDomain(rawAccount);
|
||||
} catch (error: any) {
|
||||
return this._handleDatabaseError(error, this._customErrorMapper);
|
||||
}
|
||||
}
|
||||
|
||||
async findByEmail(
|
||||
email: EmailAddress,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<Account, Error>> {
|
||||
try {
|
||||
const rawAccount: any = await this._getBy(
|
||||
AccountModel,
|
||||
"email",
|
||||
email.toString(),
|
||||
{},
|
||||
transaction
|
||||
);
|
||||
|
||||
if (!rawAccount === true) {
|
||||
return Result.fail(new Error(`Account with email ${email.toString()} not exists`));
|
||||
}
|
||||
|
||||
return this._mapper.mapToDomain(rawAccount);
|
||||
} catch (error: any) {
|
||||
return this._handleDatabaseError(error, this._customErrorMapper);
|
||||
}
|
||||
}
|
||||
|
||||
async create(account: Account, transaction?: Transaction): Promise<void> {
|
||||
const accountData = this._mapper.mapToPersistence(account);
|
||||
await this._save(AccountModel, account.id, accountData, {}, transaction);
|
||||
}
|
||||
|
||||
async update(account: Account, transaction?: Transaction): Promise<void> {
|
||||
const accountData = this._mapper.mapToPersistence(account);
|
||||
await this._save(AccountModel, account.id, accountData, {}, transaction);
|
||||
}
|
||||
}
|
||||
|
||||
const accountRepository = new AccountRepository(accountMapper);
|
||||
export { accountRepository };
|
||||
@ -1,10 +0,0 @@
|
||||
import { IAccountRepository } from "@/contexts/accounts/domain/repositories/account-repository.interface";
|
||||
import { accountRepository } from "./account.repository";
|
||||
|
||||
export * from "./account.model";
|
||||
|
||||
export * from "./account.repository";
|
||||
|
||||
export const createAccountRepository = (): IAccountRepository => {
|
||||
return accountRepository;
|
||||
};
|
||||
@ -1,45 +0,0 @@
|
||||
import { UniqueID } from "@/core/common/domain";
|
||||
import { ExpressController } from "@/core/common/presentation";
|
||||
import { CreateAccountUseCase } from "../../../application";
|
||||
import { ICreateAccountRequestDTO } from "../../dto";
|
||||
import { ICreateAccountPresenter } from "./create-account.presenter";
|
||||
|
||||
export class CreateAccountController extends ExpressController {
|
||||
public constructor(
|
||||
private readonly createAccount: CreateAccountUseCase,
|
||||
private readonly presenter: ICreateAccountPresenter
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async executeImpl() {
|
||||
const createDTO: ICreateAccountRequestDTO = this.req.body;
|
||||
|
||||
// Validar ID
|
||||
const accountIdOrError = UniqueID.create(createDTO.id);
|
||||
if (accountIdOrError.isFailure) return this.invalidInputError("Account ID not valid");
|
||||
|
||||
const accountOrError = await this.createAccount.execute(accountIdOrError.data, createDTO);
|
||||
|
||||
if (accountOrError.isFailure) {
|
||||
return this.handleError(accountOrError.error);
|
||||
}
|
||||
|
||||
return this.ok(this.presenter.toDTO(accountOrError.data));
|
||||
}
|
||||
|
||||
private handleError(error: Error) {
|
||||
const message = error.message;
|
||||
|
||||
if (
|
||||
message.includes("Database connection lost") ||
|
||||
message.includes("Database request timed out")
|
||||
) {
|
||||
return this.unavailableError(
|
||||
"Database service is currently unavailable. Please try again later."
|
||||
);
|
||||
}
|
||||
|
||||
return this.conflictError(message);
|
||||
}
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
import { Account } from "@/contexts/accounts/domain";
|
||||
import { ensureBoolean, ensureNumber, ensureString } from "@repo/rdx-utils";
|
||||
import { ICreateAccountResponseDTO } from "../../dto";
|
||||
|
||||
export interface ICreateAccountPresenter {
|
||||
toDTO: (account: Account) => ICreateAccountResponseDTO;
|
||||
}
|
||||
|
||||
export const createAccountPresenter: ICreateAccountPresenter = {
|
||||
toDTO: (account: Account): ICreateAccountResponseDTO => ({
|
||||
id: ensureString(account.id.toString()),
|
||||
|
||||
is_companyr: ensureBoolean(account.isFreelancer),
|
||||
name: ensureString(account.name),
|
||||
trade_name: ensureString(account.tradeName.getOrUndefined()),
|
||||
tin: ensureString(account.tin.toString()),
|
||||
|
||||
street: ensureString(account.address.street),
|
||||
city: ensureString(account.address.city),
|
||||
state: ensureString(account.address.state),
|
||||
postal_code: ensureString(account.address.postalCode),
|
||||
country: ensureString(account.address.country),
|
||||
|
||||
email: ensureString(account.email.toString()),
|
||||
phone: ensureString(account.phone.toString()),
|
||||
fax: ensureString(account.fax.getOrUndefined()?.toString()),
|
||||
website: ensureString(account.website.getOrUndefined()),
|
||||
|
||||
legal_record: ensureString(account.legalRecord),
|
||||
|
||||
default_tax: ensureNumber(account.defaultTax),
|
||||
status: ensureString(account.isActive ? "active" : "inactive"),
|
||||
language_code: ensureString(account.langCode),
|
||||
currency_code: ensureString(account.currencyCode),
|
||||
logo: ensureString(account.logo.getOrUndefined()),
|
||||
}),
|
||||
};
|
||||
@ -1,16 +0,0 @@
|
||||
import { CreateAccountUseCase } from "@/contexts/accounts/application/create-account.use-case";
|
||||
import { AccountService } from "@/contexts/accounts/domain";
|
||||
import { accountRepository } from "@/contexts/accounts/infraestructure";
|
||||
import { SequelizeTransactionManager } from "@/core/common/infrastructure";
|
||||
import { CreateAccountController } from "./create-account.controller";
|
||||
import { createAccountPresenter } from "./create-account.presenter";
|
||||
|
||||
export const buildCreateAccountController = () => {
|
||||
const transactionManager = new SequelizeTransactionManager();
|
||||
const accountService = new AccountService(accountRepository);
|
||||
|
||||
const useCase = new CreateAccountUseCase(accountService, transactionManager);
|
||||
const presenter = createAccountPresenter;
|
||||
|
||||
return new CreateAccountController(useCase, presenter);
|
||||
};
|
||||
@ -1,44 +0,0 @@
|
||||
import { GetAccountUseCase } from "@/contexts/accounts/application";
|
||||
import { UniqueID } from "@/core/common/domain";
|
||||
import { ExpressController } from "@/core/common/presentation";
|
||||
import { IGetAccountPresenter } from "./get-account.presenter";
|
||||
|
||||
export class GetAccountController extends ExpressController {
|
||||
public constructor(
|
||||
private readonly getAccount: GetAccountUseCase,
|
||||
private readonly presenter: IGetAccountPresenter
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async executeImpl() {
|
||||
const { accountId } = this.req.params;
|
||||
|
||||
// Validar ID
|
||||
const accountIdOrError = UniqueID.create(accountId);
|
||||
if (accountIdOrError.isFailure) return this.invalidInputError("Account ID not valid");
|
||||
|
||||
const accountOrError = await this.getAccount.execute(accountIdOrError.data);
|
||||
|
||||
if (accountOrError.isFailure) {
|
||||
return this.handleError(accountOrError.error);
|
||||
}
|
||||
|
||||
return this.ok(this.presenter.toDTO(accountOrError.data));
|
||||
}
|
||||
|
||||
private handleError(error: Error) {
|
||||
const message = error.message;
|
||||
|
||||
if (
|
||||
message.includes("Database connection lost") ||
|
||||
message.includes("Database request timed out")
|
||||
) {
|
||||
return this.unavailableError(
|
||||
"Database service is currently unavailable. Please try again later."
|
||||
);
|
||||
}
|
||||
|
||||
return this.conflictError(message);
|
||||
}
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
import { Account } from "@/contexts/accounts/domain";
|
||||
import { ensureBoolean, ensureNumber, ensureString } from "@repo/rdx-utils";
|
||||
import { IGetAccountResponseDTO } from "../../dto";
|
||||
|
||||
export interface IGetAccountPresenter {
|
||||
toDTO: (account: Account) => IGetAccountResponseDTO;
|
||||
}
|
||||
|
||||
export const getAccountPresenter: IGetAccountPresenter = {
|
||||
toDTO: (account: Account): IGetAccountResponseDTO => ({
|
||||
id: ensureString(account.id.toPrimitive()),
|
||||
|
||||
is_companyr: ensureBoolean(account.isFreelancer),
|
||||
name: ensureString(account.name),
|
||||
trade_name: ensureString(account.tradeName.getOrUndefined()),
|
||||
tin: ensureString(account.tin.toPrimitive()),
|
||||
|
||||
street: ensureString(account.address.street),
|
||||
city: ensureString(account.address.city),
|
||||
state: ensureString(account.address.state),
|
||||
postal_code: ensureString(account.address.postalCode),
|
||||
country: ensureString(account.address.country),
|
||||
|
||||
email: ensureString(account.email.toPrimitive()),
|
||||
phone: ensureString(account.phone.toPrimitive()),
|
||||
fax: ensureString(account.fax.getOrUndefined()?.toPrimitive()),
|
||||
website: ensureString(account.website.getOrUndefined()),
|
||||
|
||||
legal_record: ensureString(account.legalRecord),
|
||||
|
||||
default_tax: ensureNumber(account.defaultTax),
|
||||
status: ensureString(account.isActive ? "active" : "inactive"),
|
||||
language_code: ensureString(account.langCode),
|
||||
currency_code: ensureString(account.currencyCode),
|
||||
logo: ensureString(account.logo.getOrUndefined()),
|
||||
}),
|
||||
};
|
||||
@ -1,16 +0,0 @@
|
||||
import { GetAccountUseCase } from "@/contexts/accounts/application";
|
||||
import { AccountService } from "@/contexts/accounts/domain";
|
||||
import { accountRepository } from "@/contexts/accounts/infraestructure";
|
||||
import { SequelizeTransactionManager } from "@/core/common/infrastructure";
|
||||
import { GetAccountController } from "./get-account.controller";
|
||||
import { getAccountPresenter } from "./get-account.presenter";
|
||||
|
||||
export const buildGetAccountController = () => {
|
||||
const transactionManager = new SequelizeTransactionManager();
|
||||
const accountService = new AccountService(accountRepository);
|
||||
|
||||
const useCase = new GetAccountUseCase(accountService, transactionManager);
|
||||
const presenter = getAccountPresenter;
|
||||
|
||||
return new GetAccountController(useCase, presenter);
|
||||
};
|
||||
@ -1,4 +0,0 @@
|
||||
export * from "./create-account";
|
||||
export * from "./get-account";
|
||||
export * from "./list-accounts";
|
||||
export * from "./update-account";
|
||||
@ -1,16 +0,0 @@
|
||||
import { ListAccountsUseCase } from "@/contexts/accounts/application";
|
||||
import { AccountService } from "@/contexts/accounts/domain";
|
||||
import { accountRepository } from "@/contexts/accounts/infraestructure";
|
||||
import { SequelizeTransactionManager } from "@/core/common/infrastructure";
|
||||
import { ListAccountsController } from "./list-accounts.controller";
|
||||
import { listAccountsPresenter } from "./list-accounts.presenter";
|
||||
|
||||
export const buildListAccountsController = () => {
|
||||
const transactionManager = new SequelizeTransactionManager();
|
||||
const accountService = new AccountService(accountRepository);
|
||||
|
||||
const useCase = new ListAccountsUseCase(accountService, transactionManager);
|
||||
const presenter = listAccountsPresenter;
|
||||
|
||||
return new ListAccountsController(useCase, presenter);
|
||||
};
|
||||
@ -1,37 +0,0 @@
|
||||
import { ListAccountsUseCase } from "@/contexts/accounts/application";
|
||||
import { ExpressController } from "@/core/common/presentation";
|
||||
import { IListAccountsPresenter } from "./list-accounts.presenter";
|
||||
|
||||
export class ListAccountsController extends ExpressController {
|
||||
public constructor(
|
||||
private readonly listAccounts: ListAccountsUseCase,
|
||||
private readonly presenter: IListAccountsPresenter
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async executeImpl() {
|
||||
const accountsOrError = await this.listAccounts.execute();
|
||||
|
||||
if (accountsOrError.isFailure) {
|
||||
return this.handleError(accountsOrError.error);
|
||||
}
|
||||
|
||||
return this.ok(this.presenter.toDTO(accountsOrError.data));
|
||||
}
|
||||
|
||||
private handleError(error: Error) {
|
||||
const message = error.message;
|
||||
|
||||
if (
|
||||
message.includes("Database connection lost") ||
|
||||
message.includes("Database request timed out")
|
||||
) {
|
||||
return this.unavailableError(
|
||||
"Database service is currently unavailable. Please try again later."
|
||||
);
|
||||
}
|
||||
|
||||
return this.conflictError(message);
|
||||
}
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
import { Account } from "@/contexts/accounts/domain";
|
||||
import { Collection, ensureBoolean, ensureNumber, ensureString } from "@repo/rdx-utils";
|
||||
import { IListAccountsResponseDTO } from "../../dto";
|
||||
|
||||
export interface IListAccountsPresenter {
|
||||
toDTO: (accounts: Collection<Account>) => IListAccountsResponseDTO[];
|
||||
}
|
||||
|
||||
export const listAccountsPresenter: IListAccountsPresenter = {
|
||||
toDTO: (accounts: Collection<Account>): IListAccountsResponseDTO[] =>
|
||||
accounts.map((account) => ({
|
||||
id: ensureString(account.id.toString()),
|
||||
|
||||
is_companyr: ensureBoolean(account.isFreelancer),
|
||||
name: ensureString(account.name),
|
||||
trade_name: ensureString(account.tradeName.getOrUndefined()),
|
||||
tin: ensureString(account.tin.toString()),
|
||||
|
||||
street: ensureString(account.address.street),
|
||||
city: ensureString(account.address.city),
|
||||
state: ensureString(account.address.state),
|
||||
postal_code: ensureString(account.address.postalCode),
|
||||
country: ensureString(account.address.country),
|
||||
|
||||
email: ensureString(account.email.toString()),
|
||||
phone: ensureString(account.phone.toString()),
|
||||
fax: ensureString(account.fax.getOrUndefined()?.toString()),
|
||||
website: ensureString(account.website.getOrUndefined()),
|
||||
|
||||
legal_record: ensureString(account.legalRecord),
|
||||
|
||||
default_tax: ensureNumber(account.defaultTax),
|
||||
status: ensureString(account.isActive ? "active" : "inactive"),
|
||||
language_code: ensureString(account.langCode),
|
||||
currency_code: ensureString(account.currencyCode),
|
||||
logo: ensureString(account.logo.getOrUndefined()),
|
||||
})),
|
||||
};
|
||||
@ -1,16 +0,0 @@
|
||||
import { UpdateAccountUseCase } from "@/contexts/accounts/application";
|
||||
import { AccountService } from "@/contexts/accounts/domain";
|
||||
import { accountRepository } from "@/contexts/accounts/infraestructure";
|
||||
import { SequelizeTransactionManager } from "@/core/common/infrastructure";
|
||||
import { UpdateAccountController } from "./update-account.controller";
|
||||
import { updateAccountPresenter } from "./update-account.presenter";
|
||||
|
||||
export const buildUpdateAccountController = () => {
|
||||
const transactionManager = new SequelizeTransactionManager();
|
||||
const accountService = new AccountService(accountRepository);
|
||||
|
||||
const useCase = new UpdateAccountUseCase(accountService, transactionManager);
|
||||
const presenter = updateAccountPresenter;
|
||||
|
||||
return new UpdateAccountController(useCase, presenter);
|
||||
};
|
||||
@ -1,46 +0,0 @@
|
||||
import { UpdateAccountUseCase } from "@/contexts/accounts/application/update-account.use-case";
|
||||
import { UniqueID } from "@/core/common/domain";
|
||||
import { ExpressController } from "@/core/common/presentation";
|
||||
import { IUpdateAccountRequestDTO } from "../../dto";
|
||||
import { IUpdateAccountPresenter } from "./update-account.presenter";
|
||||
|
||||
export class UpdateAccountController extends ExpressController {
|
||||
public constructor(
|
||||
private readonly updateAccount: UpdateAccountUseCase,
|
||||
private readonly presenter: IUpdateAccountPresenter
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async executeImpl() {
|
||||
const { accountId } = this.req.params;
|
||||
const updateDTO: IUpdateAccountRequestDTO = this.req.body;
|
||||
|
||||
// Validar ID
|
||||
const accountIdOrError = UniqueID.create(accountId);
|
||||
if (accountIdOrError.isFailure) return this.invalidInputError("Account ID not valid");
|
||||
|
||||
const accountOrError = await this.updateAccount.execute(accountIdOrError.data, updateDTO);
|
||||
|
||||
if (accountOrError.isFailure) {
|
||||
return this.handleError(accountOrError.error);
|
||||
}
|
||||
|
||||
return this.ok(this.presenter.toDTO(accountOrError.data));
|
||||
}
|
||||
|
||||
private handleError(error: Error) {
|
||||
const message = error.message;
|
||||
|
||||
if (
|
||||
message.includes("Database connection lost") ||
|
||||
message.includes("Database request timed out")
|
||||
) {
|
||||
return this.unavailableError(
|
||||
"Database service is currently unavailable. Please try again later."
|
||||
);
|
||||
}
|
||||
|
||||
return this.conflictError(message);
|
||||
}
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
import { Account } from "@/contexts/accounts/domain";
|
||||
import { ensureBoolean, ensureNumber, ensureString } from "@repo/rdx-utils";
|
||||
import { IUpdateAccountResponseDTO } from "../../dto";
|
||||
|
||||
export interface IUpdateAccountPresenter {
|
||||
toDTO: (account: Account) => IUpdateAccountResponseDTO;
|
||||
}
|
||||
|
||||
export const updateAccountPresenter: IUpdateAccountPresenter = {
|
||||
toDTO: (account: Account): IUpdateAccountResponseDTO => ({
|
||||
id: ensureString(account.id.toString()),
|
||||
|
||||
is_companyr: ensureBoolean(account.isFreelancer),
|
||||
name: ensureString(account.name),
|
||||
trade_name: ensureString(account.tradeName.getOrUndefined()),
|
||||
tin: ensureString(account.tin.toString()),
|
||||
|
||||
street: ensureString(account.address.street),
|
||||
city: ensureString(account.address.city),
|
||||
state: ensureString(account.address.state),
|
||||
postal_code: ensureString(account.address.postalCode),
|
||||
country: ensureString(account.address.country),
|
||||
|
||||
email: ensureString(account.email.toString()),
|
||||
phone: ensureString(account.phone.toString()),
|
||||
fax: ensureString(account.fax.getOrUndefined()?.toString()),
|
||||
website: ensureString(account.website.getOrUndefined()),
|
||||
|
||||
legal_record: ensureString(account.legalRecord),
|
||||
|
||||
default_tax: ensureNumber(account.defaultTax),
|
||||
status: ensureString(account.isActive ? "active" : "inactive"),
|
||||
language_code: ensureString(account.langCode),
|
||||
currency_code: ensureString(account.currencyCode),
|
||||
logo: ensureString(account.logo.getOrUndefined()),
|
||||
}),
|
||||
};
|
||||
@ -1,52 +0,0 @@
|
||||
export type IListAccountsRequestDTO = {};
|
||||
|
||||
export interface ICreateAccountRequestDTO {
|
||||
id: string;
|
||||
is_companyr: boolean;
|
||||
name: string;
|
||||
trade_name: string;
|
||||
tin: string;
|
||||
|
||||
street: string;
|
||||
city: string;
|
||||
state: string;
|
||||
postal_code: string;
|
||||
country: string;
|
||||
|
||||
email: string;
|
||||
phone: string;
|
||||
fax: string;
|
||||
website: string;
|
||||
|
||||
legal_record: string;
|
||||
|
||||
default_tax: number;
|
||||
language_code: string;
|
||||
currency_code: string;
|
||||
logo: string;
|
||||
}
|
||||
|
||||
export interface IUpdateAccountRequestDTO {
|
||||
is_companyr: boolean;
|
||||
name: string;
|
||||
trade_name: string;
|
||||
tin: string;
|
||||
|
||||
street: string;
|
||||
city: string;
|
||||
state: string;
|
||||
postal_code: string;
|
||||
country: string;
|
||||
|
||||
email: string;
|
||||
phone: string;
|
||||
fax: string;
|
||||
website: string;
|
||||
|
||||
legal_record: string;
|
||||
|
||||
default_tax: number;
|
||||
language_code: string;
|
||||
currency_code: string;
|
||||
logo: string;
|
||||
}
|
||||
@ -1,114 +0,0 @@
|
||||
export interface IListAccountsResponseDTO {
|
||||
id: string;
|
||||
|
||||
is_companyr: boolean;
|
||||
name: string;
|
||||
trade_name: string;
|
||||
tin: string;
|
||||
|
||||
street: string;
|
||||
city: string;
|
||||
state: string;
|
||||
postal_code: string;
|
||||
country: string;
|
||||
|
||||
email: string;
|
||||
phone: string;
|
||||
fax: string;
|
||||
website: string;
|
||||
|
||||
legal_record: string;
|
||||
|
||||
default_tax: number;
|
||||
status: string;
|
||||
language_code: string;
|
||||
currency_code: string;
|
||||
logo: string;
|
||||
}
|
||||
|
||||
export interface IGetAccountResponseDTO {
|
||||
id: string;
|
||||
|
||||
is_companyr: boolean;
|
||||
name: string;
|
||||
trade_name: string;
|
||||
tin: string;
|
||||
|
||||
street: string;
|
||||
city: string;
|
||||
state: string;
|
||||
postal_code: string;
|
||||
country: string;
|
||||
|
||||
email: string;
|
||||
phone: string;
|
||||
fax: string;
|
||||
website: string;
|
||||
|
||||
legal_record: string;
|
||||
|
||||
default_tax: number;
|
||||
status: string;
|
||||
language_code: string;
|
||||
currency_code: string;
|
||||
logo: string;
|
||||
}
|
||||
|
||||
export interface ICreateAccountResponseDTO {
|
||||
id: string;
|
||||
|
||||
is_companyr: boolean;
|
||||
name: string;
|
||||
trade_name: string;
|
||||
tin: string;
|
||||
|
||||
street: string;
|
||||
city: string;
|
||||
state: string;
|
||||
postal_code: string;
|
||||
country: string;
|
||||
|
||||
email: string;
|
||||
phone: string;
|
||||
fax: string;
|
||||
website: string;
|
||||
|
||||
legal_record: string;
|
||||
|
||||
default_tax: number;
|
||||
status: string;
|
||||
language_code: string;
|
||||
currency_code: string;
|
||||
logo: string;
|
||||
}
|
||||
|
||||
// Inferir el tipo en TypeScript desde el esquema Zod
|
||||
//export type IUpdateAcccountResponseDTO = z.infer<typeof IUpdateAcccountResponseDTOSchema>;
|
||||
|
||||
export interface IUpdateAccountResponseDTO {
|
||||
id: string;
|
||||
|
||||
is_companyr: boolean;
|
||||
name: string;
|
||||
trade_name: string;
|
||||
tin: string;
|
||||
|
||||
street: string;
|
||||
city: string;
|
||||
state: string;
|
||||
postal_code: string;
|
||||
country: string;
|
||||
|
||||
email: string;
|
||||
phone: string;
|
||||
fax: string;
|
||||
website: string;
|
||||
|
||||
legal_record: string;
|
||||
|
||||
default_tax: number;
|
||||
status: string;
|
||||
language_code: string;
|
||||
currency_code: string;
|
||||
logo: string;
|
||||
}
|
||||
@ -1,63 +0,0 @@
|
||||
import * as z from "zod/v4";
|
||||
|
||||
export const ListAccountsRequestSchema = z.object({});
|
||||
|
||||
export const IGetAccountRequestSchema = z.object({});
|
||||
|
||||
export const ICreateAccountRequestSchema = z.object({
|
||||
id: z.string(),
|
||||
|
||||
is_companyr: z.boolean(),
|
||||
name: z.string(),
|
||||
trade_name: z.string(),
|
||||
tin: z.string(),
|
||||
|
||||
street: z.string(),
|
||||
city: z.string(),
|
||||
state: z.string(),
|
||||
postal_code: z.string(),
|
||||
country: z.string(),
|
||||
|
||||
email: z.string().email(), // Validación específica para email
|
||||
phone: z.string(),
|
||||
fax: z.string(),
|
||||
website: z.string().url(), // Validación específica para URL
|
||||
|
||||
legal_record: z.string(),
|
||||
|
||||
default_tax: z.number(),
|
||||
status: z.string(),
|
||||
language_code: LanguageCodeSchema,
|
||||
currency_code: CurrencyCodeSchema,
|
||||
logo: z.string(),
|
||||
});
|
||||
|
||||
export const IUpdateAccountRequestSchema = z.object({
|
||||
id: z.string(),
|
||||
|
||||
is_companyr: z.boolean(),
|
||||
name: z.string(),
|
||||
trade_name: z.string(),
|
||||
tin: z.string(),
|
||||
|
||||
street: z.string(),
|
||||
city: z.string(),
|
||||
state: z.string(),
|
||||
postal_code: z.string(),
|
||||
country: z.string(),
|
||||
|
||||
email: z.string().email(), // Validación específica para email
|
||||
phone: z.string(),
|
||||
fax: z.string(),
|
||||
website: z.string().url(), // Validación específica para URL
|
||||
|
||||
legal_record: z.string(),
|
||||
|
||||
default_tax: z.number(),
|
||||
status: z.string(),
|
||||
language_code: LanguageCodeSchema,
|
||||
currency_code: CurrencyCodeSchema,
|
||||
logo: z.string(),
|
||||
});
|
||||
|
||||
export const IDeleteAccountRequestSchema = z.object({});
|
||||
@ -1,3 +0,0 @@
|
||||
export * from "./accounts.request.dto";
|
||||
export * from "./accounts.response.dto";
|
||||
export * from "./accounts.schemas";
|
||||
@ -1,2 +0,0 @@
|
||||
export * from "./controllers";
|
||||
export * from "./dto";
|
||||
@ -1,5 +0,0 @@
|
||||
export * from "./list-users";
|
||||
export * from "./login";
|
||||
export * from "./logout";
|
||||
export * from "./refresh-token";
|
||||
export * from "./register";
|
||||
@ -1 +0,0 @@
|
||||
export * from "./list-users.use-case";
|
||||
@ -1,17 +0,0 @@
|
||||
import { ITransactionManager } from "@/core/common/infrastructure/database";
|
||||
import { Collection, Result } from "@repo/rdx-utils";
|
||||
import { User } from "../../domain";
|
||||
import { IUserService } from "../../domain/services";
|
||||
|
||||
export class ListUsersUseCase {
|
||||
constructor(
|
||||
private readonly userService: IUserService,
|
||||
private readonly transactionManager: ITransactionManager
|
||||
) {}
|
||||
|
||||
public execute(): Promise<Result<Collection<User>, Error>> {
|
||||
return this.transactionManager.complete((transaction) => {
|
||||
return this.userService.findUsers(transaction);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from "./login.use-case";
|
||||
@ -1,16 +0,0 @@
|
||||
import { ITransactionManager } from "@/core/common/infrastructure/database";
|
||||
import { LoginData } from "../../domain";
|
||||
import { IAuthService } from "../../domain/services";
|
||||
|
||||
export class LoginUseCase {
|
||||
constructor(
|
||||
private readonly authService: IAuthService,
|
||||
private readonly transactionManager: ITransactionManager
|
||||
) {}
|
||||
|
||||
public async execute(loginData: LoginData) {
|
||||
return await this.transactionManager.complete(async (transaction) => {
|
||||
return await this.authService.loginUser(loginData, transaction);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from "./logout.use-case";
|
||||
@ -1,16 +0,0 @@
|
||||
import { ITransactionManager } from "@/core/common/infrastructure/database";
|
||||
import { LogoutData } from "../../domain";
|
||||
import { IAuthService } from "../../domain/services";
|
||||
|
||||
export class LogoutUseCase {
|
||||
constructor(
|
||||
private readonly authService: IAuthService,
|
||||
private readonly transactionManager: ITransactionManager
|
||||
) {}
|
||||
|
||||
public async execute(logoutData: LogoutData) {
|
||||
return await this.transactionManager.complete(async (transaction) => {
|
||||
return await this.authService.logoutUser(logoutData, transaction);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from "./refresh-token.use-case";
|
||||
@ -1,24 +0,0 @@
|
||||
import { ITransactionManager } from "@/core/common/infrastructure/database";
|
||||
import { Token } from "../../domain";
|
||||
import { IAuthService } from "../../domain/services";
|
||||
|
||||
export class RefreshTokenUseCase {
|
||||
constructor(
|
||||
private readonly authService: IAuthService,
|
||||
private readonly transactionManager: ITransactionManager
|
||||
) {}
|
||||
|
||||
public async execute(token: Token) {
|
||||
return await this.transactionManager.complete(async (transaction) => {
|
||||
const payloadData = this.authService.verifyRefreshToken(token);
|
||||
|
||||
/*if (!payload || !payload.email || !payload.user_id || !payload.tab_id || !payload.roles) {
|
||||
return Result.fail(new Error("Invalid input data"));
|
||||
}*/
|
||||
|
||||
return this.authService.generateRefreshToken({
|
||||
...payloadData,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from "./register.use-case";
|
||||
@ -1,23 +0,0 @@
|
||||
import { ITransactionManager } from "@/core/common/infrastructure/database";
|
||||
import { logger } from "@/core/logger";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { RegisterData } from "../../domain";
|
||||
import { IAuthService } from "../../domain/services";
|
||||
|
||||
export class RegisterUseCase {
|
||||
constructor(
|
||||
private readonly authService: IAuthService,
|
||||
private readonly transactionManager: ITransactionManager
|
||||
) {}
|
||||
|
||||
public async execute(registerData: RegisterData) {
|
||||
return await this.transactionManager.complete(async (transaction) => {
|
||||
try {
|
||||
return await this.authService.registerUser(registerData, transaction);
|
||||
} catch (error: unknown) {
|
||||
logger.error(error as Error);
|
||||
return Result.fail(error as Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,91 +0,0 @@
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
import { AggregateRoot, EmailAddress, UniqueID } from "@/core/common/domain";
|
||||
import { UserAuthenticatedEvent } from "../events";
|
||||
import { HashPassword, PlainPassword, Username } from "../value-objects";
|
||||
|
||||
export interface IAuthenticatedUserProps {
|
||||
username: Username;
|
||||
email: EmailAddress;
|
||||
hashPassword: HashPassword;
|
||||
roles: string[];
|
||||
}
|
||||
|
||||
export interface IAuthenticatedUser {
|
||||
username: Username;
|
||||
email: EmailAddress;
|
||||
hashPassword: HashPassword;
|
||||
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
|
||||
isUser: boolean;
|
||||
isAdmin: boolean;
|
||||
|
||||
verifyPassword(candidatePassword: PlainPassword): Promise<boolean>;
|
||||
hasRole(role: string): boolean;
|
||||
hasRoles(roles: string[]): boolean;
|
||||
getRoles(): string[];
|
||||
toPersistenceData(): any;
|
||||
}
|
||||
|
||||
export class AuthenticatedUser
|
||||
extends AggregateRoot<IAuthenticatedUserProps>
|
||||
implements IAuthenticatedUser
|
||||
{
|
||||
public accessToken = "";
|
||||
public refreshToken = "";
|
||||
|
||||
static create(props: IAuthenticatedUserProps, id: UniqueID): Result<AuthenticatedUser, Error> {
|
||||
const user = new AuthenticatedUser(props, id);
|
||||
|
||||
// 🔹 Disparar evento de dominio "UserAuthenticatedEvent"
|
||||
const { email } = props;
|
||||
user.addDomainEvent(new UserAuthenticatedEvent(id, email.toString()));
|
||||
|
||||
return Result.ok(user);
|
||||
}
|
||||
|
||||
verifyPassword(candidatePassword: PlainPassword): Promise<boolean> {
|
||||
return this.props.hashPassword.verifyPassword(candidatePassword.toString());
|
||||
}
|
||||
|
||||
getRoles(): string[] {
|
||||
return this.props.roles;
|
||||
}
|
||||
|
||||
hasRole(role: string): boolean {
|
||||
return (this.props.roles || []).some((r) => r === role);
|
||||
}
|
||||
|
||||
hasRoles(roles: string[]): boolean {
|
||||
return roles && roles.map((rol) => this.hasRole(rol)).some((value) => value != false);
|
||||
}
|
||||
|
||||
get username(): Username {
|
||||
return this.props.username;
|
||||
}
|
||||
|
||||
get email(): EmailAddress {
|
||||
return this.props.email;
|
||||
}
|
||||
|
||||
get hashPassword(): HashPassword {
|
||||
return this.props.hashPassword;
|
||||
}
|
||||
|
||||
get isUser(): boolean {
|
||||
return this.hasRole("user");
|
||||
}
|
||||
|
||||
get isAdmin(): boolean {
|
||||
return this.hasRole("admin");
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔹 Devuelve una representación lista para persistencia
|
||||
*/
|
||||
toPersistenceData(): any {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
export * from "./authenticated-user";
|
||||
|
||||
export * from "./role";
|
||||
export * from "./user";
|
||||
@ -1,15 +0,0 @@
|
||||
import { AggregateRoot, UniqueID } from "@/core/common/domain";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
export type IRoleProps = {};
|
||||
|
||||
export type IRole = {};
|
||||
|
||||
export class Role extends AggregateRoot<IRoleProps> implements IRole {
|
||||
static create(props: IRoleProps, id: UniqueID): Result<Role, Error> {
|
||||
const role = new Role(props, id);
|
||||
return Result.ok(role);
|
||||
}
|
||||
|
||||
toPersistenceData(): any {}
|
||||
}
|
||||
@ -1,67 +0,0 @@
|
||||
import { AggregateRoot, EmailAddress, UniqueID } from "@/core/common/domain";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { UserAuthenticatedEvent } from "../events";
|
||||
import { Username } from "../value-objects";
|
||||
|
||||
export interface IUserProps {
|
||||
username: Username;
|
||||
email: EmailAddress;
|
||||
roles: string[];
|
||||
}
|
||||
|
||||
export interface IUser {
|
||||
username: Username;
|
||||
email: EmailAddress;
|
||||
|
||||
isUser: boolean;
|
||||
isAdmin: boolean;
|
||||
isActive: boolean;
|
||||
|
||||
hasRole(role: string): boolean;
|
||||
hasRoles(roles: string[]): boolean;
|
||||
getRoles(): string[];
|
||||
}
|
||||
|
||||
export class User extends AggregateRoot<IUserProps> implements IUser {
|
||||
static create(props: IUserProps, id: UniqueID): Result<User, Error> {
|
||||
const user = new User(props, id);
|
||||
|
||||
// 🔹 Disparar evento de dominio "UserAuthenticatedEvent"
|
||||
const { email } = props;
|
||||
user.addDomainEvent(new UserAuthenticatedEvent(id, email.toString()));
|
||||
|
||||
return Result.ok(user);
|
||||
}
|
||||
|
||||
getRoles(): string[] {
|
||||
return this.props.roles;
|
||||
}
|
||||
|
||||
hasRole(role: string): boolean {
|
||||
return (this.props.roles || []).some((r) => r === role);
|
||||
}
|
||||
|
||||
hasRoles(roles: string[]): boolean {
|
||||
return roles.map((rol) => this.hasRole(rol)).some((value) => value != false);
|
||||
}
|
||||
|
||||
get username(): Username {
|
||||
return this.props.username;
|
||||
}
|
||||
|
||||
get email(): EmailAddress {
|
||||
return this.props.email;
|
||||
}
|
||||
|
||||
get isUser(): boolean {
|
||||
return this.hasRole("user");
|
||||
}
|
||||
|
||||
get isAdmin(): boolean {
|
||||
return this.hasRole("admin");
|
||||
}
|
||||
|
||||
get isActive(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
export * from "./jwt-payload";
|
||||
export * from "./login-data";
|
||||
export * from "./logout-data";
|
||||
export * from "./register-data";
|
||||
export * from "./tab-context";
|
||||
@ -1,67 +0,0 @@
|
||||
import { DomainEntity, EmailAddress, UniqueID } from "@/core/common/domain";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
export interface IJWTPayloadProps {
|
||||
tabId: UniqueID;
|
||||
userId: UniqueID;
|
||||
email: EmailAddress;
|
||||
}
|
||||
|
||||
export interface IJWTPayloadPrimitives {
|
||||
tab_id: string;
|
||||
user_id: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface IJWTPayload {
|
||||
tabId: UniqueID;
|
||||
userId: UniqueID;
|
||||
email: EmailAddress;
|
||||
|
||||
toPersistenceData(): any;
|
||||
}
|
||||
|
||||
export class JWTPayload extends DomainEntity<IJWTPayloadProps> implements IJWTPayload {
|
||||
static create(props: IJWTPayloadProps): Result<JWTPayload, Error> {
|
||||
return Result.ok(new JWTPayload(props));
|
||||
}
|
||||
|
||||
static createFromPrimitives(values: IJWTPayloadPrimitives): Result<JWTPayload, Error> {
|
||||
const { email, user_id, tab_id } = values;
|
||||
const emailOrError = EmailAddress.create(email);
|
||||
const userIdOrError = UniqueID.create(user_id, false);
|
||||
const tabIdOrError = UniqueID.create(tab_id, false);
|
||||
|
||||
const result = Result.combine([emailOrError, userIdOrError, tabIdOrError]);
|
||||
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
}
|
||||
|
||||
return JWTPayload.create({
|
||||
email: emailOrError.data,
|
||||
userId: userIdOrError.data,
|
||||
tabId: tabIdOrError.data,
|
||||
});
|
||||
}
|
||||
|
||||
get tabId(): UniqueID {
|
||||
return this.props.tabId;
|
||||
}
|
||||
|
||||
get userId(): UniqueID {
|
||||
return this.props.userId;
|
||||
}
|
||||
|
||||
get email(): EmailAddress {
|
||||
return this.props.email;
|
||||
}
|
||||
|
||||
toPersistenceData(): any {
|
||||
return {
|
||||
tab_id: this.tabId.toString(),
|
||||
user_id: this.userId.toString(),
|
||||
email: this.email.toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,58 +0,0 @@
|
||||
import { DomainEntity, EmailAddress, UniqueID } from "@/core/common/domain";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { PlainPassword } from "../value-objects";
|
||||
|
||||
export interface ILoginDataProps {
|
||||
email: EmailAddress;
|
||||
plainPassword: PlainPassword;
|
||||
tabId: UniqueID;
|
||||
}
|
||||
|
||||
export interface ILoginDataPrimitives {
|
||||
email: string;
|
||||
plainPassword: string;
|
||||
tabId: string;
|
||||
}
|
||||
|
||||
export interface ILoginData {
|
||||
email: EmailAddress;
|
||||
plainPassword: PlainPassword;
|
||||
tabId: UniqueID;
|
||||
}
|
||||
|
||||
export class LoginData extends DomainEntity<ILoginDataProps> implements ILoginData {
|
||||
static create(props: ILoginDataProps): Result<LoginData, Error> {
|
||||
return Result.ok(new this(props));
|
||||
}
|
||||
|
||||
static createFromPrimitives(values: ILoginDataPrimitives): Result<LoginData, Error> {
|
||||
const { email, plainPassword, tabId } = values;
|
||||
const emailOrError = EmailAddress.create(email);
|
||||
const plainPasswordOrError = PlainPassword.create(plainPassword);
|
||||
const tabIdOrError = UniqueID.create(tabId, false);
|
||||
|
||||
const result = Result.combine([emailOrError, plainPasswordOrError, tabIdOrError]);
|
||||
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
}
|
||||
|
||||
return LoginData.create({
|
||||
email: emailOrError.data,
|
||||
plainPassword: plainPasswordOrError.data,
|
||||
tabId: tabIdOrError.data,
|
||||
});
|
||||
}
|
||||
|
||||
get email(): EmailAddress {
|
||||
return this.props.email;
|
||||
}
|
||||
|
||||
get plainPassword(): PlainPassword {
|
||||
return this.props.plainPassword;
|
||||
}
|
||||
|
||||
get tabId(): UniqueID {
|
||||
return this.props.tabId;
|
||||
}
|
||||
}
|
||||
@ -1,48 +0,0 @@
|
||||
import { DomainEntity, EmailAddress, UniqueID } from "@/core/common/domain";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
export interface ILogoutDataProps {
|
||||
email: EmailAddress;
|
||||
tabId: UniqueID;
|
||||
}
|
||||
|
||||
export interface ILogoutDataPrimitives {
|
||||
email: string;
|
||||
tabId: string;
|
||||
}
|
||||
|
||||
export interface ILogoutData {
|
||||
email: EmailAddress;
|
||||
tabId: UniqueID;
|
||||
}
|
||||
|
||||
export class LogoutData extends DomainEntity<ILogoutDataProps> implements ILogoutData {
|
||||
static create(props: ILogoutDataProps): Result<LogoutData, Error> {
|
||||
return Result.ok(new this(props));
|
||||
}
|
||||
|
||||
static createFromPrimitives(values: ILogoutDataPrimitives): Result<LogoutData, Error> {
|
||||
const { email, tabId } = values;
|
||||
const emailOrError = EmailAddress.create(email);
|
||||
const tabIdOrError = UniqueID.create(tabId, false);
|
||||
|
||||
const result = Result.combine([emailOrError, tabIdOrError]);
|
||||
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
}
|
||||
|
||||
return LogoutData.create({
|
||||
email: emailOrError.data,
|
||||
tabId: tabIdOrError.data,
|
||||
});
|
||||
}
|
||||
|
||||
get email(): EmailAddress {
|
||||
return this.props.email;
|
||||
}
|
||||
|
||||
get tabId(): UniqueID {
|
||||
return this.props.tabId;
|
||||
}
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
import { DomainEntity, EmailAddress } from "@/core/common/domain";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { HashPassword, Username } from "../value-objects";
|
||||
|
||||
export interface IRegisterDataProps {
|
||||
username: Username;
|
||||
email: EmailAddress;
|
||||
hashPassword: HashPassword;
|
||||
}
|
||||
|
||||
export interface IRegisterDataPrimitives {
|
||||
username: string;
|
||||
email: string;
|
||||
plainPassword: string;
|
||||
}
|
||||
|
||||
export interface IRegisterData {
|
||||
username: Username;
|
||||
email: EmailAddress;
|
||||
hashPassword: HashPassword;
|
||||
}
|
||||
|
||||
export class RegisterData extends DomainEntity<IRegisterDataProps> implements IRegisterData {
|
||||
static create(props: IRegisterDataProps): Result<RegisterData, Error> {
|
||||
return Result.ok(new this(props));
|
||||
}
|
||||
|
||||
static createFromPrimitives(props: IRegisterDataPrimitives): Result<RegisterData, Error> {
|
||||
const { username, email, plainPassword } = props;
|
||||
|
||||
const userNameOrError = Username.create(username);
|
||||
const emailOrError = EmailAddress.create(email);
|
||||
const hashPasswordOrError = HashPassword.createFromPlainText(plainPassword);
|
||||
|
||||
const result = Result.combine([userNameOrError, emailOrError, hashPasswordOrError]);
|
||||
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
}
|
||||
|
||||
return RegisterData.create({
|
||||
username: userNameOrError.data,
|
||||
email: emailOrError.data,
|
||||
hashPassword: hashPasswordOrError.data,
|
||||
});
|
||||
}
|
||||
|
||||
get username(): Username {
|
||||
return this.props.username;
|
||||
}
|
||||
|
||||
get email(): EmailAddress {
|
||||
return this.props.email;
|
||||
}
|
||||
|
||||
get hashPassword(): HashPassword {
|
||||
return this.props.hashPassword;
|
||||
}
|
||||
}
|
||||
@ -1,49 +0,0 @@
|
||||
import { DomainEntity, UniqueID } from "@/core/common/domain";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
export interface ITabContextProps {
|
||||
tabId: UniqueID;
|
||||
userId: UniqueID;
|
||||
}
|
||||
|
||||
export interface ITabContextPrimitives {
|
||||
id: string;
|
||||
tab_id: string;
|
||||
user_id: string;
|
||||
}
|
||||
|
||||
export interface ITabContext {
|
||||
tabId: UniqueID;
|
||||
userId: UniqueID;
|
||||
}
|
||||
|
||||
export class TabContext extends DomainEntity<ITabContextProps> implements ITabContext {
|
||||
static create(props: ITabContextProps, id?: UniqueID): Result<TabContext, Error> {
|
||||
return Result.ok(new this(props, id));
|
||||
}
|
||||
|
||||
static createFromPrimitives(values: ITabContextPrimitives): Result<TabContext, Error> {
|
||||
const { user_id, tab_id } = values;
|
||||
const userIdOrError = UniqueID.create(user_id, false);
|
||||
const tabIdOrError = UniqueID.create(tab_id, false);
|
||||
|
||||
const result = Result.combine([userIdOrError, tabIdOrError]);
|
||||
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
}
|
||||
|
||||
return TabContext.create({
|
||||
userId: userIdOrError.data,
|
||||
tabId: tabIdOrError.data,
|
||||
});
|
||||
}
|
||||
|
||||
get tabId(): UniqueID {
|
||||
return this.props.tabId;
|
||||
}
|
||||
|
||||
get userId(): UniqueID {
|
||||
return this.props.userId;
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from "./user-authenticated.event";
|
||||
@ -1,13 +0,0 @@
|
||||
import { IDomainEvent, UniqueID } from "@/core/common/domain";
|
||||
|
||||
export class UserAuthenticatedEvent implements IDomainEvent {
|
||||
public readonly eventName = "UserAuthenticated";
|
||||
public readonly occurredAt: Date;
|
||||
|
||||
constructor(
|
||||
public readonly aggregateId: UniqueID,
|
||||
public readonly email: string // Email en formato string
|
||||
) {
|
||||
this.occurredAt = new Date();
|
||||
}
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
export * from "./aggregates";
|
||||
export * from "./entities";
|
||||
export * from "./events";
|
||||
export * from "./repositories";
|
||||
export * from "./value-objects";
|
||||
@ -1,15 +0,0 @@
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
import { EmailAddress } from "@/core/common/domain";
|
||||
import { AuthenticatedUser } from "../aggregates";
|
||||
import { Username } from "../value-objects";
|
||||
|
||||
export interface IAuthenticatedUserRepository {
|
||||
getUserByEmail(email: EmailAddress, transaction?: any): Promise<Result<AuthenticatedUser, Error>>;
|
||||
userExists(
|
||||
username: Username,
|
||||
email: EmailAddress,
|
||||
transaction?: any
|
||||
): Promise<Result<boolean, Error>>;
|
||||
createUser(user: AuthenticatedUser, transaction?: any): Promise<Result<void, Error>>;
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
export * from "./authenticated-user-repository.interface";
|
||||
export * from "./tab-context-repository.interface";
|
||||
export * from "./user-permission-repository.interface";
|
||||
export * from "./user-repository.interface";
|
||||
@ -1,20 +0,0 @@
|
||||
import { UniqueID } from "@/core/common/domain";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { Transaction } from "sequelize";
|
||||
import { TabContext } from "../entities";
|
||||
|
||||
export interface ITabContextRepository {
|
||||
getContextByTabId(tabId: UniqueID, transaction?: any): Promise<Result<TabContext, Error>>;
|
||||
|
||||
contextExistsByTabId(tabId: UniqueID, transaction?: any): Promise<Result<boolean, Error>>;
|
||||
|
||||
registerContextByTabId(
|
||||
context: TabContext,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<void, Error>>;
|
||||
|
||||
unregisterContextByTabId(
|
||||
context: TabContext,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<void, Error>>;
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export type IUserPermissionRepository = {}
|
||||
@ -1,9 +0,0 @@
|
||||
import { EmailAddress, UniqueID } from "@/core/common/domain";
|
||||
import { Collection, Result } from "@repo/rdx-utils";
|
||||
import { User } from "../aggregates";
|
||||
|
||||
export interface IUserRepository {
|
||||
findAll(transaction?: any): Promise<Result<Collection<User>, Error>>;
|
||||
findById(id: UniqueID, transaction?: any): Promise<Result<User, Error>>;
|
||||
findByEmail(email: EmailAddress, transaction?: any): Promise<Result<User, Error>>;
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
import { EmailAddress } from "@/core/common/domain";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import {
|
||||
AuthenticatedUser,
|
||||
IJWTPayload,
|
||||
LoginData,
|
||||
LogoutData,
|
||||
RegisterData,
|
||||
TabContext,
|
||||
Token,
|
||||
} from "..";
|
||||
|
||||
export interface IAuthService {
|
||||
generateAccessToken(payload: IJWTPayload): Result<Token, Error>;
|
||||
generateRefreshToken(payload: IJWTPayload): Result<Token, Error>;
|
||||
verifyRefreshToken(token: Token): IJWTPayload;
|
||||
|
||||
registerUser(
|
||||
registerData: RegisterData,
|
||||
transaction?: any
|
||||
): Promise<Result<AuthenticatedUser, Error>>;
|
||||
|
||||
loginUser(
|
||||
loginData: LoginData,
|
||||
transaction?: any
|
||||
): Promise<
|
||||
Result<
|
||||
{
|
||||
user: AuthenticatedUser;
|
||||
tabContext: TabContext;
|
||||
tokens: {
|
||||
accessToken: Token;
|
||||
refreshToken: Token;
|
||||
};
|
||||
},
|
||||
Error
|
||||
>
|
||||
>;
|
||||
|
||||
logoutUser(logoutData: LogoutData, transaction?: any): Promise<Result<void, Error>>;
|
||||
getUserByEmail(email: EmailAddress, transaction?: any): Promise<Result<AuthenticatedUser, Error>>;
|
||||
}
|
||||
@ -1,205 +0,0 @@
|
||||
import { EmailAddress } from "@/core/common/domain";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import {
|
||||
AuthenticatedUser,
|
||||
type IJWTPayload,
|
||||
type LoginData,
|
||||
type RegisterData,
|
||||
TabContext,
|
||||
Token,
|
||||
} from "..";
|
||||
|
||||
import { UniqueID } from "@/core/common/domain";
|
||||
import { IAuthenticatedUserRepository, JWTPayload } from "..";
|
||||
import { JwtHelper } from "../../../../../../../modules/auth/src/api/lib/passport/jwt.helper";
|
||||
import { ITabContextRepository } from "../repositories/tab-context-repository.interface";
|
||||
import { IAuthService } from "./auth-service.interface";
|
||||
|
||||
const ACCESS_EXPIRATION = process.env.JWT_ACCESS_EXPIRATION || "1h";
|
||||
const REFRESH_EXPIRATION = process.env.JWT_REFRESH_EXPIRATION || "7d";
|
||||
|
||||
export class AuthService implements IAuthService {
|
||||
constructor(
|
||||
private readonly authUserRepo: IAuthenticatedUserRepository,
|
||||
private readonly tabContextRepo: ITabContextRepository
|
||||
) {}
|
||||
|
||||
generateAccessToken(payload: IJWTPayload): Result<Token, Error> {
|
||||
const data = payload.toPersistenceData();
|
||||
return Token.create(JwtHelper.generateToken(data, ACCESS_EXPIRATION));
|
||||
}
|
||||
|
||||
generateRefreshToken(payload: IJWTPayload): Result<Token, Error> {
|
||||
const data = payload.toPersistenceData();
|
||||
return Token.create(JwtHelper.generateToken(data, REFRESH_EXPIRATION));
|
||||
}
|
||||
|
||||
verifyRefreshToken(token: Token): IJWTPayload {
|
||||
return JwtHelper.verifyToken(token.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Registra un nuevo usuario en la base de datos bajo transacción.
|
||||
*/
|
||||
async registerUser(
|
||||
registerData: RegisterData,
|
||||
transaction?: any
|
||||
): Promise<Result<AuthenticatedUser, Error>> {
|
||||
const { username, email, hashPassword } = registerData;
|
||||
|
||||
// Verificar si el usuario ya existe
|
||||
const userExists = await this.authUserRepo.userExists(username, email, transaction);
|
||||
if (userExists.isSuccess && userExists.data) {
|
||||
return Result.fail(new Error("Email is already registered"));
|
||||
}
|
||||
|
||||
const newUserId = UniqueID.generateNewID().data;
|
||||
|
||||
const userOrError = AuthenticatedUser.create(
|
||||
{
|
||||
username,
|
||||
email,
|
||||
hashPassword,
|
||||
roles: ["USER"],
|
||||
},
|
||||
newUserId
|
||||
);
|
||||
|
||||
if (userOrError.isFailure) {
|
||||
return Result.fail(userOrError.error);
|
||||
}
|
||||
|
||||
const createdResult = await this.authUserRepo.createUser(userOrError.data, transaction);
|
||||
|
||||
if (createdResult.isFailure) {
|
||||
return Result.fail(createdResult.error);
|
||||
}
|
||||
|
||||
return Result.ok(userOrError.data);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Autentica a un usuario validando su email y contraseña.
|
||||
*/
|
||||
async loginUser(
|
||||
loginData: LoginData,
|
||||
transaction?: any
|
||||
): Promise<
|
||||
Result<
|
||||
{
|
||||
user: AuthenticatedUser;
|
||||
tabContext: TabContext;
|
||||
tokens: {
|
||||
accessToken: Token;
|
||||
refreshToken: Token;
|
||||
};
|
||||
},
|
||||
Error
|
||||
>
|
||||
> {
|
||||
let result: any;
|
||||
const { email, plainPassword, tabId } = loginData;
|
||||
|
||||
// 🔹 Verificar si el usuario existe en la base de datos
|
||||
result = await this.authUserRepo.getUserByEmail(email, transaction);
|
||||
if (result.isFailure) {
|
||||
return Result.fail(new Error("Invalid email or password"));
|
||||
}
|
||||
|
||||
const user = result.data;
|
||||
|
||||
// 🔹 Verificar que la contraseña sea correcta
|
||||
const isValidPassword = await user.verifyPassword(plainPassword);
|
||||
if (!isValidPassword) {
|
||||
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,
|
||||
});
|
||||
|
||||
// 🔹 Generar Access Token y Refresh Token
|
||||
const payloadOrError = JWTPayload.create({
|
||||
userId: user.id,
|
||||
email: email,
|
||||
tabId: tabId,
|
||||
//roles: ["USER"],
|
||||
});
|
||||
|
||||
result = Result.combine([contextOrError, payloadOrError]);
|
||||
if (result.isFailure) {
|
||||
return Result.fail(new Error("Error on login"));
|
||||
}
|
||||
|
||||
const tabContext = contextOrError.data;
|
||||
await this.tabContextRepo.registerContextByTabId(tabContext, transaction);
|
||||
|
||||
const accessTokenOrError = this.generateAccessToken(payloadOrError.data);
|
||||
const refreshTokenOrError = this.generateRefreshToken(payloadOrError.data);
|
||||
|
||||
result = Result.combine([accessTokenOrError, refreshTokenOrError]);
|
||||
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
}
|
||||
|
||||
return Result.ok({
|
||||
user,
|
||||
tabContext,
|
||||
tokens: {
|
||||
accessToken: accessTokenOrError.data,
|
||||
refreshToken: refreshTokenOrError.data,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Autentica a un usuario validando su email y contraseña.
|
||||
*/
|
||||
async logoutUser(
|
||||
params: { email: EmailAddress; tabId: UniqueID },
|
||||
transaction?: any
|
||||
): Promise<Result<void, Error>> {
|
||||
const { email, tabId } = params;
|
||||
|
||||
// 🔹 Verificar si el usuario existe en la base de datos
|
||||
const userResult = await this.authUserRepo.getUserByEmail(email, transaction);
|
||||
if (userResult.isFailure) {
|
||||
return Result.fail(new Error("Invalid email or password"));
|
||||
}
|
||||
|
||||
const user = userResult.data;
|
||||
|
||||
const contextOrError = TabContext.create({
|
||||
userId: user.id,
|
||||
tabId: tabId,
|
||||
});
|
||||
|
||||
if (contextOrError.isFailure) {
|
||||
return Result.fail(new Error("Error creating user context"));
|
||||
}
|
||||
|
||||
// Desregistrar el contexto de ese tab ID
|
||||
await this.tabContextRepo.unregisterContextByTabId(contextOrError.data, transaction);
|
||||
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
async getUserByEmail(
|
||||
email: EmailAddress,
|
||||
transaction?: any
|
||||
): Promise<Result<AuthenticatedUser, Error>> {
|
||||
const userResult = await this.authUserRepo.getUserByEmail(email, transaction);
|
||||
|
||||
if (userResult.isFailure || !userResult.data) {
|
||||
return Result.fail(new Error("Invalid email or password"));
|
||||
}
|
||||
|
||||
return Result.ok(userResult.data);
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
export * from "./auth-service.interface";
|
||||
export * from "./auth.service";
|
||||
|
||||
export * from "./tab-context-service.interface";
|
||||
export * from "./tab-context.service";
|
||||
|
||||
export * from "./user-service.interface";
|
||||
export * from "./user.service";
|
||||
@ -1,15 +0,0 @@
|
||||
import { UniqueID } from "@/core/common/domain";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { TabContext } from "../entities";
|
||||
|
||||
export interface ITabContextService {
|
||||
getContextByTabId(tabId: UniqueID, transaction?: any): Promise<Result<TabContext, Error>>;
|
||||
createContext(
|
||||
params: { tabId: UniqueID; userId: UniqueID },
|
||||
transaction?: any
|
||||
): Promise<Result<TabContext, Error>>;
|
||||
removeContext(
|
||||
params: { tabId: UniqueID; userId: UniqueID },
|
||||
transaction?: any
|
||||
): Promise<Result<void, Error>>;
|
||||
}
|
||||
@ -1,87 +0,0 @@
|
||||
import { UniqueID } from "@/core/common/domain";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { TabContext } from "../entities";
|
||||
import { ITabContextRepository } from "../repositories";
|
||||
import { ITabContextService } from "./tab-context-service.interface";
|
||||
|
||||
export class TabContextService implements ITabContextService {
|
||||
constructor(private readonly tabContextRepo: ITabContextRepository) {}
|
||||
|
||||
/**
|
||||
* Obtiene el contexto de una pestaña por su ID
|
||||
*/
|
||||
async getContextByTabId(tabId: UniqueID, transaction?: any): Promise<Result<TabContext, Error>> {
|
||||
// Verificar si la pestaña existe
|
||||
const tabContextOrError = await this.tabContextRepo.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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registra un nuevo contexto de pestaña para un usuario
|
||||
*/
|
||||
async createContext(
|
||||
params: {
|
||||
tabId: UniqueID;
|
||||
userId: UniqueID;
|
||||
},
|
||||
transaction?: any
|
||||
): Promise<Result<TabContext, Error>> {
|
||||
const { tabId, userId } = params;
|
||||
|
||||
if (!userId || !tabId) {
|
||||
return Result.fail(new Error("User ID and Tab ID are required"));
|
||||
}
|
||||
|
||||
const contextOrError = TabContext.create(
|
||||
{
|
||||
userId,
|
||||
tabId,
|
||||
},
|
||||
UniqueID.generateNewID().data
|
||||
);
|
||||
|
||||
if (contextOrError.isFailure) {
|
||||
return Result.fail(contextOrError.error);
|
||||
}
|
||||
|
||||
await this.tabContextRepo.registerContextByTabId(contextOrError.data, transaction);
|
||||
|
||||
return Result.ok(contextOrError.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Elimina un contexto de pestaña por su ID
|
||||
*/
|
||||
async removeContext(
|
||||
params: { tabId: UniqueID; userId: UniqueID },
|
||||
transaction?: any
|
||||
): Promise<Result<void, Error>> {
|
||||
const { tabId, userId } = params;
|
||||
|
||||
if (!userId || !tabId) {
|
||||
return Result.fail(new Error("User ID and Tab ID are required"));
|
||||
}
|
||||
|
||||
const contextOrError = TabContext.create(
|
||||
{
|
||||
userId,
|
||||
tabId,
|
||||
},
|
||||
UniqueID.generateNewID().data
|
||||
);
|
||||
|
||||
if (contextOrError.isFailure) {
|
||||
return Result.fail(contextOrError.error);
|
||||
}
|
||||
|
||||
return await this.tabContextRepo.unregisterContextByTabId(contextOrError.data, transaction);
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
import { UniqueID } from "@/core/common/domain";
|
||||
import { Collection, Result } from "@repo/rdx-utils";
|
||||
import { User } from "../aggregates";
|
||||
|
||||
export interface IUserService {
|
||||
findUsers(transaction?: any): Promise<Result<Collection<User>, Error>>;
|
||||
findUserById(userId: UniqueID, transaction?: any): Promise<Result<User>>;
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
import { UniqueID } from "@/core/common/domain";
|
||||
import { Collection, Result } from "@repo/rdx-utils";
|
||||
import { IUserRepository, User } from "..";
|
||||
import { IUserService } from "./user-service.interface";
|
||||
|
||||
export class UserService implements IUserService {
|
||||
constructor(private readonly userRepository: IUserRepository) {}
|
||||
|
||||
async findUsers(transaction?: any): Promise<Result<Collection<User>, Error>> {
|
||||
const usersOrError = await this.userRepository.findAll(transaction);
|
||||
if (usersOrError.isFailure) {
|
||||
return Result.fail(usersOrError.error);
|
||||
}
|
||||
|
||||
// Solo devolver usuarios activos
|
||||
const activeUsers = usersOrError.data.filter((user) => user.isActive);
|
||||
return Result.ok(new Collection(activeUsers));
|
||||
}
|
||||
|
||||
async findUserById(userId: UniqueID, transaction?: any): Promise<Result<User>> {
|
||||
return await this.userRepository.findById(userId, transaction);
|
||||
}
|
||||
|
||||
/*public async createUser(
|
||||
data: { name: string; email: EmailAddress },
|
||||
transaction?: Transaction
|
||||
): Promise<Result<User>> {
|
||||
// Evitar duplicados por email
|
||||
const existingUser = await this.userRepository.findByEmail(data.email);
|
||||
if (existingUser.isSuccess) {
|
||||
return Result.fail(new Error("El correo ya está registrado."));
|
||||
}
|
||||
|
||||
const newUser = User.create({
|
||||
email,
|
||||
username
|
||||
})
|
||||
return await this.userRepository.save(newUser, transaction);
|
||||
}*/
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
import { ValueObject } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import * as z from "zod/v4";
|
||||
|
||||
const RoleSchema = z.enum(["Admin", "User", "Manager", "Editor"]);
|
||||
|
||||
interface UserRolesProps {
|
||||
value: string[];
|
||||
}
|
||||
|
||||
export class UserRoles extends ValueObject<UserRolesProps> {
|
||||
static create(roles: string[]): Result<UserRoles, Error> {
|
||||
const result = UserRoles.validate(roles);
|
||||
|
||||
return result.success
|
||||
? Result.ok(new UserRoles({ value: result.data }))
|
||||
: Result.fail(new Error("Invalid user roles"));
|
||||
}
|
||||
|
||||
private static validate(roles: string[]) {
|
||||
return z.array(RoleSchema).safeParse(roles);
|
||||
}
|
||||
|
||||
hasRole(role: string): boolean {
|
||||
return this.props.value.includes(role);
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
toPrimitive() {
|
||||
return this.props.value;
|
||||
}
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
import bcrypt from "bcrypt";
|
||||
import { HashPassword } from "./hash-password";
|
||||
|
||||
describe("HashPassword", () => {
|
||||
test("debe crear una instancia de HashPassword desde un texto plano válido", () => {
|
||||
const result = HashPassword.createFromPlainText("securepassword");
|
||||
expect(result.isSuccess).toBe(true);
|
||||
expect(result.data).toBeInstanceOf(HashPassword);
|
||||
});
|
||||
|
||||
test("debe fallar al crear una instancia con una contraseña demasiado corta", () => {
|
||||
const result = HashPassword.createFromPlainText("123");
|
||||
expect(result.isFailure).toBe(true);
|
||||
expect(result.error.message).toBe("Password must be at least 6 characters long");
|
||||
});
|
||||
|
||||
test("debe crear una instancia de HashPassword desde un hash válido", () => {
|
||||
const hashedPassword = bcrypt.hashSync("securepassword", 10);
|
||||
const result = HashPassword.createFromHash(hashedPassword);
|
||||
expect(result.isSuccess).toBe(true);
|
||||
expect(result.data).toBeInstanceOf(HashPassword);
|
||||
});
|
||||
|
||||
test("debe verificar correctamente una contraseña válida", async () => {
|
||||
const password = "securepassword";
|
||||
const result = HashPassword.createFromPlainText(password);
|
||||
expect(result.isSuccess).toBe(true);
|
||||
const hashPasswordInstance = result.data;
|
||||
|
||||
const isValid = await hashPasswordInstance.verifyPassword(password);
|
||||
expect(isValid).toBe(true);
|
||||
});
|
||||
|
||||
test("debe fallar la verificación con una contraseña incorrecta", async () => {
|
||||
const password = "securepassword";
|
||||
const wrongPassword = "wrongpassword";
|
||||
const result = HashPassword.createFromPlainText(password);
|
||||
expect(result.isSuccess).toBe(true);
|
||||
const hashPasswordInstance = result.data;
|
||||
|
||||
const isValid = await hashPasswordInstance.verifyPassword(wrongPassword);
|
||||
expect(isValid).toBe(false);
|
||||
});
|
||||
});
|
||||
@ -1,48 +0,0 @@
|
||||
import { ValueObject } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import bcrypt from "bcrypt";
|
||||
import * as z from "zod/v4";
|
||||
|
||||
interface HashPasswordProps {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export class HashPassword extends ValueObject<HashPasswordProps> {
|
||||
private static readonly SALT_ROUNDS = 10;
|
||||
|
||||
static createFromPlainText(plainTextPassword: string): Result<HashPassword, Error> {
|
||||
const result = HashPassword.validate(plainTextPassword);
|
||||
|
||||
if (!result.success) {
|
||||
return Result.fail(new Error(result.error.errors[0].message));
|
||||
}
|
||||
|
||||
const hashed = bcrypt.hashSync(result.data, this.SALT_ROUNDS);
|
||||
return Result.ok(new HashPassword({ value: hashed }));
|
||||
}
|
||||
|
||||
private static validate(password: string) {
|
||||
const schema = z.string().min(6, { message: "Password must be at least 6 characters long" });
|
||||
return schema.safeParse(password);
|
||||
}
|
||||
|
||||
static createFromHash(hashedPassword: string): Result<HashPassword, Error> {
|
||||
return Result.ok(new HashPassword({ value: hashedPassword }));
|
||||
}
|
||||
|
||||
async verifyPassword(plainTextPassword: string): Promise<boolean> {
|
||||
return await bcrypt.compare(plainTextPassword, this.props.value);
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
toPrimitive() {
|
||||
return this.props.value;
|
||||
}
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
export * from "./auth-user-roles";
|
||||
export * from "./hash-password";
|
||||
export * from "./plain-password";
|
||||
export * from "./token";
|
||||
export * from "./username";
|
||||
@ -1,36 +0,0 @@
|
||||
import { ValueObject } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import * as z from "zod/v4";
|
||||
|
||||
interface PlainPasswordProps {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export class PlainPassword extends ValueObject<PlainPasswordProps> {
|
||||
static create(plainTextPassword: string): Result<PlainPassword, Error> {
|
||||
const result = PlainPassword.validate(plainTextPassword);
|
||||
|
||||
if (!result.success) {
|
||||
return Result.fail(new Error(result.error.errors[0].message));
|
||||
}
|
||||
|
||||
return Result.ok(new PlainPassword({ value: result.data }));
|
||||
}
|
||||
|
||||
private static validate(password: string) {
|
||||
const schema = z.string().min(6, { message: "Password must be at least 6 characters long" });
|
||||
return schema.safeParse(password);
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
toPrimitive() {
|
||||
return this.props.value;
|
||||
}
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
import { ValueObject } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import * as z from "zod/v4";
|
||||
|
||||
interface TokenProps {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export class Token extends ValueObject<TokenProps> {
|
||||
static create(token: string): Result<Token, Error> {
|
||||
const result = Token.validate(token);
|
||||
|
||||
if (!result.success) {
|
||||
return Result.fail(new Error(result.error.errors[0].message));
|
||||
}
|
||||
|
||||
return Result.ok(new Token({ value: result.data }));
|
||||
}
|
||||
|
||||
private static validate(token: string) {
|
||||
const schema = z.string().min(1, { message: "Invalid token string" });
|
||||
return schema.safeParse(token);
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
toPrimitive() {
|
||||
return this.props.value;
|
||||
}
|
||||
}
|
||||
@ -1,41 +0,0 @@
|
||||
import { ValueObject } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import * as z from "zod/v4";
|
||||
|
||||
interface UsernameProps {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export class Username extends ValueObject<UsernameProps> {
|
||||
static create(username: string): Result<Username, Error> {
|
||||
const result = Username.validate(username);
|
||||
|
||||
return result.success
|
||||
? Result.ok(new Username({ value: result.data }))
|
||||
: Result.fail(new Error(result.error.errors[0].message));
|
||||
}
|
||||
|
||||
private static validate(username: string) {
|
||||
const schema = z
|
||||
.string()
|
||||
.min(3, { message: "Username must be at least 3 characters long" })
|
||||
.max(30, { message: "Username cannot exceed 30 characters" })
|
||||
.regex(/^[a-zA-Z0-9_]+$/, {
|
||||
message: "Username can only contain letters, numbers, and underscores",
|
||||
});
|
||||
|
||||
return schema.safeParse(username);
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
toPrimitive() {
|
||||
return this.props.value;
|
||||
}
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
import type { Request } from "express";
|
||||
|
||||
import type { AuthenticatedUser, TabContext } from "../../domain";
|
||||
|
||||
export interface TabContextRequest extends Request {
|
||||
tabContext?: TabContext;
|
||||
}
|
||||
|
||||
export interface AuthenticatedRequest extends Request {
|
||||
user?: AuthenticatedUser;
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
export * from "../../../../../../modules/auth/src/api/lib/passport";
|
||||
export * from "./mappers";
|
||||
export * from "./middleware";
|
||||
export * from "./sequelize";
|
||||
@ -1,69 +0,0 @@
|
||||
import {
|
||||
EmailAddress,
|
||||
ISequelizeMapper,
|
||||
MapperParamsType,
|
||||
Result,
|
||||
SequelizeMapper,
|
||||
UniqueID,
|
||||
} from "@/core";
|
||||
import { AuthUserCreationAttributes, AuthUserModel } from '../sequelize';
|
||||
import { AuthenticatedUser, HashPassword, Username } from '../../domain';
|
||||
|
||||
export interface IAuthenticatedUserMapper
|
||||
extends ISequelizeMapper<AuthUserModel, AuthUserCreationAttributes, AuthenticatedUser> {}
|
||||
|
||||
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
|
||||
const uniqueIdResult = UniqueID.create(source.id);
|
||||
const usernameResult = Username.create(source.username);
|
||||
const passwordHashResult = HashPassword.createFromHash(source.hash_password);
|
||||
const emailResult = EmailAddress.create(source.email);
|
||||
|
||||
// Validar que no haya errores en la creación de los Value Objects
|
||||
const okOrError = Result.combine([
|
||||
uniqueIdResult,
|
||||
usernameResult,
|
||||
passwordHashResult,
|
||||
emailResult,
|
||||
]);
|
||||
if (okOrError.isFailure) {
|
||||
return Result.fail(okOrError.error.message);
|
||||
}
|
||||
|
||||
// Crear el agregado de dominio
|
||||
return AuthenticatedUser.create(
|
||||
{
|
||||
username: usernameResult.data!,
|
||||
email: emailResult.data!,
|
||||
hashPassword: passwordHashResult.data!,
|
||||
roles: source.roles || [],
|
||||
},
|
||||
uniqueIdResult.data!
|
||||
);
|
||||
}
|
||||
|
||||
public mapToPersistence(
|
||||
source: AuthenticatedUser,
|
||||
params?: MapperParamsType
|
||||
): AuthUserCreationAttributes {
|
||||
return {
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const authenticatedUserMapper: IAuthenticatedUserMapper = new AuthenticatedUserMapper();
|
||||
export { authenticatedUserMapper };
|
||||
@ -1,3 +0,0 @@
|
||||
export * from "./authenticated-user.mapper";
|
||||
export * from "./tab-context.mapper";
|
||||
export * from "./user.mapper";
|
||||
@ -1,66 +0,0 @@
|
||||
import { UniqueID } from "@/core/common/domain";
|
||||
import {
|
||||
type ISequelizeMapper,
|
||||
type MapperParamsType,
|
||||
SequelizeMapper,
|
||||
} from "@/core/common/infrastructure";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { TabContext } from "../../domain";
|
||||
import { TabContextCreationAttributes, TabContextModel } from "../sequelize";
|
||||
|
||||
export interface ITabContextMapper
|
||||
extends ISequelizeMapper<TabContextModel, TabContextCreationAttributes, TabContext> {}
|
||||
|
||||
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
|
||||
const uniqueIdResult = UniqueID.create(source.id);
|
||||
const tabIdResult = UniqueID.create(source.tab_id);
|
||||
const userIdResult = UniqueID.create(source.user_id);
|
||||
//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
|
||||
const okOrError = Result.combine([
|
||||
uniqueIdResult,
|
||||
tabIdResult,
|
||||
userIdResult,
|
||||
//companyIdResult,
|
||||
//brachIdResult,
|
||||
]);
|
||||
if (okOrError.isFailure) {
|
||||
return Result.fail(okOrError.error.message);
|
||||
}
|
||||
|
||||
// Crear el agregado de dominio
|
||||
return TabContext.create(
|
||||
{
|
||||
tabId: tabIdResult.data!,
|
||||
userId: userIdResult.data!,
|
||||
//companyId: companyIdResult.data,
|
||||
//branchId: brachIdResult.data,
|
||||
},
|
||||
uniqueIdResult.data!
|
||||
);
|
||||
}
|
||||
|
||||
public mapToPersistence(
|
||||
source: TabContext,
|
||||
params?: MapperParamsType
|
||||
): TabContextCreationAttributes {
|
||||
return {
|
||||
id: source.id.toString(),
|
||||
tab_id: source.tabId.toString(),
|
||||
user_id: source.userId.toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const tabContextMapper: ITabContextMapper = new TabContextMapper();
|
||||
export { tabContextMapper };
|
||||
@ -1,52 +0,0 @@
|
||||
import { EmailAddress, UniqueID } from "@/core/common/domain";
|
||||
import {
|
||||
type ISequelizeMapper,
|
||||
type MapperParamsType,
|
||||
SequelizeMapper,
|
||||
} from "@/core/common/infrastructure/sequelize/sequelize-mapper";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { User, Username } from "../../domain";
|
||||
import { UserCreationAttributes, UserModel } from "../sequelize";
|
||||
|
||||
export interface IUserMapper extends ISequelizeMapper<UserModel, UserCreationAttributes, User> {}
|
||||
|
||||
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
|
||||
const uniqueIdResult = UniqueID.create(source.id);
|
||||
const usernameResult = Username.create(source.username);
|
||||
const emailResult = EmailAddress.create(source.email);
|
||||
|
||||
// Validar que no haya errores en la creación de los Value Objects
|
||||
const okOrError = Result.combine([uniqueIdResult, usernameResult, emailResult]);
|
||||
if (okOrError.isFailure) {
|
||||
return Result.fail(okOrError.error.message);
|
||||
}
|
||||
|
||||
// Crear el agregado de dominio
|
||||
return User.create(
|
||||
{
|
||||
username: usernameResult.data!,
|
||||
email: emailResult.data!,
|
||||
roles: [],
|
||||
//roles: entity.roles || [],
|
||||
},
|
||||
uniqueIdResult.data!
|
||||
);
|
||||
}
|
||||
|
||||
public mapToPersistence(source: User, params?: MapperParamsType): UserCreationAttributes {
|
||||
return {
|
||||
id: source.id.toString(),
|
||||
username: source.username.toString(),
|
||||
email: source.email.toString(),
|
||||
//roles: source.getRoles().map((role) => role.toString()),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const userMapper: IUserMapper = new UserMapper();
|
||||
export { userMapper };
|
||||
@ -1,2 +0,0 @@
|
||||
export * from "./passport-auth.middleware";
|
||||
export * from "./tab-context.middleware";
|
||||
@ -1,72 +0,0 @@
|
||||
//import { authProvider } from "@/contexts/auth/infraestructure";
|
||||
import type { NextFunction, Response } from "express";
|
||||
|
||||
import { UniqueID } from "@/core/common/domain";
|
||||
import { ApiError, ExpressController } from "@/core/common/presentation";
|
||||
|
||||
import { authProvider } from "../../../../../../../modules/auth/src/api/lib/passport";
|
||||
import type { AuthenticatedUser } from "../../domain";
|
||||
import type { AuthenticatedRequest } from "../express/types";
|
||||
|
||||
// Comprueba el rol del usuario
|
||||
const _authorizeUser = (condition: (user: AuthenticatedUser) => boolean) => {
|
||||
return (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
|
||||
const user = req.user as AuthenticatedUser;
|
||||
|
||||
if (!(user && condition(user))) {
|
||||
return ExpressController.errorResponse(
|
||||
new ApiError({
|
||||
status: 401,
|
||||
title: "Unauthorized",
|
||||
detail: "You are not authorized to access this resource.",
|
||||
}),
|
||||
res
|
||||
);
|
||||
}
|
||||
|
||||
return next();
|
||||
};
|
||||
};
|
||||
|
||||
// Verifica que el usuario esté autenticado
|
||||
export const checkUser = [
|
||||
authProvider.authenticateJWT(),
|
||||
_authorizeUser((user) => true /*user.isUser*/),
|
||||
];
|
||||
|
||||
// Verifica que el usuario sea administrador
|
||||
export const checkUserIsAdmin = [
|
||||
authProvider.authenticateJWT(),
|
||||
_authorizeUser((user) => true /*user.isAdmin*/),
|
||||
];
|
||||
|
||||
// Middleware para verificar que el usuario sea administrador o el dueño de los datos (self)
|
||||
export const checkUserIsAdminOrOwner = [
|
||||
(req: AuthenticatedRequest, res: Response, next: NextFunction) => {
|
||||
const user = req.user as AuthenticatedUser;
|
||||
const { userId } = req.params;
|
||||
|
||||
// Si el usuario es admin, está autorizado
|
||||
if (user.isAdmin) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// Si el usuario es sí mismo
|
||||
if (user.isUser && userId) {
|
||||
const paramIdOrError = UniqueID.create(userId);
|
||||
if (paramIdOrError.isSuccess && user.id.equals(paramIdOrError.data)) {
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
return ExpressController.errorResponse(
|
||||
new ApiError({
|
||||
status: 401,
|
||||
title: "Unauthorized",
|
||||
detail: "You are not authorized to access this resource.",
|
||||
}),
|
||||
req,
|
||||
res
|
||||
);
|
||||
},
|
||||
];
|
||||
@ -1,3 +0,0 @@
|
||||
import { authProvider } from "../../../../../../../modules/auth/src/api/lib/passport";
|
||||
|
||||
export const checkTabContext = [authProvider.authenticateTabId()];
|
||||
@ -1,74 +0,0 @@
|
||||
import { DataTypes, InferAttributes, InferCreationAttributes, Model, Sequelize } from "sequelize";
|
||||
|
||||
export type AuthUserCreationAttributes = InferCreationAttributes<AuthUserModel>;
|
||||
|
||||
export class AuthUserModel extends Model<
|
||||
InferAttributes<AuthUserModel>,
|
||||
InferCreationAttributes<AuthUserModel>
|
||||
> {
|
||||
// To avoid table creation
|
||||
/*static async sync(): Promise<any> {
|
||||
return Promise.resolve();
|
||||
}*/
|
||||
|
||||
declare id: string;
|
||||
declare username: string;
|
||||
declare email: string;
|
||||
declare hash_password: string;
|
||||
declare roles: string[];
|
||||
}
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
AuthUserModel.init(
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
primaryKey: true,
|
||||
},
|
||||
username: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
email: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
hash_password: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
roles: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
defaultValue: "USER",
|
||||
get(this: AuthUserModel): string[] {
|
||||
const rawValue = this.getDataValue("roles") as any;
|
||||
return String(rawValue).split(";");
|
||||
},
|
||||
set(this: AuthUserModel, value: string[]) {
|
||||
const rawValue = value.join(";") as any;
|
||||
this.setDataValue("roles", rawValue);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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 }],
|
||||
|
||||
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
|
||||
|
||||
defaultScope: {},
|
||||
|
||||
scopes: {},
|
||||
}
|
||||
);
|
||||
return AuthUserModel;
|
||||
};
|
||||
@ -1,86 +0,0 @@
|
||||
import { EmailAddress } from "@/core/common/domain";
|
||||
import { SequelizeRepository } from "@/core/common/infrastructure";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { Sequelize, Transaction } from "sequelize";
|
||||
import { AuthenticatedUser, IAuthenticatedUserRepository, Username } from "../../domain";
|
||||
import { IAuthenticatedUserMapper } from "../mappers";
|
||||
import { AuthUserModel } from "./auth-user.model";
|
||||
|
||||
export class AuthenticatedUserRepository
|
||||
extends SequelizeRepository<AuthenticatedUser>
|
||||
implements IAuthenticatedUserRepository
|
||||
{
|
||||
private readonly _mapper!: IAuthenticatedUserMapper;
|
||||
|
||||
/**
|
||||
* 🔹 Función personalizada para mapear errores de unicidad en autenticación
|
||||
*/
|
||||
private _customErrorMapper(error: Error): string | null {
|
||||
if (error.name === "SequelizeUniqueConstraintError") {
|
||||
return "User with this email or username already exists";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
constructor(database: Sequelize, mapper: IAuthenticatedUserMapper) {
|
||||
super(database);
|
||||
this._mapper = mapper;
|
||||
}
|
||||
|
||||
async userExists(
|
||||
username: Username,
|
||||
email: EmailAddress,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<boolean, Error>> {
|
||||
try {
|
||||
const userWithEmail = await this._findById(
|
||||
AuthUserModel,
|
||||
"email",
|
||||
email.toString(),
|
||||
transaction
|
||||
);
|
||||
|
||||
const userWithUsername = await this._findById(
|
||||
AuthUserModel,
|
||||
"username",
|
||||
username.toString(),
|
||||
transaction
|
||||
);
|
||||
|
||||
return Result.ok(Boolean(userWithEmail || userWithUsername));
|
||||
} catch (error: unknown) {
|
||||
return this._handleDatabaseError(error, this._customErrorMapper);
|
||||
}
|
||||
}
|
||||
|
||||
async getUserByEmail(
|
||||
email: EmailAddress,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<AuthenticatedUser, Error>> {
|
||||
try {
|
||||
const rawUser = await this._getBy(AuthUserModel, "email", email.toString(), {}, transaction);
|
||||
|
||||
if (!rawUser === true) {
|
||||
return Result.fail(new Error("User with email not exists"));
|
||||
}
|
||||
|
||||
return this._mapper.mapToDomain(rawUser);
|
||||
} catch (error: unknown) {
|
||||
return this._handleDatabaseError(error, this._customErrorMapper);
|
||||
}
|
||||
}
|
||||
|
||||
async createUser(
|
||||
user: AuthenticatedUser,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<void, Error>> {
|
||||
try {
|
||||
const persistenceData = this._mapper.mapToPersistence(user);
|
||||
await AuthUserModel.create(persistenceData, { transaction });
|
||||
return Result.ok();
|
||||
} catch (error: unknown) {
|
||||
return this._handleDatabaseError(error, this._customErrorMapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
export * from "./auth-user.model";
|
||||
export * from "./authenticated-user.repository";
|
||||
|
||||
export * from "./tab-context.model";
|
||||
export * from "./tab-context.repository";
|
||||
|
||||
export * from "./user.model";
|
||||
export * from "./user.repository";
|
||||
@ -1,58 +0,0 @@
|
||||
import { DataTypes, InferAttributes, InferCreationAttributes, Model, Sequelize } from "sequelize";
|
||||
|
||||
export type TabContextCreationAttributes = InferCreationAttributes<TabContextModel>;
|
||||
|
||||
export class TabContextModel extends Model<
|
||||
InferAttributes<TabContextModel>,
|
||||
InferCreationAttributes<TabContextModel>
|
||||
> {
|
||||
// To avoid table creation
|
||||
/*static async sync(): Promise<any> {
|
||||
return Promise.resolve();
|
||||
}*/
|
||||
static associate(connection: Sequelize) {
|
||||
const { AuthUserModel } = connection.models;
|
||||
}
|
||||
|
||||
declare id: string;
|
||||
declare tab_id: string;
|
||||
declare user_id: string;
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
{
|
||||
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;
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user