.
This commit is contained in:
parent
7d1c441d34
commit
5a9e7261f9
1
apps/server/src/contexts/accounts/application/index.ts
Normal file
1
apps/server/src/contexts/accounts/application/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./list-accounts";
|
||||
@ -0,0 +1 @@
|
||||
export * from "./list-accounts.use-case";
|
||||
@ -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<Result<Collection<Account>, Error>> {
|
||||
return this.transactionManager.complete((transaction) => {
|
||||
return this.accountService.findAccounts(transaction);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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<string>;
|
||||
}
|
||||
|
||||
export interface ICompany {
|
||||
export interface IAccount {
|
||||
id: UniqueID;
|
||||
name: string;
|
||||
tin: TINNumber;
|
||||
@ -44,24 +44,24 @@ export interface ICompany {
|
||||
website: Maybe<string>;
|
||||
logo: Maybe<string>;
|
||||
|
||||
isCompany: boolean;
|
||||
isAccount: boolean;
|
||||
isFreelancer: boolean;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export class Company extends AggregateRoot<ICompanyProps> implements ICompany {
|
||||
static create(props: ICompanyProps, id?: UniqueID): Result<Company, Error> {
|
||||
const company = new Company(props, id);
|
||||
export class Account extends AggregateRoot<IAccountProps> implements IAccount {
|
||||
static create(props: IAccountProps, id?: UniqueID): Result<Account, Error> {
|
||||
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<ICompanyProps> implements ICompany {
|
||||
return this.props.logo;
|
||||
}
|
||||
|
||||
get isCompany(): boolean {
|
||||
get isAccount(): boolean {
|
||||
return !this.props.isFreelancer;
|
||||
}
|
||||
|
||||
@ -0,0 +1 @@
|
||||
export * from "./account";
|
||||
3
apps/server/src/contexts/accounts/domain/index.ts
Normal file
3
apps/server/src/contexts/accounts/domain/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from "./aggregates";
|
||||
|
||||
export * from "./repositories";
|
||||
@ -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<Result<Collection<Account>, Error>>;
|
||||
findById(id: UniqueID, transaction?: any): Promise<Result<Account, Error>>;
|
||||
findByEmail(email: EmailAddress, transaction?: any): Promise<Result<Account, Error>>;
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export * from "./account-repository.interface";
|
||||
@ -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<Result<Collection<Account>, Error>>;
|
||||
findAccountById(userId: UniqueID, transaction?: any): Promise<Result<Account>>;
|
||||
}
|
||||
@ -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<Result<Collection<Account>, 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<Result<Account>> {
|
||||
return await this.accountRepository.findById(accountId, transaction);
|
||||
}
|
||||
}
|
||||
@ -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<CompanyModel, CompanyCreationAttributes, Company> {}
|
||||
export interface IAccountMapper
|
||||
extends ISequelizeMapper<AccountModel, AccountCreationAttributes, Account> {}
|
||||
|
||||
export class CompanyMapper
|
||||
extends SequelizeMapper<CompanyModel, CompanyCreationAttributes, Company>
|
||||
implements ICompanyMapper
|
||||
export class AccountMapper
|
||||
extends SequelizeMapper<AccountModel, AccountCreationAttributes, Account>
|
||||
implements IAccountMapper
|
||||
{
|
||||
public mapToDomain(source: CompanyModel, params?: MapperParamsType): Result<Company, Error> {
|
||||
public mapToDomain(source: AccountModel, params?: MapperParamsType): Result<Account, Error> {
|
||||
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<CompanyCreationAttributes, Error> {
|
||||
): Result<AccountCreationAttributes, Error> {
|
||||
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 };
|
||||
@ -0,0 +1 @@
|
||||
export * from "./account.mapper";
|
||||
@ -7,11 +7,11 @@ import {
|
||||
Sequelize,
|
||||
} from "sequelize";
|
||||
|
||||
export type CompanyCreationAttributes = InferCreationAttributes<CompanyModel, {}> & {};
|
||||
export type AccountCreationAttributes = InferCreationAttributes<AccountModel, {}> & {};
|
||||
|
||||
export class CompanyModel extends Model<
|
||||
InferAttributes<CompanyModel>,
|
||||
InferCreationAttributes<CompanyModel>
|
||||
export class AccountModel extends Model<
|
||||
InferAttributes<AccountModel>,
|
||||
InferCreationAttributes<AccountModel>
|
||||
> {
|
||||
// To avoid table creation
|
||||
/*static async sync(): Promise<any> {
|
||||
@ -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;
|
||||
};
|
||||
@ -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<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 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const accountRepository = new AccountRepository(accountMapper);
|
||||
export { accountRepository };
|
||||
@ -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;
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export * from "./list-accounts";
|
||||
@ -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);
|
||||
};
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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<Account>) => IListAccountsResponseDTO[];
|
||||
}
|
||||
|
||||
export const listAccountsPresenter: IListAccountsPresenter = {
|
||||
toDTO: (accounts: Collection<Account>): 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()),
|
||||
})),
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export interface IListAccountsRequestDTO {}
|
||||
@ -1,4 +1,4 @@
|
||||
export interface IListCompaniesResponseDTO {
|
||||
export interface IListAccountsResponseDTO {
|
||||
id: string;
|
||||
|
||||
is_freelancer: boolean;
|
||||
@ -0,0 +1,3 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const ListAccountsSchema = z.object({});
|
||||
@ -0,0 +1,3 @@
|
||||
export * from "./accounts.request.dto";
|
||||
export * from "./accounts.response.dto";
|
||||
export * from "./accounts.validation.dto";
|
||||
@ -1 +0,0 @@
|
||||
export * from "./list-companies";
|
||||
@ -1 +0,0 @@
|
||||
export * from "./list-companies.use-case";
|
||||
@ -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<Result<Collection<Company>, Error>> {
|
||||
return this.transactionManager.complete((transaction) => {
|
||||
return this.companyService.findCompanies(transaction);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from "./company";
|
||||
@ -1,5 +0,0 @@
|
||||
export * from "./aggregates";
|
||||
export * from "./entities";
|
||||
export * from "./events";
|
||||
export * from "./repositories";
|
||||
export * from "./value-objects";
|
||||
@ -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<Result<Collection<Company>, Error>>;
|
||||
findById(id: UniqueID, transaction?: any): Promise<Result<Company, Error>>;
|
||||
findByEmail(email: EmailAddress, transaction?: any): Promise<Result<Company, Error>>;
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from "./company-repository.interface";
|
||||
@ -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<Result<Collection<Company>, Error>>;
|
||||
findCompanyById(userId: UniqueID, transaction?: any): Promise<Result<Company>>;
|
||||
}
|
||||
@ -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<Result<Collection<Company>, 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<Result<Company>> {
|
||||
return await this.companyRepository.findById(companyId, transaction);
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from "./company.mapper";
|
||||
@ -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;
|
||||
};
|
||||
@ -1 +0,0 @@
|
||||
export * from "./list-companies";
|
||||
@ -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);
|
||||
};
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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<Company>) => IListCompaniesResponseDTO[];
|
||||
}
|
||||
|
||||
export const listCompaniesPresenter: IListCompaniesPresenter = {
|
||||
toDTO: (companies: Collection<Company>): 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()),
|
||||
})),
|
||||
};
|
||||
@ -1 +0,0 @@
|
||||
export interface IListCompaniesRequestDTO {}
|
||||
@ -1,3 +0,0 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const ListCompaniesSchema = z.object({});
|
||||
@ -1,3 +0,0 @@
|
||||
export * from "./companies.request.dto";
|
||||
export * from "./companies.response.dto";
|
||||
export * from "./companies.validation.dto";
|
||||
1
apps/server/src/contexts/contacts/application/index.ts
Normal file
1
apps/server/src/contexts/contacts/application/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./list-contacts.use-case";
|
||||
@ -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<Result<Collection<Contact>, Error>> {
|
||||
return this.transactionManager.complete((transaction) => {
|
||||
return this.contactService.findContact(transaction);
|
||||
});
|
||||
}
|
||||
}
|
||||
130
apps/server/src/contexts/contacts/domain/aggregates/contact.ts
Normal file
130
apps/server/src/contexts/contacts/domain/aggregates/contact.ts
Normal file
@ -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<string>;
|
||||
website: Maybe<string>;
|
||||
fax: Maybe<PhoneNumber>;
|
||||
}
|
||||
|
||||
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<string>;
|
||||
fax: Maybe<PhoneNumber>;
|
||||
website: Maybe<string>;
|
||||
|
||||
isContact: boolean;
|
||||
isFreelancer: boolean;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export class Contact extends AggregateRoot<IContactProps> implements IContact {
|
||||
static create(props: IContactProps, id?: UniqueID): Result<Contact, Error> {
|
||||
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<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 isContact(): boolean {
|
||||
return !this.props.isFreelancer;
|
||||
}
|
||||
|
||||
get isFreelancer(): boolean {
|
||||
return this.props.isFreelancer;
|
||||
}
|
||||
|
||||
get isActive(): boolean {
|
||||
return this.props.status === "active";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export * from "./contact";
|
||||
3
apps/server/src/contexts/contacts/domain/index.ts
Normal file
3
apps/server/src/contexts/contacts/domain/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from "./aggregates";
|
||||
export * from "./repositories";
|
||||
export * from "./services";
|
||||
@ -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<Result<Collection<Contact>, Error>>;
|
||||
findById(id: UniqueID, transaction?: any): Promise<Result<Contact, Error>>;
|
||||
findByEmail(email: EmailAddress, transaction?: any): Promise<Result<Contact, Error>>;
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export * from "./contact-repository.interface";
|
||||
@ -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<Result<Collection<Contact>, Error>>;
|
||||
findContactById(contactId: UniqueID, transaction?: any): Promise<Result<Contact>>;
|
||||
}
|
||||
@ -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<Result<Collection<Contact>, 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<Result<Contact>> {
|
||||
return await this.contactRepository.findById(contactId, transaction);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./contact-service.interface";
|
||||
export * from "./contact.service";
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./mappers";
|
||||
export * from "./sequelize";
|
||||
@ -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<ContactModel, ContactCreationAttributes, Contact> {}
|
||||
|
||||
export class ContactMapper
|
||||
extends SequelizeMapper<ContactModel, ContactCreationAttributes, Contact>
|
||||
implements IContactMapper
|
||||
{
|
||||
public mapToDomain(source: ContactModel, params?: MapperParamsType): Result<Contact, Error> {
|
||||
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<ContactCreationAttributes, Error> {
|
||||
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 };
|
||||
@ -0,0 +1 @@
|
||||
export * from "./contact.mapper";
|
||||
@ -0,0 +1,172 @@
|
||||
import {
|
||||
CreationOptional,
|
||||
DataTypes,
|
||||
InferAttributes,
|
||||
InferCreationAttributes,
|
||||
Model,
|
||||
Sequelize,
|
||||
} from "sequelize";
|
||||
|
||||
export type ContactCreationAttributes = InferCreationAttributes<ContactModel, {}> & {};
|
||||
|
||||
export class ContactModel extends Model<
|
||||
InferAttributes<ContactModel>,
|
||||
InferCreationAttributes<ContactModel>
|
||||
> {
|
||||
// To avoid table creation
|
||||
/*static async sync(): Promise<any> {
|
||||
return Promise.resolve();
|
||||
}*/
|
||||
|
||||
declare id: string;
|
||||
declare reference: CreationOptional<string>;
|
||||
|
||||
declare is_freelancer: 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 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;
|
||||
};
|
||||
@ -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<Company> implements ICompanyRepository {
|
||||
private readonly _mapper!: ICompanyMapper;
|
||||
class ContactRepository extends SequelizeRepository<Contact> 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<Result<Collection<Company>, Error>> {
|
||||
async findAll(transaction?: Transaction): Promise<Result<Collection<Contact>, 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<Result<Company, Error>> {
|
||||
async findById(id: UniqueID, transaction?: Transaction): Promise<Result<Contact, Error>> {
|
||||
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<Company> implements ICompany
|
||||
async findByEmail(
|
||||
email: EmailAddress,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<Company, Error>> {
|
||||
): Promise<Result<Contact, Error>> {
|
||||
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 };
|
||||
@ -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;
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export * from "./list";
|
||||
@ -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);
|
||||
};
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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<Contact>) => IListContactsResponseDTO[];
|
||||
}
|
||||
|
||||
export const listContactsPresenter: IListContactsPresenter = {
|
||||
toDTO: (contacts: Collection<Contact>): 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),
|
||||
})),
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export interface IListContactsRequestDTO {}
|
||||
@ -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;
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const ListContactsSchema = z.object({});
|
||||
@ -0,0 +1,3 @@
|
||||
export * from "./contacts.request.dto";
|
||||
export * from "./contacts.response.dto";
|
||||
export * from "./contacts.validation.dto";
|
||||
2
apps/server/src/contexts/contacts/presentation/index.ts
Normal file
2
apps/server/src/contexts/contacts/presentation/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./controllers";
|
||||
export * from "./dto";
|
||||
@ -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);
|
||||
};
|
||||
@ -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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user