From 5a9e7261f97fe2700bb20c44fe3794d43a00c36c Mon Sep 17 00:00:00 2001 From: david Date: Tue, 25 Feb 2025 18:47:42 +0100 Subject: [PATCH] . --- .../contexts/accounts/application/index.ts | 1 + .../application/list-accounts/index.ts | 1 + .../list-accounts/list-accounts.use-case.ts | 17 ++ .../domain/aggregates/account.ts} | 22 +-- .../accounts/domain/aggregates/index.ts | 1 + .../src/contexts/accounts/domain/index.ts | 3 + .../company-repository.interface.ts | 9 + .../accounts/domain/repositories/index.ts | 1 + .../services/company-service.interface.ts | 8 + .../domain/services/company.service.ts | 23 +++ .../infraestructure/index.ts | 0 .../mappers/account.mapper.ts} | 26 +-- .../accounts/infraestructure/mappers/index.ts | 1 + .../sequelize/account.model.ts} | 14 +- .../sequelize/account.repository.ts | 82 +++++++++ .../infraestructure/sequelize/index.ts | 8 + .../presentation/controllers/index.ts | 1 + .../controllers/list-accounts/index.ts | 16 ++ .../list-accounts/list-accounts.controller.ts | 37 ++++ .../list-accounts/list-accounts.presenter.ts | 38 ++++ .../presentation/dto/accounts.request.dto.ts | 1 + .../dto/accounts.response.dto.ts} | 2 +- .../dto/accounts.validation.dto.ts | 3 + .../accounts/presentation/dto/index.ts | 3 + .../presentation/index.ts | 0 .../contexts/companies/application/index.ts | 1 - .../application/list-companies/index.ts | 1 - .../list-companies/list-companies.use-case.ts | 17 -- .../companies/domain/aggregates/index.ts | 1 - .../src/contexts/companies/domain/index.ts | 5 - .../company-repository.interface.ts | 9 - .../companies/domain/repositories/index.ts | 1 - .../services/company-service.interface.ts | 8 - .../domain/services/company.service.ts | 23 --- .../infraestructure/mappers/index.ts | 1 - .../infraestructure/sequelize/index.ts | 9 - .../presentation/controllers/index.ts | 1 - .../controllers/list-companies/index.ts | 16 -- .../list-companies.controller.ts | 37 ---- .../list-companies.presenter.ts | 38 ---- .../presentation/dto/companies.request.dto.ts | 1 - .../dto/companies.validation.dto.ts | 3 - .../companies/presentation/dto/index.ts | 3 - .../contexts/contacts/application/index.ts | 1 + .../application/list-contacts.use-case.ts | 16 ++ .../contacts/domain/aggregates/contact.ts | 130 +++++++++++++ .../contacts/domain/aggregates/index.ts | 1 + .../src/contexts/contacts/domain/index.ts | 3 + .../contact-repository.interface.ts | 9 + .../contacts/domain/repositories/index.ts | 1 + .../services/contact-service.interface.ts | 8 + .../domain/services/contact.service.ts | 24 +++ .../contacts/domain/services/index.ts | 2 + .../contacts/infraestructure/index.ts | 2 + .../infraestructure/mappers/contact.mapper.ts | 100 ++++++++++ .../contacts/infraestructure/mappers/index.ts | 1 + .../sequelize/contact.model.ts | 172 ++++++++++++++++++ .../sequelize/contact.repository.ts} | 50 ++--- .../infraestructure/sequelize/index.ts | 9 + .../presentation/controllers/index.ts | 1 + .../presentation/controllers/list/index.ts | 16 ++ .../list/list-contacts.controller.ts | 37 ++++ .../list/list-contacts.presenter.ts | 38 ++++ .../presentation/dto/contacts.request.dto.ts | 1 + .../presentation/dto/contacts.response.dto.ts | 27 +++ .../dto/contacts.validation.dto.ts | 3 + .../contacts/presentation/dto/index.ts | 3 + .../contexts/contacts/presentation/index.ts | 2 + ...companies.routes.ts => accounts.routes.ts} | 11 +- apps/server/src/routes/v1.routes.ts | 4 +- 70 files changed, 926 insertions(+), 239 deletions(-) create mode 100644 apps/server/src/contexts/accounts/application/index.ts create mode 100644 apps/server/src/contexts/accounts/application/list-accounts/index.ts create mode 100644 apps/server/src/contexts/accounts/application/list-accounts/list-accounts.use-case.ts rename apps/server/src/contexts/{companies/domain/aggregates/company.ts => accounts/domain/aggregates/account.ts} (78%) create mode 100644 apps/server/src/contexts/accounts/domain/aggregates/index.ts create mode 100644 apps/server/src/contexts/accounts/domain/index.ts create mode 100644 apps/server/src/contexts/accounts/domain/repositories/company-repository.interface.ts create mode 100644 apps/server/src/contexts/accounts/domain/repositories/index.ts create mode 100644 apps/server/src/contexts/accounts/domain/services/company-service.interface.ts create mode 100644 apps/server/src/contexts/accounts/domain/services/company.service.ts rename apps/server/src/contexts/{companies => accounts}/infraestructure/index.ts (100%) rename apps/server/src/contexts/{companies/infraestructure/mappers/company.mapper.ts => accounts/infraestructure/mappers/account.mapper.ts} (79%) create mode 100644 apps/server/src/contexts/accounts/infraestructure/mappers/index.ts rename apps/server/src/contexts/{companies/infraestructure/sequelize/company.model.ts => accounts/infraestructure/sequelize/account.model.ts} (92%) create mode 100644 apps/server/src/contexts/accounts/infraestructure/sequelize/account.repository.ts create mode 100644 apps/server/src/contexts/accounts/infraestructure/sequelize/index.ts create mode 100644 apps/server/src/contexts/accounts/presentation/controllers/index.ts create mode 100644 apps/server/src/contexts/accounts/presentation/controllers/list-accounts/index.ts create mode 100644 apps/server/src/contexts/accounts/presentation/controllers/list-accounts/list-accounts.controller.ts create mode 100644 apps/server/src/contexts/accounts/presentation/controllers/list-accounts/list-accounts.presenter.ts create mode 100644 apps/server/src/contexts/accounts/presentation/dto/accounts.request.dto.ts rename apps/server/src/contexts/{companies/presentation/dto/companies.response.dto.ts => accounts/presentation/dto/accounts.response.dto.ts} (89%) create mode 100644 apps/server/src/contexts/accounts/presentation/dto/accounts.validation.dto.ts create mode 100644 apps/server/src/contexts/accounts/presentation/dto/index.ts rename apps/server/src/contexts/{companies => accounts}/presentation/index.ts (100%) delete mode 100644 apps/server/src/contexts/companies/application/index.ts delete mode 100644 apps/server/src/contexts/companies/application/list-companies/index.ts delete mode 100644 apps/server/src/contexts/companies/application/list-companies/list-companies.use-case.ts delete mode 100644 apps/server/src/contexts/companies/domain/aggregates/index.ts delete mode 100644 apps/server/src/contexts/companies/domain/index.ts delete mode 100644 apps/server/src/contexts/companies/domain/repositories/company-repository.interface.ts delete mode 100644 apps/server/src/contexts/companies/domain/repositories/index.ts delete mode 100644 apps/server/src/contexts/companies/domain/services/company-service.interface.ts delete mode 100644 apps/server/src/contexts/companies/domain/services/company.service.ts delete mode 100644 apps/server/src/contexts/companies/infraestructure/mappers/index.ts delete mode 100644 apps/server/src/contexts/companies/infraestructure/sequelize/index.ts delete mode 100644 apps/server/src/contexts/companies/presentation/controllers/index.ts delete mode 100644 apps/server/src/contexts/companies/presentation/controllers/list-companies/index.ts delete mode 100644 apps/server/src/contexts/companies/presentation/controllers/list-companies/list-companies.controller.ts delete mode 100644 apps/server/src/contexts/companies/presentation/controllers/list-companies/list-companies.presenter.ts delete mode 100644 apps/server/src/contexts/companies/presentation/dto/companies.request.dto.ts delete mode 100644 apps/server/src/contexts/companies/presentation/dto/companies.validation.dto.ts delete mode 100644 apps/server/src/contexts/companies/presentation/dto/index.ts create mode 100644 apps/server/src/contexts/contacts/application/index.ts create mode 100644 apps/server/src/contexts/contacts/application/list-contacts.use-case.ts create mode 100644 apps/server/src/contexts/contacts/domain/aggregates/contact.ts create mode 100644 apps/server/src/contexts/contacts/domain/aggregates/index.ts create mode 100644 apps/server/src/contexts/contacts/domain/index.ts create mode 100644 apps/server/src/contexts/contacts/domain/repositories/contact-repository.interface.ts create mode 100644 apps/server/src/contexts/contacts/domain/repositories/index.ts create mode 100644 apps/server/src/contexts/contacts/domain/services/contact-service.interface.ts create mode 100644 apps/server/src/contexts/contacts/domain/services/contact.service.ts create mode 100644 apps/server/src/contexts/contacts/domain/services/index.ts create mode 100644 apps/server/src/contexts/contacts/infraestructure/index.ts create mode 100644 apps/server/src/contexts/contacts/infraestructure/mappers/contact.mapper.ts create mode 100644 apps/server/src/contexts/contacts/infraestructure/mappers/index.ts create mode 100644 apps/server/src/contexts/contacts/infraestructure/sequelize/contact.model.ts rename apps/server/src/contexts/{companies/infraestructure/sequelize/company.repository.ts => contacts/infraestructure/sequelize/contact.repository.ts} (50%) create mode 100644 apps/server/src/contexts/contacts/infraestructure/sequelize/index.ts create mode 100644 apps/server/src/contexts/contacts/presentation/controllers/index.ts create mode 100644 apps/server/src/contexts/contacts/presentation/controllers/list/index.ts create mode 100644 apps/server/src/contexts/contacts/presentation/controllers/list/list-contacts.controller.ts create mode 100644 apps/server/src/contexts/contacts/presentation/controllers/list/list-contacts.presenter.ts create mode 100644 apps/server/src/contexts/contacts/presentation/dto/contacts.request.dto.ts create mode 100644 apps/server/src/contexts/contacts/presentation/dto/contacts.response.dto.ts create mode 100644 apps/server/src/contexts/contacts/presentation/dto/contacts.validation.dto.ts create mode 100644 apps/server/src/contexts/contacts/presentation/dto/index.ts create mode 100644 apps/server/src/contexts/contacts/presentation/index.ts rename apps/server/src/routes/{companies.routes.ts => accounts.routes.ts} (51%) diff --git a/apps/server/src/contexts/accounts/application/index.ts b/apps/server/src/contexts/accounts/application/index.ts new file mode 100644 index 00000000..a568d0fe --- /dev/null +++ b/apps/server/src/contexts/accounts/application/index.ts @@ -0,0 +1 @@ +export * from "./list-accounts"; diff --git a/apps/server/src/contexts/accounts/application/list-accounts/index.ts b/apps/server/src/contexts/accounts/application/list-accounts/index.ts new file mode 100644 index 00000000..4a1555cd --- /dev/null +++ b/apps/server/src/contexts/accounts/application/list-accounts/index.ts @@ -0,0 +1 @@ +export * from "./list-accounts.use-case"; diff --git a/apps/server/src/contexts/accounts/application/list-accounts/list-accounts.use-case.ts b/apps/server/src/contexts/accounts/application/list-accounts/list-accounts.use-case.ts new file mode 100644 index 00000000..fa603160 --- /dev/null +++ b/apps/server/src/contexts/accounts/application/list-accounts/list-accounts.use-case.ts @@ -0,0 +1,17 @@ +import { Collection, Result } from "@common/helpers"; +import { ITransactionManager } from "@common/infrastructure/database"; +import { Account } from "@contexts/accounts/domain"; +import { IAccountService } from "@contexts/accounts/domain/services/account-service.interface"; + +export class ListAccountsUseCase { + constructor( + private readonly accountService: IAccountService, + private readonly transactionManager: ITransactionManager + ) {} + + public execute(): Promise, Error>> { + return this.transactionManager.complete((transaction) => { + return this.accountService.findAccounts(transaction); + }); + } +} diff --git a/apps/server/src/contexts/companies/domain/aggregates/company.ts b/apps/server/src/contexts/accounts/domain/aggregates/account.ts similarity index 78% rename from apps/server/src/contexts/companies/domain/aggregates/company.ts rename to apps/server/src/contexts/accounts/domain/aggregates/account.ts index c11db356..49ab6249 100644 --- a/apps/server/src/contexts/companies/domain/aggregates/company.ts +++ b/apps/server/src/contexts/accounts/domain/aggregates/account.ts @@ -8,7 +8,7 @@ import { } from "@common/domain"; import { Maybe, Result } from "@common/helpers"; -export interface ICompanyProps { +export interface IAccountProps { isFreelancer: boolean; name: string; tin: TINNumber; @@ -27,7 +27,7 @@ export interface ICompanyProps { logo: Maybe; } -export interface ICompany { +export interface IAccount { id: UniqueID; name: string; tin: TINNumber; @@ -44,24 +44,24 @@ export interface ICompany { website: Maybe; logo: Maybe; - isCompany: boolean; + isAccount: boolean; isFreelancer: boolean; isActive: boolean; } -export class Company extends AggregateRoot implements ICompany { - static create(props: ICompanyProps, id?: UniqueID): Result { - const company = new Company(props, id); +export class Account extends AggregateRoot implements IAccount { + static create(props: IAccountProps, id?: UniqueID): Result { + const account = new Account(props, id); // Reglas de negocio / validaciones // ... // ... - // 馃敼 Disparar evento de dominio "CompanyAuthenticatedEvent" - //const { company } = props; - //user.addDomainEvent(new CompanyAuthenticatedEvent(id, company.toString())); + // 馃敼 Disparar evento de dominio "AccountAuthenticatedEvent" + //const { account } = props; + //user.addDomainEvent(new AccountAuthenticatedEvent(id, account.toString())); - return Result.ok(company); + return Result.ok(account); } get name() { @@ -116,7 +116,7 @@ export class Company extends AggregateRoot implements ICompany { return this.props.logo; } - get isCompany(): boolean { + get isAccount(): boolean { return !this.props.isFreelancer; } diff --git a/apps/server/src/contexts/accounts/domain/aggregates/index.ts b/apps/server/src/contexts/accounts/domain/aggregates/index.ts new file mode 100644 index 00000000..ed4079f3 --- /dev/null +++ b/apps/server/src/contexts/accounts/domain/aggregates/index.ts @@ -0,0 +1 @@ +export * from "./account"; diff --git a/apps/server/src/contexts/accounts/domain/index.ts b/apps/server/src/contexts/accounts/domain/index.ts new file mode 100644 index 00000000..160d6093 --- /dev/null +++ b/apps/server/src/contexts/accounts/domain/index.ts @@ -0,0 +1,3 @@ +export * from "./aggregates"; + +export * from "./repositories"; diff --git a/apps/server/src/contexts/accounts/domain/repositories/company-repository.interface.ts b/apps/server/src/contexts/accounts/domain/repositories/company-repository.interface.ts new file mode 100644 index 00000000..0ff7f330 --- /dev/null +++ b/apps/server/src/contexts/accounts/domain/repositories/company-repository.interface.ts @@ -0,0 +1,9 @@ +import { EmailAddress, UniqueID } from "@common/domain"; +import { Collection, Result } from "@common/helpers"; +import { Account } from "../aggregates"; + +export interface IAccountRepository { + findAll(transaction?: any): Promise, Error>>; + findById(id: UniqueID, transaction?: any): Promise>; + findByEmail(email: EmailAddress, transaction?: any): Promise>; +} diff --git a/apps/server/src/contexts/accounts/domain/repositories/index.ts b/apps/server/src/contexts/accounts/domain/repositories/index.ts new file mode 100644 index 00000000..f0804e69 --- /dev/null +++ b/apps/server/src/contexts/accounts/domain/repositories/index.ts @@ -0,0 +1 @@ +export * from "./account-repository.interface"; diff --git a/apps/server/src/contexts/accounts/domain/services/company-service.interface.ts b/apps/server/src/contexts/accounts/domain/services/company-service.interface.ts new file mode 100644 index 00000000..a27c487a --- /dev/null +++ b/apps/server/src/contexts/accounts/domain/services/company-service.interface.ts @@ -0,0 +1,8 @@ +import { UniqueID } from "@common/domain"; +import { Collection, Result } from "@common/helpers"; +import { Account } from "../aggregates"; + +export interface IAccountService { + findAccounts(transaction?: any): Promise, Error>>; + findAccountById(userId: UniqueID, transaction?: any): Promise>; +} diff --git a/apps/server/src/contexts/accounts/domain/services/company.service.ts b/apps/server/src/contexts/accounts/domain/services/company.service.ts new file mode 100644 index 00000000..4f006785 --- /dev/null +++ b/apps/server/src/contexts/accounts/domain/services/company.service.ts @@ -0,0 +1,23 @@ +import { UniqueID } from "@common/domain"; +import { Collection, Result } from "@common/helpers"; +import { Account, IAccountRepository } from ".."; +import { IAccountService } from "./account-service.interface"; + +export class AccountService implements IAccountService { + constructor(private readonly accountRepository: IAccountRepository) {} + + async findAccounts(transaction?: any): Promise, Error>> { + const accountsOrError = await this.accountRepository.findAll(transaction); + if (accountsOrError.isFailure) { + return Result.fail(accountsOrError.error); + } + + // Solo devolver usuarios activos + const activeAccounts = accountsOrError.data.filter((account) => account.isActive); + return Result.ok(new Collection(activeAccounts)); + } + + async findAccountById(accountId: UniqueID, transaction?: any): Promise> { + return await this.accountRepository.findById(accountId, transaction); + } +} diff --git a/apps/server/src/contexts/companies/infraestructure/index.ts b/apps/server/src/contexts/accounts/infraestructure/index.ts similarity index 100% rename from apps/server/src/contexts/companies/infraestructure/index.ts rename to apps/server/src/contexts/accounts/infraestructure/index.ts diff --git a/apps/server/src/contexts/companies/infraestructure/mappers/company.mapper.ts b/apps/server/src/contexts/accounts/infraestructure/mappers/account.mapper.ts similarity index 79% rename from apps/server/src/contexts/companies/infraestructure/mappers/company.mapper.ts rename to apps/server/src/contexts/accounts/infraestructure/mappers/account.mapper.ts index eb1c463f..cb330243 100644 --- a/apps/server/src/contexts/companies/infraestructure/mappers/company.mapper.ts +++ b/apps/server/src/contexts/accounts/infraestructure/mappers/account.mapper.ts @@ -5,17 +5,17 @@ import { MapperParamsType, SequelizeMapper, } from "@common/infrastructure/sequelize/sequelize-mapper"; -import { Company } from "@contexts/companies/domain/aggregates/company"; -import { CompanyCreationAttributes, CompanyModel } from "../sequelize/company.model"; +import { Account } from "@contexts/accounts/domain/"; +import { AccountCreationAttributes, AccountModel } from "../sequelize/account.model"; -export interface ICompanyMapper - extends ISequelizeMapper {} +export interface IAccountMapper + extends ISequelizeMapper {} -export class CompanyMapper - extends SequelizeMapper - implements ICompanyMapper +export class AccountMapper + extends SequelizeMapper + implements IAccountMapper { - public mapToDomain(source: CompanyModel, params?: MapperParamsType): Result { + public mapToDomain(source: AccountModel, params?: MapperParamsType): Result { const idOrError = UniqueID.create(source.id); const tinOrError = TINNumber.create(source.tin); const emailOrError = EmailAddress.create(source.email); @@ -42,7 +42,7 @@ export class CompanyMapper return Result.fail(result.error); } - return Company.create( + return Account.create( { isFreelancer: source.is_freelancer, name: source.name, @@ -65,9 +65,9 @@ export class CompanyMapper } public mapToPersistence( - source: Company, + source: Account, params?: MapperParamsType - ): Result { + ): Result { return Result.ok({ id: source.id.toString(), is_freelancer: source.isFreelancer, @@ -96,5 +96,5 @@ export class CompanyMapper } } -const companyMapper: CompanyMapper = new CompanyMapper(); -export { companyMapper }; +const accountMapper: AccountMapper = new AccountMapper(); +export { accountMapper }; diff --git a/apps/server/src/contexts/accounts/infraestructure/mappers/index.ts b/apps/server/src/contexts/accounts/infraestructure/mappers/index.ts new file mode 100644 index 00000000..b32ce87d --- /dev/null +++ b/apps/server/src/contexts/accounts/infraestructure/mappers/index.ts @@ -0,0 +1 @@ +export * from "./account.mapper"; diff --git a/apps/server/src/contexts/companies/infraestructure/sequelize/company.model.ts b/apps/server/src/contexts/accounts/infraestructure/sequelize/account.model.ts similarity index 92% rename from apps/server/src/contexts/companies/infraestructure/sequelize/company.model.ts rename to apps/server/src/contexts/accounts/infraestructure/sequelize/account.model.ts index e4a6c263..ea5d4e42 100644 --- a/apps/server/src/contexts/companies/infraestructure/sequelize/company.model.ts +++ b/apps/server/src/contexts/accounts/infraestructure/sequelize/account.model.ts @@ -7,11 +7,11 @@ import { Sequelize, } from "sequelize"; -export type CompanyCreationAttributes = InferCreationAttributes & {}; +export type AccountCreationAttributes = InferCreationAttributes & {}; -export class CompanyModel extends Model< - InferAttributes, - InferCreationAttributes +export class AccountModel extends Model< + InferAttributes, + InferCreationAttributes > { // To avoid table creation /*static async sync(): Promise { @@ -46,7 +46,7 @@ export class CompanyModel extends Model< } export default (sequelize: Sequelize) => { - CompanyModel.init( + AccountModel.init( { id: { type: DataTypes.UUID, @@ -148,7 +148,7 @@ export default (sequelize: Sequelize) => { }, { sequelize, - tableName: "companies", + tableName: "accounts", paranoid: true, // softs deletes timestamps: true, @@ -166,5 +166,5 @@ export default (sequelize: Sequelize) => { scopes: {}, } ); - return CompanyModel; + return AccountModel; }; diff --git a/apps/server/src/contexts/accounts/infraestructure/sequelize/account.repository.ts b/apps/server/src/contexts/accounts/infraestructure/sequelize/account.repository.ts new file mode 100644 index 00000000..8a608714 --- /dev/null +++ b/apps/server/src/contexts/accounts/infraestructure/sequelize/account.repository.ts @@ -0,0 +1,82 @@ +import { EmailAddress, UniqueID } from "@common/domain"; +import { Collection, Result } from "@common/helpers"; +import { SequelizeRepository } from "@common/infrastructure"; +import { Account } from "@contexts/accounts/domain"; +import { IAccountRepository } from "@contexts/accounts/domain/repositories/company-repository.interface"; +import { Transaction } from "sequelize"; +import { accountMapper, IAccountMapper } from "../mappers/account.mapper"; +import { AccountModel } from "./account.model"; + +class AccountRepository extends SequelizeRepository 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 findAll(transaction?: Transaction): Promise, 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> { + 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> { + 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); + } + } +} + +const accountRepository = new AccountRepository(accountMapper); +export { accountRepository }; diff --git a/apps/server/src/contexts/accounts/infraestructure/sequelize/index.ts b/apps/server/src/contexts/accounts/infraestructure/sequelize/index.ts new file mode 100644 index 00000000..40efc27d --- /dev/null +++ b/apps/server/src/contexts/accounts/infraestructure/sequelize/index.ts @@ -0,0 +1,8 @@ +import { IAccountRepository } from "@contexts/accounts/domain/repositories/company-repository.interface"; +import { accountRepository } from "./account.repository"; + +export * from "./account.model"; + +export const createAccountRepository = (): IAccountRepository => { + return accountRepository; +}; diff --git a/apps/server/src/contexts/accounts/presentation/controllers/index.ts b/apps/server/src/contexts/accounts/presentation/controllers/index.ts new file mode 100644 index 00000000..a568d0fe --- /dev/null +++ b/apps/server/src/contexts/accounts/presentation/controllers/index.ts @@ -0,0 +1 @@ +export * from "./list-accounts"; diff --git a/apps/server/src/contexts/accounts/presentation/controllers/list-accounts/index.ts b/apps/server/src/contexts/accounts/presentation/controllers/list-accounts/index.ts new file mode 100644 index 00000000..149ee8f7 --- /dev/null +++ b/apps/server/src/contexts/accounts/presentation/controllers/list-accounts/index.ts @@ -0,0 +1,16 @@ +import { SequelizeTransactionManager } from "@common/infrastructure"; +import { ListAccountsUseCase } from "@contexts/accounts/application/list-accounts/list-accounts.use-case"; +import { AccountService } from "@contexts/accounts/domain/services/company.service"; +import { accountRepository } from "@contexts/accounts/infraestructure/sequelize/account.repository"; +import { ListAccountsController } from "./list-accounts.controller"; +import { listAccountsPresenter } from "./list-accounts.presenter"; + +export const listAccountsController = () => { + const transactionManager = new SequelizeTransactionManager(); + const accountService = new AccountService(accountRepository); + + const useCase = new ListAccountsUseCase(accountService, transactionManager); + const presenter = listAccountsPresenter; + + return new ListAccountsController(useCase, presenter); +}; diff --git a/apps/server/src/contexts/accounts/presentation/controllers/list-accounts/list-accounts.controller.ts b/apps/server/src/contexts/accounts/presentation/controllers/list-accounts/list-accounts.controller.ts new file mode 100644 index 00000000..281a796b --- /dev/null +++ b/apps/server/src/contexts/accounts/presentation/controllers/list-accounts/list-accounts.controller.ts @@ -0,0 +1,37 @@ +import { ExpressController } from "@common/presentation"; +import { ListAccountsUseCase } from "@contexts/accounts/application/list-accounts/list-accounts.use-case"; +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); + } +} diff --git a/apps/server/src/contexts/accounts/presentation/controllers/list-accounts/list-accounts.presenter.ts b/apps/server/src/contexts/accounts/presentation/controllers/list-accounts/list-accounts.presenter.ts new file mode 100644 index 00000000..a13e8324 --- /dev/null +++ b/apps/server/src/contexts/accounts/presentation/controllers/list-accounts/list-accounts.presenter.ts @@ -0,0 +1,38 @@ +import { Collection, ensureBoolean, ensureNumber, ensureString } from "@common/helpers"; +import { Account } from "@contexts/accounts/domain"; +import { IListAccountsResponseDTO } from "../../dto"; + +export interface IListAccountsPresenter { + toDTO: (accounts: Collection) => IListAccountsResponseDTO[]; +} + +export const listAccountsPresenter: IListAccountsPresenter = { + toDTO: (accounts: Collection): IListAccountsResponseDTO[] => + accounts.map((account) => ({ + id: ensureString(account.id.toString()), + + is_freelancer: 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"), + lang_code: ensureString(account.langCode), + currency_code: ensureString(account.currencyCode), + logo: ensureString(account.logo.getOrUndefined()), + })), +}; diff --git a/apps/server/src/contexts/accounts/presentation/dto/accounts.request.dto.ts b/apps/server/src/contexts/accounts/presentation/dto/accounts.request.dto.ts new file mode 100644 index 00000000..96c10a0a --- /dev/null +++ b/apps/server/src/contexts/accounts/presentation/dto/accounts.request.dto.ts @@ -0,0 +1 @@ +export interface IListAccountsRequestDTO {} diff --git a/apps/server/src/contexts/companies/presentation/dto/companies.response.dto.ts b/apps/server/src/contexts/accounts/presentation/dto/accounts.response.dto.ts similarity index 89% rename from apps/server/src/contexts/companies/presentation/dto/companies.response.dto.ts rename to apps/server/src/contexts/accounts/presentation/dto/accounts.response.dto.ts index 1468a294..aa59d914 100644 --- a/apps/server/src/contexts/companies/presentation/dto/companies.response.dto.ts +++ b/apps/server/src/contexts/accounts/presentation/dto/accounts.response.dto.ts @@ -1,4 +1,4 @@ -export interface IListCompaniesResponseDTO { +export interface IListAccountsResponseDTO { id: string; is_freelancer: boolean; diff --git a/apps/server/src/contexts/accounts/presentation/dto/accounts.validation.dto.ts b/apps/server/src/contexts/accounts/presentation/dto/accounts.validation.dto.ts new file mode 100644 index 00000000..44c98e72 --- /dev/null +++ b/apps/server/src/contexts/accounts/presentation/dto/accounts.validation.dto.ts @@ -0,0 +1,3 @@ +import { z } from "zod"; + +export const ListAccountsSchema = z.object({}); diff --git a/apps/server/src/contexts/accounts/presentation/dto/index.ts b/apps/server/src/contexts/accounts/presentation/dto/index.ts new file mode 100644 index 00000000..1086720f --- /dev/null +++ b/apps/server/src/contexts/accounts/presentation/dto/index.ts @@ -0,0 +1,3 @@ +export * from "./accounts.request.dto"; +export * from "./accounts.response.dto"; +export * from "./accounts.validation.dto"; diff --git a/apps/server/src/contexts/companies/presentation/index.ts b/apps/server/src/contexts/accounts/presentation/index.ts similarity index 100% rename from apps/server/src/contexts/companies/presentation/index.ts rename to apps/server/src/contexts/accounts/presentation/index.ts diff --git a/apps/server/src/contexts/companies/application/index.ts b/apps/server/src/contexts/companies/application/index.ts deleted file mode 100644 index 943e2a00..00000000 --- a/apps/server/src/contexts/companies/application/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./list-companies"; diff --git a/apps/server/src/contexts/companies/application/list-companies/index.ts b/apps/server/src/contexts/companies/application/list-companies/index.ts deleted file mode 100644 index 39b989ec..00000000 --- a/apps/server/src/contexts/companies/application/list-companies/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./list-companies.use-case"; diff --git a/apps/server/src/contexts/companies/application/list-companies/list-companies.use-case.ts b/apps/server/src/contexts/companies/application/list-companies/list-companies.use-case.ts deleted file mode 100644 index c6daea47..00000000 --- a/apps/server/src/contexts/companies/application/list-companies/list-companies.use-case.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Collection, Result } from "@common/helpers"; -import { ITransactionManager } from "@common/infrastructure/database"; -import { Company } from "@contexts/companies/domain"; -import { ICompanyService } from "@contexts/companies/domain/services/company-service.interface"; - -export class ListCompaniesUseCase { - constructor( - private readonly companyService: ICompanyService, - private readonly transactionManager: ITransactionManager - ) {} - - public execute(): Promise, Error>> { - return this.transactionManager.complete((transaction) => { - return this.companyService.findCompanies(transaction); - }); - } -} diff --git a/apps/server/src/contexts/companies/domain/aggregates/index.ts b/apps/server/src/contexts/companies/domain/aggregates/index.ts deleted file mode 100644 index 2e80831a..00000000 --- a/apps/server/src/contexts/companies/domain/aggregates/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./company"; diff --git a/apps/server/src/contexts/companies/domain/index.ts b/apps/server/src/contexts/companies/domain/index.ts deleted file mode 100644 index 4a16e729..00000000 --- a/apps/server/src/contexts/companies/domain/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./aggregates"; -export * from "./entities"; -export * from "./events"; -export * from "./repositories"; -export * from "./value-objects"; diff --git a/apps/server/src/contexts/companies/domain/repositories/company-repository.interface.ts b/apps/server/src/contexts/companies/domain/repositories/company-repository.interface.ts deleted file mode 100644 index d13a05af..00000000 --- a/apps/server/src/contexts/companies/domain/repositories/company-repository.interface.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { EmailAddress, UniqueID } from "@common/domain"; -import { Collection, Result } from "@common/helpers"; -import { Company } from "../aggregates"; - -export interface ICompanyRepository { - findAll(transaction?: any): Promise, Error>>; - findById(id: UniqueID, transaction?: any): Promise>; - findByEmail(email: EmailAddress, transaction?: any): Promise>; -} diff --git a/apps/server/src/contexts/companies/domain/repositories/index.ts b/apps/server/src/contexts/companies/domain/repositories/index.ts deleted file mode 100644 index 4a2beff9..00000000 --- a/apps/server/src/contexts/companies/domain/repositories/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./company-repository.interface"; diff --git a/apps/server/src/contexts/companies/domain/services/company-service.interface.ts b/apps/server/src/contexts/companies/domain/services/company-service.interface.ts deleted file mode 100644 index b5032f40..00000000 --- a/apps/server/src/contexts/companies/domain/services/company-service.interface.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { UniqueID } from "@common/domain"; -import { Collection, Result } from "@common/helpers"; -import { Company } from "../aggregates"; - -export interface ICompanyService { - findCompanies(transaction?: any): Promise, Error>>; - findCompanyById(userId: UniqueID, transaction?: any): Promise>; -} diff --git a/apps/server/src/contexts/companies/domain/services/company.service.ts b/apps/server/src/contexts/companies/domain/services/company.service.ts deleted file mode 100644 index 6c1bced1..00000000 --- a/apps/server/src/contexts/companies/domain/services/company.service.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { UniqueID } from "@common/domain"; -import { Collection, Result } from "@common/helpers"; -import { Company, ICompanyRepository } from ".."; -import { ICompanyService } from "./company-service.interface"; - -export class CompanyService implements ICompanyService { - constructor(private readonly companyRepository: ICompanyRepository) {} - - async findCompanies(transaction?: any): Promise, Error>> { - const companysOrError = await this.companyRepository.findAll(transaction); - if (companysOrError.isFailure) { - return Result.fail(companysOrError.error); - } - - // Solo devolver usuarios activos - const activeCompanies = companysOrError.data.filter((company) => company.isActive); - return Result.ok(new Collection(activeCompanies)); - } - - async findCompanyById(companyId: UniqueID, transaction?: any): Promise> { - return await this.companyRepository.findById(companyId, transaction); - } -} diff --git a/apps/server/src/contexts/companies/infraestructure/mappers/index.ts b/apps/server/src/contexts/companies/infraestructure/mappers/index.ts deleted file mode 100644 index 7bf6f481..00000000 --- a/apps/server/src/contexts/companies/infraestructure/mappers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./company.mapper"; diff --git a/apps/server/src/contexts/companies/infraestructure/sequelize/index.ts b/apps/server/src/contexts/companies/infraestructure/sequelize/index.ts deleted file mode 100644 index 1d4e8eed..00000000 --- a/apps/server/src/contexts/companies/infraestructure/sequelize/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ICompanyRepository } from "@contexts/companies/domain"; -import { companyRepository } from "./company.repository"; - -export * from "./company.model"; -export * from "./company.repository"; - -export const createCompanyRepository = (): ICompanyRepository => { - return companyRepository; -}; diff --git a/apps/server/src/contexts/companies/presentation/controllers/index.ts b/apps/server/src/contexts/companies/presentation/controllers/index.ts deleted file mode 100644 index 943e2a00..00000000 --- a/apps/server/src/contexts/companies/presentation/controllers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./list-companies"; diff --git a/apps/server/src/contexts/companies/presentation/controllers/list-companies/index.ts b/apps/server/src/contexts/companies/presentation/controllers/list-companies/index.ts deleted file mode 100644 index cbe0301e..00000000 --- a/apps/server/src/contexts/companies/presentation/controllers/list-companies/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { SequelizeTransactionManager } from "@common/infrastructure"; -import { ListCompaniesUseCase } from "@contexts/companies/application/list-companies/list-companies.use-case"; -import { CompanyService } from "@contexts/companies/domain/services/company.service"; -import { companyRepository } from "@contexts/companies/infraestructure"; -import { ListCompaniesController } from "./list-companies.controller"; -import { listCompaniesPresenter } from "./list-companies.presenter"; - -export const listCompaniesController = () => { - const transactionManager = new SequelizeTransactionManager(); - const companyService = new CompanyService(companyRepository); - - const useCase = new ListCompaniesUseCase(companyService, transactionManager); - const presenter = listCompaniesPresenter; - - return new ListCompaniesController(useCase, presenter); -}; diff --git a/apps/server/src/contexts/companies/presentation/controllers/list-companies/list-companies.controller.ts b/apps/server/src/contexts/companies/presentation/controllers/list-companies/list-companies.controller.ts deleted file mode 100644 index d53369f7..00000000 --- a/apps/server/src/contexts/companies/presentation/controllers/list-companies/list-companies.controller.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { ExpressController } from "@common/presentation"; -import { ListCompaniesUseCase } from "@contexts/companies/application/list-companies/list-companies.use-case"; -import { IListCompaniesPresenter } from "./list-companies.presenter"; - -export class ListCompaniesController extends ExpressController { - public constructor( - private readonly listCompanies: ListCompaniesUseCase, - private readonly presenter: IListCompaniesPresenter - ) { - super(); - } - - protected async executeImpl() { - const companiesOrError = await this.listCompanies.execute(); - - if (companiesOrError.isFailure) { - return this.handleError(companiesOrError.error); - } - - return this.ok(this.presenter.toDTO(companiesOrError.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); - } -} diff --git a/apps/server/src/contexts/companies/presentation/controllers/list-companies/list-companies.presenter.ts b/apps/server/src/contexts/companies/presentation/controllers/list-companies/list-companies.presenter.ts deleted file mode 100644 index 7c70f667..00000000 --- a/apps/server/src/contexts/companies/presentation/controllers/list-companies/list-companies.presenter.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Collection, ensureBoolean, ensureNumber, ensureString } from "@common/helpers"; -import { Company } from "@contexts/companies/domain"; -import { IListCompaniesResponseDTO } from "../../dto"; - -export interface IListCompaniesPresenter { - toDTO: (companies: Collection) => IListCompaniesResponseDTO[]; -} - -export const listCompaniesPresenter: IListCompaniesPresenter = { - toDTO: (companies: Collection): IListCompaniesResponseDTO[] => - companies.map((company) => ({ - id: ensureString(company.id.toString()), - - is_freelancer: ensureBoolean(company.isFreelancer), - name: ensureString(company.name), - trade_name: ensureString(company.tradeName.getOrUndefined()), - tin: ensureString(company.tin.toString()), - - street: ensureString(company.address.street), - city: ensureString(company.address.city), - state: ensureString(company.address.state), - postal_code: ensureString(company.address.postalCode), - country: ensureString(company.address.country), - - email: ensureString(company.email.toString()), - phone: ensureString(company.phone.toString()), - fax: ensureString(company.fax.getOrUndefined()?.toString()), - website: ensureString(company.website.getOrUndefined()), - - legal_record: ensureString(company.legalRecord), - - default_tax: ensureNumber(company.defaultTax), - status: ensureString(company.isActive ? "active" : "inactive"), - lang_code: ensureString(company.langCode), - currency_code: ensureString(company.currencyCode), - logo: ensureString(company.logo.getOrUndefined()), - })), -}; diff --git a/apps/server/src/contexts/companies/presentation/dto/companies.request.dto.ts b/apps/server/src/contexts/companies/presentation/dto/companies.request.dto.ts deleted file mode 100644 index 8ae02aa2..00000000 --- a/apps/server/src/contexts/companies/presentation/dto/companies.request.dto.ts +++ /dev/null @@ -1 +0,0 @@ -export interface IListCompaniesRequestDTO {} diff --git a/apps/server/src/contexts/companies/presentation/dto/companies.validation.dto.ts b/apps/server/src/contexts/companies/presentation/dto/companies.validation.dto.ts deleted file mode 100644 index 54b1803a..00000000 --- a/apps/server/src/contexts/companies/presentation/dto/companies.validation.dto.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { z } from "zod"; - -export const ListCompaniesSchema = z.object({}); diff --git a/apps/server/src/contexts/companies/presentation/dto/index.ts b/apps/server/src/contexts/companies/presentation/dto/index.ts deleted file mode 100644 index 3109872c..00000000 --- a/apps/server/src/contexts/companies/presentation/dto/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./companies.request.dto"; -export * from "./companies.response.dto"; -export * from "./companies.validation.dto"; diff --git a/apps/server/src/contexts/contacts/application/index.ts b/apps/server/src/contexts/contacts/application/index.ts new file mode 100644 index 00000000..2a2e90ae --- /dev/null +++ b/apps/server/src/contexts/contacts/application/index.ts @@ -0,0 +1 @@ +export * from "./list-contacts.use-case"; diff --git a/apps/server/src/contexts/contacts/application/list-contacts.use-case.ts b/apps/server/src/contexts/contacts/application/list-contacts.use-case.ts new file mode 100644 index 00000000..e251cc66 --- /dev/null +++ b/apps/server/src/contexts/contacts/application/list-contacts.use-case.ts @@ -0,0 +1,16 @@ +import { Collection, Result } from "@common/helpers"; +import { ITransactionManager } from "@common/infrastructure/database"; +import { Contact, IContactService } from "../domain"; + +export class ListContactsUseCase { + constructor( + private readonly contactService: IContactService, + private readonly transactionManager: ITransactionManager + ) {} + + public execute(): Promise, Error>> { + return this.transactionManager.complete((transaction) => { + return this.contactService.findContact(transaction); + }); + } +} diff --git a/apps/server/src/contexts/contacts/domain/aggregates/contact.ts b/apps/server/src/contexts/contacts/domain/aggregates/contact.ts new file mode 100644 index 00000000..93186586 --- /dev/null +++ b/apps/server/src/contexts/contacts/domain/aggregates/contact.ts @@ -0,0 +1,130 @@ +import { + AggregateRoot, + EmailAddress, + PhoneNumber, + PostalAddress, + TINNumber, + UniqueID, +} from "@common/domain"; +import { Maybe, Result } from "@common/helpers"; + +export interface IContactProps { + reference: string; + isFreelancer: boolean; + name: string; + tin: TINNumber; + address: PostalAddress; + email: EmailAddress; + phone: PhoneNumber; + legalRecord: string; + defaultTax: number; + status: string; + langCode: string; + currencyCode: string; + + tradeName: Maybe; + website: Maybe; + fax: Maybe; +} + +export interface IContact { + id: UniqueID; + reference: string; + name: string; + tin: TINNumber; + address: PostalAddress; + email: EmailAddress; + phone: PhoneNumber; + legalRecord: string; + defaultTax: number; + langCode: string; + currencyCode: string; + + tradeName: Maybe; + fax: Maybe; + website: Maybe; + + isContact: boolean; + isFreelancer: boolean; + isActive: boolean; +} + +export class Contact extends AggregateRoot implements IContact { + static create(props: IContactProps, id?: UniqueID): Result { + const contact = new Contact(props, id); + + // Reglas de negocio / validaciones + // ... + // ... + + // 馃敼 Disparar evento de dominio "ContactAuthenticatedEvent" + //const { contact } = props; + //user.addDomainEvent(new ContactAuthenticatedEvent(id, contact.toString())); + + return Result.ok(contact); + } + + get reference() { + return this.props.reference; + } + + 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 { + 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 isContact(): boolean { + return !this.props.isFreelancer; + } + + get isFreelancer(): boolean { + return this.props.isFreelancer; + } + + get isActive(): boolean { + return this.props.status === "active"; + } +} diff --git a/apps/server/src/contexts/contacts/domain/aggregates/index.ts b/apps/server/src/contexts/contacts/domain/aggregates/index.ts new file mode 100644 index 00000000..d191830c --- /dev/null +++ b/apps/server/src/contexts/contacts/domain/aggregates/index.ts @@ -0,0 +1 @@ +export * from "./contact"; diff --git a/apps/server/src/contexts/contacts/domain/index.ts b/apps/server/src/contexts/contacts/domain/index.ts new file mode 100644 index 00000000..ef023faa --- /dev/null +++ b/apps/server/src/contexts/contacts/domain/index.ts @@ -0,0 +1,3 @@ +export * from "./aggregates"; +export * from "./repositories"; +export * from "./services"; diff --git a/apps/server/src/contexts/contacts/domain/repositories/contact-repository.interface.ts b/apps/server/src/contexts/contacts/domain/repositories/contact-repository.interface.ts new file mode 100644 index 00000000..d3c5a24e --- /dev/null +++ b/apps/server/src/contexts/contacts/domain/repositories/contact-repository.interface.ts @@ -0,0 +1,9 @@ +import { EmailAddress, UniqueID } from "@common/domain"; +import { Collection, Result } from "@common/helpers"; +import { Contact } from "../aggregates"; + +export interface IContactRepository { + findAll(transaction?: any): Promise, Error>>; + findById(id: UniqueID, transaction?: any): Promise>; + findByEmail(email: EmailAddress, transaction?: any): Promise>; +} diff --git a/apps/server/src/contexts/contacts/domain/repositories/index.ts b/apps/server/src/contexts/contacts/domain/repositories/index.ts new file mode 100644 index 00000000..fb499b12 --- /dev/null +++ b/apps/server/src/contexts/contacts/domain/repositories/index.ts @@ -0,0 +1 @@ +export * from "./contact-repository.interface"; diff --git a/apps/server/src/contexts/contacts/domain/services/contact-service.interface.ts b/apps/server/src/contexts/contacts/domain/services/contact-service.interface.ts new file mode 100644 index 00000000..ae61751f --- /dev/null +++ b/apps/server/src/contexts/contacts/domain/services/contact-service.interface.ts @@ -0,0 +1,8 @@ +import { UniqueID } from "@common/domain"; +import { Collection, Result } from "@common/helpers"; +import { Contact } from "../aggregates"; + +export interface IContactService { + findContact(transaction?: any): Promise, Error>>; + findContactById(contactId: UniqueID, transaction?: any): Promise>; +} diff --git a/apps/server/src/contexts/contacts/domain/services/contact.service.ts b/apps/server/src/contexts/contacts/domain/services/contact.service.ts new file mode 100644 index 00000000..f3ed1313 --- /dev/null +++ b/apps/server/src/contexts/contacts/domain/services/contact.service.ts @@ -0,0 +1,24 @@ +import { UniqueID } from "@common/domain"; +import { Collection, Result } from "@common/helpers"; +import { Contact } from "../aggregates"; +import { IContactRepository } from "../repositories"; +import { IContactService } from "./contact-service.interface"; + +export class ContactService implements IContactService { + constructor(private readonly contactRepository: IContactRepository) {} + + async findContact(transaction?: any): Promise, Error>> { + const contactsOrError = await this.contactRepository.findAll(transaction); + if (contactsOrError.isFailure) { + return Result.fail(contactsOrError.error); + } + + // Solo devolver usuarios activos + const activeContacts = contactsOrError.data.filter((contact) => contact.isActive); + return Result.ok(new Collection(activeContacts)); + } + + async findContactById(contactId: UniqueID, transaction?: any): Promise> { + return await this.contactRepository.findById(contactId, transaction); + } +} diff --git a/apps/server/src/contexts/contacts/domain/services/index.ts b/apps/server/src/contexts/contacts/domain/services/index.ts new file mode 100644 index 00000000..f029fdca --- /dev/null +++ b/apps/server/src/contexts/contacts/domain/services/index.ts @@ -0,0 +1,2 @@ +export * from "./contact-service.interface"; +export * from "./contact.service"; diff --git a/apps/server/src/contexts/contacts/infraestructure/index.ts b/apps/server/src/contexts/contacts/infraestructure/index.ts new file mode 100644 index 00000000..7ccefa3a --- /dev/null +++ b/apps/server/src/contexts/contacts/infraestructure/index.ts @@ -0,0 +1,2 @@ +export * from "./mappers"; +export * from "./sequelize"; diff --git a/apps/server/src/contexts/contacts/infraestructure/mappers/contact.mapper.ts b/apps/server/src/contexts/contacts/infraestructure/mappers/contact.mapper.ts new file mode 100644 index 00000000..2d9c7b1b --- /dev/null +++ b/apps/server/src/contexts/contacts/infraestructure/mappers/contact.mapper.ts @@ -0,0 +1,100 @@ +import { EmailAddress, PhoneNumber, PostalAddress, TINNumber, UniqueID } from "@common/domain"; +import { Maybe, Result } from "@common/helpers"; +import { + ISequelizeMapper, + MapperParamsType, + SequelizeMapper, +} from "@common/infrastructure/sequelize/sequelize-mapper"; +import { Contact } from "../../domain"; +import { ContactCreationAttributes, ContactModel } from "../sequelize/contact.model"; + +export interface IContactMapper + extends ISequelizeMapper {} + +export class ContactMapper + extends SequelizeMapper + implements IContactMapper +{ + public mapToDomain(source: ContactModel, params?: MapperParamsType): Result { + const idOrError = UniqueID.create(source.id); + 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, + tinOrError, + emailOrError, + phoneOrError, + faxOrError, + postalAddressOrError, + ]); + + if (result.isFailure) { + return Result.fail(result.error); + } + + return Contact.create( + { + isFreelancer: source.is_freelancer, + reference: source.reference, + 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, + status: source.status, + langCode: source.lang_code, + currencyCode: source.currency_code, + }, + idOrError.data + ); + } + + public mapToPersistence( + source: Contact, + params?: MapperParamsType + ): Result { + return Result.ok({ + id: source.id.toString(), + reference: source.reference, + is_freelancer: source.isFreelancer, + name: source.name, + trade_name: source.tradeName.getOrUndefined(), + tin: source.tin.toString(), + + street: source.address.street, + city: source.address.city, + state: source.address.state, + postal_code: source.address.postalCode, + country: source.address.country, + + email: source.email.toString(), + phone: source.phone.toString(), + fax: source.fax.isSome() ? source.fax.getOrUndefined()?.toString() : undefined, + website: source.website.getOrUndefined(), + + legal_record: source.legalRecord, + default_tax: source.defaultTax, + status: source.isActive ? "active" : "inactive", + lang_code: source.langCode, + currency_code: source.currencyCode, + }); + } +} + +const contactMapper: ContactMapper = new ContactMapper(); +export { contactMapper }; diff --git a/apps/server/src/contexts/contacts/infraestructure/mappers/index.ts b/apps/server/src/contexts/contacts/infraestructure/mappers/index.ts new file mode 100644 index 00000000..f2d160c4 --- /dev/null +++ b/apps/server/src/contexts/contacts/infraestructure/mappers/index.ts @@ -0,0 +1 @@ +export * from "./contact.mapper"; diff --git a/apps/server/src/contexts/contacts/infraestructure/sequelize/contact.model.ts b/apps/server/src/contexts/contacts/infraestructure/sequelize/contact.model.ts new file mode 100644 index 00000000..908a58b8 --- /dev/null +++ b/apps/server/src/contexts/contacts/infraestructure/sequelize/contact.model.ts @@ -0,0 +1,172 @@ +import { + CreationOptional, + DataTypes, + InferAttributes, + InferCreationAttributes, + Model, + Sequelize, +} from "sequelize"; + +export type ContactCreationAttributes = InferCreationAttributes & {}; + +export class ContactModel extends Model< + InferAttributes, + InferCreationAttributes +> { + // To avoid table creation + /*static async sync(): Promise { + return Promise.resolve(); + }*/ + + declare id: string; + declare reference: CreationOptional; + + declare is_freelancer: boolean; + declare name: string; + declare trade_name: CreationOptional; + 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; + declare website: CreationOptional; + + declare legal_record: string; + + declare default_tax: number; + declare status: string; + declare lang_code: string; + declare currency_code: string; +} + +export default (sequelize: Sequelize) => { + ContactModel.init( + { + id: { + type: DataTypes.UUID, + primaryKey: true, + }, + reference: { + type: DataTypes.STRING, + allowNull: false, + }, + is_freelancer: { + type: DataTypes.BOOLEAN, + allowNull: false, + }, + name: { + type: DataTypes.STRING, + allowNull: false, + }, + trade_name: { + type: DataTypes.STRING, + allowNull: true, + }, + 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, + }, + website: { + type: DataTypes.STRING, + allowNull: true, + validate: { + isUrl: true, + }, + }, + legal_record: { + type: DataTypes.TEXT, + allowNull: false, + }, + + default_tax: { + type: new DataTypes.SMALLINT(), + allowNull: false, + defaultValue: 2100, + }, + + lang_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: "contacts", + + paranoid: true, // softs deletes + timestamps: true, + + createdAt: "created_at", + updatedAt: "updated_at", + deletedAt: "deleted_at", + + indexes: [ + { name: "email_idx", fields: ["email"], unique: true }, + { name: "reference_idx", fields: ["reference"], unique: true }, + ], + + whereMergeStrategy: "and", // <- c贸mo tratar el merge de un scope + + defaultScope: {}, + + scopes: {}, + } + ); + return ContactModel; +}; diff --git a/apps/server/src/contexts/companies/infraestructure/sequelize/company.repository.ts b/apps/server/src/contexts/contacts/infraestructure/sequelize/contact.repository.ts similarity index 50% rename from apps/server/src/contexts/companies/infraestructure/sequelize/company.repository.ts rename to apps/server/src/contexts/contacts/infraestructure/sequelize/contact.repository.ts index 59230bdb..cfc96c67 100644 --- a/apps/server/src/contexts/companies/infraestructure/sequelize/company.repository.ts +++ b/apps/server/src/contexts/contacts/infraestructure/sequelize/contact.repository.ts @@ -1,53 +1,53 @@ import { EmailAddress, UniqueID } from "@common/domain"; import { Collection, Result } from "@common/helpers"; import { SequelizeRepository } from "@common/infrastructure"; -import { Company, ICompanyRepository } from "@contexts/companies/domain"; import { Transaction } from "sequelize"; -import { companyMapper, ICompanyMapper } from "../mappers"; -import { CompanyModel } from "./company.model"; +import { Contact, IContactRepository } from "../../domain"; +import { contactMapper, IContactMapper } from "../mappers"; +import { ContactModel } from "./contact.model"; -class CompanyRepository extends SequelizeRepository implements ICompanyRepository { - private readonly _mapper!: ICompanyMapper; +class ContactRepository extends SequelizeRepository implements IContactRepository { + private readonly _mapper!: IContactMapper; /** * 馃敼 Funci贸n personalizada para mapear errores de unicidad en autenticaci贸n */ private _customErrorMapper(error: Error): string | null { if (error.name === "SequelizeUniqueConstraintError") { - return "Company with this email already exists"; + return "Contact with this email already exists"; } return null; } - constructor(mapper: ICompanyMapper) { + constructor(mapper: IContactMapper) { super(); this._mapper = mapper; } - async findAll(transaction?: Transaction): Promise, Error>> { + async findAll(transaction?: Transaction): Promise, Error>> { try { - const rawCompanys: any = await this._findAll(CompanyModel, {}, transaction); + const rawContacts: any = await this._findAll(ContactModel, {}, transaction); - if (!rawCompanys === true) { - return Result.fail(new Error("Company with email not exists")); + if (!rawContacts === true) { + return Result.fail(new Error("Contact with email not exists")); } - return this._mapper.mapArrayToDomain(rawCompanys); + return this._mapper.mapArrayToDomain(rawContacts); } catch (error: any) { return this._handleDatabaseError(error, this._customErrorMapper); } } - async findById(id: UniqueID, transaction?: Transaction): Promise> { + async findById(id: UniqueID, transaction?: Transaction): Promise> { try { - const rawCompany: any = await this._getById(CompanyModel, id, {}, transaction); + const rawContact: any = await this._getById(ContactModel, id, {}, transaction); - if (!rawCompany === true) { - return Result.fail(new Error(`Company with id ${id.toString()} not exists`)); + if (!rawContact === true) { + return Result.fail(new Error(`Contact with id ${id.toString()} not exists`)); } - return this._mapper.mapToDomain(rawCompany); + return this._mapper.mapToDomain(rawContact); } catch (error: any) { return this._handleDatabaseError(error, this._customErrorMapper); } @@ -56,26 +56,26 @@ class CompanyRepository extends SequelizeRepository implements ICompany async findByEmail( email: EmailAddress, transaction?: Transaction - ): Promise> { + ): Promise> { try { - const rawCompany: any = await this._getBy( - CompanyModel, + const rawContact: any = await this._getBy( + ContactModel, "email", email.toString(), {}, transaction ); - if (!rawCompany === true) { - return Result.fail(new Error(`Company with email ${email.toString()} not exists`)); + if (!rawContact === true) { + return Result.fail(new Error(`Contact with email ${email.toString()} not exists`)); } - return this._mapper.mapToDomain(rawCompany); + return this._mapper.mapToDomain(rawContact); } catch (error: any) { return this._handleDatabaseError(error, this._customErrorMapper); } } } -const companyRepository = new CompanyRepository(companyMapper); -export { companyRepository }; +const contactRepository = new ContactRepository(contactMapper); +export { contactRepository }; diff --git a/apps/server/src/contexts/contacts/infraestructure/sequelize/index.ts b/apps/server/src/contexts/contacts/infraestructure/sequelize/index.ts new file mode 100644 index 00000000..09c1257f --- /dev/null +++ b/apps/server/src/contexts/contacts/infraestructure/sequelize/index.ts @@ -0,0 +1,9 @@ +import { IContactRepository } from "../../domain"; +import { contactRepository } from "./contact.repository"; + +export * from "./contact.model"; +export * from "./contact.repository"; + +export const createContactRepository = (): IContactRepository => { + return contactRepository; +}; diff --git a/apps/server/src/contexts/contacts/presentation/controllers/index.ts b/apps/server/src/contexts/contacts/presentation/controllers/index.ts new file mode 100644 index 00000000..491ccf0c --- /dev/null +++ b/apps/server/src/contexts/contacts/presentation/controllers/index.ts @@ -0,0 +1 @@ +export * from "./list"; diff --git a/apps/server/src/contexts/contacts/presentation/controllers/list/index.ts b/apps/server/src/contexts/contacts/presentation/controllers/list/index.ts new file mode 100644 index 00000000..23be9228 --- /dev/null +++ b/apps/server/src/contexts/contacts/presentation/controllers/list/index.ts @@ -0,0 +1,16 @@ +import { SequelizeTransactionManager } from "@common/infrastructure"; +import { ListContactsUseCase } from "../../../application"; +import { ContactService } from "../../../domain"; +import { contactRepository } from "../../../infraestructure"; +import { ListContactsController } from "./list-contacts.controller"; +import { listContactsPresenter } from "./list-contacts.presenter"; + +export const listContactsController = () => { + const transactionManager = new SequelizeTransactionManager(); + const contactService = new ContactService(contactRepository); + + const useCase = new ListContactsUseCase(contactService, transactionManager); + const presenter = listContactsPresenter; + + return new ListContactsController(useCase, presenter); +}; diff --git a/apps/server/src/contexts/contacts/presentation/controllers/list/list-contacts.controller.ts b/apps/server/src/contexts/contacts/presentation/controllers/list/list-contacts.controller.ts new file mode 100644 index 00000000..c67eb520 --- /dev/null +++ b/apps/server/src/contexts/contacts/presentation/controllers/list/list-contacts.controller.ts @@ -0,0 +1,37 @@ +import { ExpressController } from "@common/presentation"; +import { ListContactsUseCase } from "../../../application"; +import { IListContactsPresenter } from "./list-contacts.presenter"; + +export class ListContactsController extends ExpressController { + public constructor( + private readonly listContacts: ListContactsUseCase, + private readonly presenter: IListContactsPresenter + ) { + super(); + } + + protected async executeImpl() { + const contactsOrError = await this.listContacts.execute(); + + if (contactsOrError.isFailure) { + return this.handleError(contactsOrError.error); + } + + return this.ok(this.presenter.toDTO(contactsOrError.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); + } +} diff --git a/apps/server/src/contexts/contacts/presentation/controllers/list/list-contacts.presenter.ts b/apps/server/src/contexts/contacts/presentation/controllers/list/list-contacts.presenter.ts new file mode 100644 index 00000000..98e1b59e --- /dev/null +++ b/apps/server/src/contexts/contacts/presentation/controllers/list/list-contacts.presenter.ts @@ -0,0 +1,38 @@ +import { Collection, ensureBoolean, ensureNumber, ensureString } from "@common/helpers"; +import { Contact } from "../../../domain"; +import { IListContactsResponseDTO } from "../../dto"; + +export interface IListContactsPresenter { + toDTO: (contacts: Collection) => IListContactsResponseDTO[]; +} + +export const listContactsPresenter: IListContactsPresenter = { + toDTO: (contacts: Collection): IListContactsResponseDTO[] => + contacts.map((contact) => ({ + id: ensureString(contact.id.toString()), + reference: ensureString(contact.reference), + + is_freelancer: ensureBoolean(contact.isFreelancer), + name: ensureString(contact.name), + trade_name: ensureString(contact.tradeName.getOrUndefined()), + tin: ensureString(contact.tin.toString()), + + street: ensureString(contact.address.street), + city: ensureString(contact.address.city), + state: ensureString(contact.address.state), + postal_code: ensureString(contact.address.postalCode), + country: ensureString(contact.address.country), + + email: ensureString(contact.email.toString()), + phone: ensureString(contact.phone.toString()), + fax: ensureString(contact.fax.getOrUndefined()?.toString()), + website: ensureString(contact.website.getOrUndefined()), + + legal_record: ensureString(contact.legalRecord), + + default_tax: ensureNumber(contact.defaultTax), + status: ensureString(contact.isActive ? "active" : "inactive"), + lang_code: ensureString(contact.langCode), + currency_code: ensureString(contact.currencyCode), + })), +}; diff --git a/apps/server/src/contexts/contacts/presentation/dto/contacts.request.dto.ts b/apps/server/src/contexts/contacts/presentation/dto/contacts.request.dto.ts new file mode 100644 index 00000000..8fcb5080 --- /dev/null +++ b/apps/server/src/contexts/contacts/presentation/dto/contacts.request.dto.ts @@ -0,0 +1 @@ +export interface IListContactsRequestDTO {} diff --git a/apps/server/src/contexts/contacts/presentation/dto/contacts.response.dto.ts b/apps/server/src/contexts/contacts/presentation/dto/contacts.response.dto.ts new file mode 100644 index 00000000..c28cdaa0 --- /dev/null +++ b/apps/server/src/contexts/contacts/presentation/dto/contacts.response.dto.ts @@ -0,0 +1,27 @@ +export interface IListContactsResponseDTO { + id: string; + reference: string; + + is_freelancer: 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; + lang_code: string; + currency_code: string; +} diff --git a/apps/server/src/contexts/contacts/presentation/dto/contacts.validation.dto.ts b/apps/server/src/contexts/contacts/presentation/dto/contacts.validation.dto.ts new file mode 100644 index 00000000..c844dc9c --- /dev/null +++ b/apps/server/src/contexts/contacts/presentation/dto/contacts.validation.dto.ts @@ -0,0 +1,3 @@ +import { z } from "zod"; + +export const ListContactsSchema = z.object({}); diff --git a/apps/server/src/contexts/contacts/presentation/dto/index.ts b/apps/server/src/contexts/contacts/presentation/dto/index.ts new file mode 100644 index 00000000..905cc636 --- /dev/null +++ b/apps/server/src/contexts/contacts/presentation/dto/index.ts @@ -0,0 +1,3 @@ +export * from "./contacts.request.dto"; +export * from "./contacts.response.dto"; +export * from "./contacts.validation.dto"; diff --git a/apps/server/src/contexts/contacts/presentation/index.ts b/apps/server/src/contexts/contacts/presentation/index.ts new file mode 100644 index 00000000..a123289d --- /dev/null +++ b/apps/server/src/contexts/contacts/presentation/index.ts @@ -0,0 +1,2 @@ +export * from "./controllers"; +export * from "./dto"; diff --git a/apps/server/src/routes/companies.routes.ts b/apps/server/src/routes/accounts.routes.ts similarity index 51% rename from apps/server/src/routes/companies.routes.ts rename to apps/server/src/routes/accounts.routes.ts index 6d8149e0..f98dfc79 100644 --- a/apps/server/src/routes/companies.routes.ts +++ b/apps/server/src/routes/accounts.routes.ts @@ -1,20 +1,21 @@ import { validateRequestDTO } from "@common/presentation"; +import { ListAccountsSchema } from "@contexts/accounts/presentation"; +import { listAccountsController } from "@contexts/accounts/presentation/controllers/list-accounts"; import { checkTabContext } from "@contexts/auth/infraestructure"; -import { listCompaniesController, ListCompaniesSchema } from "@contexts/companies/presentation"; import { NextFunction, Request, Response, Router } from "express"; -export const companiesRouter = (appRouter: Router) => { +export const accountsRouter = (appRouter: Router) => { const routes: Router = Router({ mergeParams: true }); routes.get( "/", - validateRequestDTO(ListCompaniesSchema), + validateRequestDTO(ListAccountsSchema), checkTabContext, //checkUser, (req: Request, res: Response, next: NextFunction) => { - listCompaniesController().execute(req, res, next); + listAccountsController().execute(req, res, next); } ); - appRouter.use("/companies", routes); + appRouter.use("/accounts", routes); }; diff --git a/apps/server/src/routes/v1.routes.ts b/apps/server/src/routes/v1.routes.ts index 2677ebc6..c127ef13 100644 --- a/apps/server/src/routes/v1.routes.ts +++ b/apps/server/src/routes/v1.routes.ts @@ -1,6 +1,6 @@ import { Router } from "express"; +import { accountsRouter } from "./accounts.routes"; import { authRouter } from "./auth.routes"; -import { companiesRouter } from "./companies.routes"; import { customersRouter } from "./customers.routes"; import { usersRouter } from "./users.routes"; @@ -13,7 +13,7 @@ export const v1Routes = () => { authRouter(routes); usersRouter(routes); - companiesRouter(routes); + accountsRouter(routes); // Sales customersRouter(routes);