.
This commit is contained in:
parent
ccdf2efef3
commit
af1eca6c30
13
server/jest.config.js
Normal file
13
server/jest.config.js
Normal file
@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
globals: {
|
||||
"ts-jest": {
|
||||
tsconfig: "tsconfig.json",
|
||||
},
|
||||
},
|
||||
moduleFileExtensions: ["ts", "js"],
|
||||
transform: {
|
||||
"^.+\\.(ts|tsx)$": "ts-jest",
|
||||
},
|
||||
testMatch: ["**/*.test.(ts|js)"],
|
||||
testEnvironment: "node",
|
||||
};
|
||||
@ -64,7 +64,6 @@
|
||||
"moment": "^2.29.4",
|
||||
"morgan": "^1.10.0",
|
||||
"mysql2": "^3.6.0",
|
||||
"node-firebird": "^1.1.8",
|
||||
"path": "^0.12.7",
|
||||
"remove": "^0.1.5",
|
||||
"response-time": "^2.3.2",
|
||||
|
||||
@ -13,25 +13,25 @@ import {
|
||||
} from "@shared/contexts";
|
||||
|
||||
import { IInfrastructureError } from "@/contexts/common/infrastructure";
|
||||
import { IFirebirdAdapter } from "@/contexts/common/infrastructure/firebird";
|
||||
import { ICatalogRepository, Product } from "../domain";
|
||||
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||
import { Article, ICatalogRepository } from "../domain";
|
||||
|
||||
export interface IListProductsParams {
|
||||
export interface IListArticlesParams {
|
||||
queryCriteria: IQueryCriteria;
|
||||
}
|
||||
|
||||
export type ListProductsResult =
|
||||
export type ListArticlesResult =
|
||||
| Result<never, IUseCaseError> // Misc errors (value objects)
|
||||
| Result<ICollection<Product>, never>; // Success!
|
||||
| Result<ICollection<Article>, never>; // Success!
|
||||
|
||||
export class ListProductsUseCase
|
||||
implements IUseCase<IListProductsParams, Promise<ListProductsResult>>
|
||||
export class ListArticlesUseCase
|
||||
implements IUseCase<IListArticlesParams, Promise<ListArticlesResult>>
|
||||
{
|
||||
private _adapter: IFirebirdAdapter;
|
||||
private _adapter: ISequelizeAdapter;
|
||||
private _repositoryManager: IRepositoryManager;
|
||||
|
||||
constructor(props: {
|
||||
adapter: IFirebirdAdapter;
|
||||
adapter: ISequelizeAdapter;
|
||||
repositoryManager: IRepositoryManager;
|
||||
}) {
|
||||
this._adapter = props.adapter;
|
||||
@ -43,19 +43,19 @@ export class ListProductsUseCase
|
||||
}
|
||||
|
||||
async execute(
|
||||
params: Partial<IListProductsParams>
|
||||
): Promise<ListProductsResult> {
|
||||
params: Partial<IListArticlesParams>
|
||||
): Promise<ListArticlesResult> {
|
||||
const { queryCriteria } = params;
|
||||
|
||||
return this.findProducts(queryCriteria);
|
||||
return this.findArticles(queryCriteria);
|
||||
}
|
||||
|
||||
private async findProducts(queryCriteria) {
|
||||
private async findArticles(queryCriteria) {
|
||||
const transaction = this._adapter.startTransaction();
|
||||
const productRepoBuilder =
|
||||
this.getRepositoryByName<ICatalogRepository>("Product");
|
||||
this.getRepositoryByName<ICatalogRepository>("Article");
|
||||
|
||||
let products: ICollection<Product> = new Collection();
|
||||
let products: ICollection<Article> = new Collection();
|
||||
|
||||
try {
|
||||
transaction.complete(async (t) => {
|
||||
@ -1 +1 @@
|
||||
export * from "./ListProductsUseCase";
|
||||
export * from "./ListArticlesUseCase";
|
||||
|
||||
87
server/src/contexts/catalog/domain/entities/Article.ts
Normal file
87
server/src/contexts/catalog/domain/entities/Article.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import {
|
||||
AggregateRoot,
|
||||
Description,
|
||||
IDomainError,
|
||||
Quantity,
|
||||
Result,
|
||||
Slug,
|
||||
UniqueID,
|
||||
UnitPrice,
|
||||
} from "@shared/contexts";
|
||||
|
||||
export interface IArticleProps {
|
||||
catalog_name: Slug;
|
||||
id_article: Description;
|
||||
reference: Slug;
|
||||
family: Description;
|
||||
subfamily: Description;
|
||||
description: Description;
|
||||
points: Quantity;
|
||||
retail_price: UnitPrice;
|
||||
}
|
||||
|
||||
export interface IArticle {
|
||||
id: UniqueID;
|
||||
catalog_name: Slug;
|
||||
id_article: Description;
|
||||
reference: Slug;
|
||||
family: Description;
|
||||
subfamily: Description;
|
||||
description: Description;
|
||||
points: Quantity;
|
||||
retail_price: UnitPrice;
|
||||
}
|
||||
|
||||
export class Article extends AggregateRoot<IArticleProps> implements IArticle {
|
||||
public static create(
|
||||
props: IArticleProps,
|
||||
id?: UniqueID
|
||||
): Result<Article, IDomainError> {
|
||||
//const isNew = !!id === false;
|
||||
|
||||
// Se hace en el constructor de la Entidad
|
||||
/* if (isNew) {
|
||||
id = UniqueEntityID.create();
|
||||
}*/
|
||||
|
||||
const product = new Article(props, id);
|
||||
|
||||
return Result.ok<Article>(product);
|
||||
}
|
||||
|
||||
private constructor(props: IArticleProps, id?: UniqueID) {
|
||||
super(props, id);
|
||||
}
|
||||
|
||||
get catalog_name(): Slug {
|
||||
return this.props.catalog_name;
|
||||
}
|
||||
|
||||
get id_article(): Description {
|
||||
return this.props.id_article;
|
||||
}
|
||||
|
||||
get reference(): Slug {
|
||||
return this.props.reference;
|
||||
}
|
||||
|
||||
get family(): Description {
|
||||
return this.props.family;
|
||||
}
|
||||
|
||||
get subfamily(): Description {
|
||||
return this.props.subfamily;
|
||||
}
|
||||
|
||||
get description(): Description {
|
||||
return this.props.description;
|
||||
}
|
||||
|
||||
get points(): Quantity {
|
||||
return this.props.points;
|
||||
}
|
||||
|
||||
get retail_price(): UnitPrice {
|
||||
return this.props.retail_price;
|
||||
}
|
||||
}
|
||||
@ -1,75 +0,0 @@
|
||||
import {
|
||||
AggregateRoot,
|
||||
Description,
|
||||
IDomainError,
|
||||
MoneyValueObject,
|
||||
Result,
|
||||
StringValueObject,
|
||||
UniqueID,
|
||||
ValueObject,
|
||||
} from "@shared/contexts";
|
||||
|
||||
export interface IProductProps {
|
||||
reference: StringValueObject;
|
||||
family: StringValueObject;
|
||||
subfamily: StringValueObject;
|
||||
description: StringValueObject;
|
||||
points: ValueObject<number>;
|
||||
pvp: MoneyValueObject;
|
||||
}
|
||||
|
||||
export interface IProduct {
|
||||
id: UniqueID;
|
||||
reference: Description;
|
||||
family: Description;
|
||||
subfamily: Description;
|
||||
description: Description;
|
||||
points: ValueObject<number>;
|
||||
pvp: MoneyValueObject;
|
||||
}
|
||||
|
||||
export class Product extends AggregateRoot<IProductProps> implements IProduct {
|
||||
public static create(
|
||||
props: IProductProps,
|
||||
id?: UniqueID
|
||||
): Result<Product, IDomainError> {
|
||||
//const isNew = !!id === false;
|
||||
|
||||
// Se hace en el constructor de la Entidad
|
||||
/* if (isNew) {
|
||||
id = UniqueEntityID.create();
|
||||
}*/
|
||||
|
||||
const product = new Product(props, id);
|
||||
|
||||
return Result.ok<Product>(product);
|
||||
}
|
||||
|
||||
private constructor(props: IProductProps, id?: UniqueID) {
|
||||
super(props, id);
|
||||
}
|
||||
|
||||
get reference(): Description {
|
||||
return this.props.reference;
|
||||
}
|
||||
|
||||
get family(): Description {
|
||||
return this.props.family;
|
||||
}
|
||||
|
||||
get subfamily(): Description {
|
||||
return this.props.subfamily;
|
||||
}
|
||||
|
||||
get description(): Description {
|
||||
return this.props.description;
|
||||
}
|
||||
|
||||
get points(): ValueObject<number> {
|
||||
return this.props.points;
|
||||
}
|
||||
|
||||
get pvp(): MoneyValueObject {
|
||||
return this.props.pvp;
|
||||
}
|
||||
}
|
||||
@ -1 +1 @@
|
||||
export * from "./Product";
|
||||
export * from "./Article";
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { IRepository } from "@/contexts/common/domain";
|
||||
import { ICollection, IQueryCriteria } from "@shared/contexts";
|
||||
import { Product } from "../entities";
|
||||
import { ICollection, IQueryCriteria, UniqueID } from "@shared/contexts";
|
||||
import { Article } from "../entities";
|
||||
|
||||
export interface ICatalogRepository extends IRepository<any> {
|
||||
findAll(queryCriteria?: IQueryCriteria): Promise<ICollection<Product>>;
|
||||
getById(id: UniqueID): Promise<Article | null>;
|
||||
findAll(queryCriteria?: IQueryCriteria): Promise<ICollection<Article>>;
|
||||
}
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import {
|
||||
FirebirdRepository,
|
||||
IFirebirdAdapter,
|
||||
} from "@/contexts/common/infrastructure/firebird";
|
||||
ISequelizeAdapter,
|
||||
SequelizeRepository,
|
||||
} from "@/contexts/common/infrastructure/sequelize";
|
||||
import { ICollection, IQueryCriteria, UniqueID } from "@shared/contexts";
|
||||
import Firebird from "node-firebird";
|
||||
import { Product } from "../domain/entities";
|
||||
import { Transaction } from "sequelize";
|
||||
import { Article } from "../domain/entities";
|
||||
import { ICatalogRepository } from "../domain/repository/CatalogRepository.interface";
|
||||
import { Product_Model } from "./firebird";
|
||||
import { IProductMapper } from "./mappers/product.mapper";
|
||||
import { IArticleMapper } from "./mappers/article.mapper";
|
||||
|
||||
export type QueryParams = {
|
||||
pagination: Record<string, any>;
|
||||
@ -15,55 +14,42 @@ export type QueryParams = {
|
||||
};
|
||||
|
||||
export class CatalogRepository
|
||||
extends FirebirdRepository<Product>
|
||||
extends SequelizeRepository<Article>
|
||||
implements ICatalogRepository
|
||||
{
|
||||
protected mapper: IProductMapper;
|
||||
protected mapper: IArticleMapper;
|
||||
|
||||
public constructor(props: {
|
||||
mapper: IProductMapper;
|
||||
adapter: IFirebirdAdapter;
|
||||
transaction: Firebird.Transaction;
|
||||
mapper: IArticleMapper;
|
||||
adapter: ISequelizeAdapter;
|
||||
transaction: Transaction;
|
||||
}) {
|
||||
const { adapter, mapper, transaction } = props;
|
||||
super({ adapter, transaction });
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
public async getById(id: UniqueID): Promise<Product | null> {
|
||||
const rawProduct: Product_Model = await this.adapter.execute(
|
||||
"SELECT * FROM ARTICULOS WHERE ID=?",
|
||||
[id.toString()]
|
||||
);
|
||||
public async getById(id: UniqueID): Promise<Article | null> {
|
||||
const rawArticle: any = await this._getById("Article_Model", id);
|
||||
|
||||
return this.mapper.mapToDomain(rawProduct);
|
||||
if (!rawArticle === true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.mapper.mapToDomain(rawArticle);
|
||||
}
|
||||
|
||||
/*public async count(): number {
|
||||
const result = await this.adapter.execute<number>(
|
||||
"SELECT count(*) FROM ARTICULOS",
|
||||
[]
|
||||
);
|
||||
|
||||
|
||||
|
||||
}*/
|
||||
|
||||
public async findAll(
|
||||
queryCriteria?: IQueryCriteria
|
||||
): Promise<ICollection<Product>> {
|
||||
let rows: Product_Model[] = [];
|
||||
const count = await this.adapter.execute<number[]>(
|
||||
"SELECT count(*) FROM ARTICULOS",
|
||||
[]
|
||||
): Promise<ICollection<any>> {
|
||||
const { rows, count } = await this._findAll(
|
||||
"Article_Model",
|
||||
queryCriteria
|
||||
/*{
|
||||
include: [], // esto es para quitar las asociaciones al hacer la consulta
|
||||
}*/
|
||||
);
|
||||
if (count[0]) {
|
||||
rows = await this.adapter.execute<Product_Model[]>(
|
||||
"SELECT * FROM ARTICULOS",
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
return this.mapper.mapArrayAndCountToDomain(rows, count[0]);
|
||||
return this.mapper.mapArrayAndCountToDomain(rows, count);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import express, { NextFunction, Request, Response, Router } from "express";
|
||||
|
||||
import { RepositoryManager } from "@/contexts/common/domain";
|
||||
import { createFirebirdAdapter } from "@/contexts/common/infrastructure/firebird";
|
||||
import { createListProductsController } from "./controllers/listProducts";
|
||||
import { createSequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||
import { createListArticlesController } from "./controllers";
|
||||
|
||||
const catalogRouter: Router = express.Router({ mergeParams: true });
|
||||
|
||||
@ -17,7 +17,7 @@ catalogRouter.use(logMiddleware);
|
||||
|
||||
const contextMiddleware = (req: Request, res: Response, next: NextFunction) => {
|
||||
res.locals["context"] = {
|
||||
adapter: createFirebirdAdapter(),
|
||||
adapter: createSequelizeAdapter(),
|
||||
repositoryManager: RepositoryManager.getInstance(),
|
||||
services: {},
|
||||
};
|
||||
@ -28,7 +28,7 @@ const contextMiddleware = (req: Request, res: Response, next: NextFunction) => {
|
||||
catalogRouter.use(contextMiddleware);
|
||||
|
||||
catalogRouter.get("/", (req: Request, res: Response, next: NextFunction) =>
|
||||
createListProductsController(res.locals["context"]).execute(req, res, next)
|
||||
createListArticlesController(res.locals["context"]).execute(req, res, next)
|
||||
);
|
||||
|
||||
/*catalogRouter.get(
|
||||
|
||||
@ -1 +1 @@
|
||||
export * from "./listProducts";
|
||||
export * from "./listArticles";
|
||||
|
||||
@ -1,33 +1,33 @@
|
||||
import Joi from "joi";
|
||||
|
||||
import {
|
||||
ListProductsResult,
|
||||
ListProductsUseCase,
|
||||
ListArticlesResult,
|
||||
ListArticlesUseCase,
|
||||
} from "@/contexts/catalog/application";
|
||||
import { Product } from "@/contexts/catalog/domain";
|
||||
import { Article } from "@/contexts/catalog/domain";
|
||||
import { QueryCriteriaService } from "@/contexts/common/application/services";
|
||||
import { IServerError } from "@/contexts/common/domain/errors";
|
||||
import { ExpressController } from "@/contexts/common/infrastructure/express";
|
||||
import {
|
||||
ICollection,
|
||||
IListProducts_Response_DTO,
|
||||
IListArticles_Response_DTO,
|
||||
IListResponse_DTO,
|
||||
IQueryCriteria,
|
||||
Result,
|
||||
RuleValidator,
|
||||
} from "@shared/contexts";
|
||||
import { ICatalogContext } from "../../..";
|
||||
import { IListProductsPresenter } from "./presenter";
|
||||
import { IListArticlesPresenter } from "./presenter";
|
||||
|
||||
export class ListProductsController extends ExpressController {
|
||||
private useCase: ListProductsUseCase;
|
||||
private presenter: IListProductsPresenter;
|
||||
export class ListArticlesController extends ExpressController {
|
||||
private useCase: ListArticlesUseCase;
|
||||
private presenter: IListArticlesPresenter;
|
||||
private context: ICatalogContext;
|
||||
|
||||
constructor(
|
||||
props: {
|
||||
useCase: ListProductsUseCase;
|
||||
presenter: IListProductsPresenter;
|
||||
useCase: ListArticlesUseCase;
|
||||
presenter: IListArticlesPresenter;
|
||||
},
|
||||
context: ICatalogContext
|
||||
) {
|
||||
@ -65,7 +65,7 @@ export class ListProductsController extends ExpressController {
|
||||
|
||||
console.log(queryCriteria);
|
||||
|
||||
const result: ListProductsResult = await this.useCase.execute({
|
||||
const result: ListArticlesResult = await this.useCase.execute({
|
||||
queryCriteria,
|
||||
});
|
||||
|
||||
@ -73,9 +73,9 @@ export class ListProductsController extends ExpressController {
|
||||
return this.clientError(result.error.message);
|
||||
}
|
||||
|
||||
const customers = <ICollection<Product>>result.object;
|
||||
const customers = <ICollection<Article>>result.object;
|
||||
|
||||
return this.ok<IListResponse_DTO<IListProducts_Response_DTO>>(
|
||||
return this.ok<IListResponse_DTO<IListArticles_Response_DTO>>(
|
||||
this.presenter.mapArray(customers, this.context, {
|
||||
page: queryCriteria.pagination.offset,
|
||||
limit: queryCriteria.pagination.limit,
|
||||
@ -0,0 +1,34 @@
|
||||
import { ListArticlesUseCase } from "@/contexts/catalog/application";
|
||||
import { ICatalogContext } from "../../..";
|
||||
import { CatalogRepository } from "../../../Catalog.repository";
|
||||
import { createArticleMapper } from "../../../mappers/article.mapper";
|
||||
import { ListArticlesController } from "./ListArticlesController";
|
||||
import { listArticlesPresenter } from "./presenter";
|
||||
|
||||
export const createListArticlesController = (context: ICatalogContext) => {
|
||||
const adapter = context.adapter;
|
||||
const repoManager = context.repositoryManager;
|
||||
|
||||
repoManager.registerRepository(
|
||||
"Article",
|
||||
(params = { transaction: null }) => {
|
||||
const { transaction } = params;
|
||||
|
||||
return new CatalogRepository({
|
||||
transaction,
|
||||
adapter,
|
||||
mapper: createArticleMapper(context),
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const listArticlesUseCase = new ListArticlesUseCase(context);
|
||||
|
||||
return new ListArticlesController(
|
||||
{
|
||||
useCase: listArticlesUseCase,
|
||||
presenter: listArticlesPresenter,
|
||||
},
|
||||
context
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,78 @@
|
||||
import { Article } from "@/contexts/catalog/domain";
|
||||
import { ICatalogContext } from "@/contexts/catalog/infrastructure";
|
||||
import {
|
||||
ICollection,
|
||||
IListArticles_Response_DTO,
|
||||
IListResponse_DTO,
|
||||
} from "@shared/contexts";
|
||||
|
||||
export interface IListArticlesPresenter {
|
||||
map: (
|
||||
article: Article,
|
||||
context: ICatalogContext
|
||||
) => IListArticles_Response_DTO;
|
||||
|
||||
mapArray: (
|
||||
articles: ICollection<Article>,
|
||||
context: ICatalogContext,
|
||||
params: {
|
||||
page: number;
|
||||
limit: number;
|
||||
}
|
||||
) => IListResponse_DTO<IListArticles_Response_DTO>;
|
||||
}
|
||||
|
||||
export const listArticlesPresenter: IListArticlesPresenter = {
|
||||
map: (
|
||||
article: Article,
|
||||
context: ICatalogContext
|
||||
): IListArticles_Response_DTO => {
|
||||
console.time("listArticlesPresenter.map");
|
||||
|
||||
const result: IListArticles_Response_DTO = {
|
||||
id: article.id.toString(),
|
||||
catalog_name: article.catalog_name.toString(),
|
||||
id_article: article.id_article.toString(),
|
||||
reference: article.reference.toString(),
|
||||
description: article.description.toString(),
|
||||
family: article.family.toString(),
|
||||
subfamily: article.subfamily.toString(),
|
||||
points: article.points.toNumber(),
|
||||
retail_price: article.retail_price.toObject(),
|
||||
};
|
||||
|
||||
console.timeEnd("listArticlesPresenter.map");
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
mapArray: (
|
||||
articles: ICollection<Article>,
|
||||
context: ICatalogContext,
|
||||
params: {
|
||||
page: number;
|
||||
limit: number;
|
||||
}
|
||||
): IListResponse_DTO<IListArticles_Response_DTO> => {
|
||||
console.time("listArticlesPresenter.mapArray");
|
||||
|
||||
const { page, limit } = params;
|
||||
|
||||
const totalCount = articles.totalCount ?? 0;
|
||||
const items = articles.items.map((article: Article) =>
|
||||
listArticlesPresenter.map(article, context)
|
||||
);
|
||||
|
||||
const result = {
|
||||
page,
|
||||
per_page: limit,
|
||||
total_pages: Math.ceil(totalCount / limit),
|
||||
total_items: totalCount,
|
||||
items,
|
||||
};
|
||||
|
||||
console.timeEnd("listArticlesPresenter.mapArray");
|
||||
|
||||
return result;
|
||||
},
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export * from "./ListArticles.presenter";
|
||||
@ -1,34 +0,0 @@
|
||||
import { ListProductsUseCase } from "@/contexts/catalog/application";
|
||||
import { ICatalogContext } from "../../..";
|
||||
import { CatalogRepository } from "../../../Catalog.repository";
|
||||
import { createProductMapper } from "../../../mappers/product.mapper";
|
||||
import { ListProductsController } from "./ListProductsController";
|
||||
import { listProductsPresenter } from "./presenter";
|
||||
|
||||
export const createListProductsController = (context: ICatalogContext) => {
|
||||
const adapter = context.adapter;
|
||||
const repoManager = context.repositoryManager;
|
||||
|
||||
repoManager.registerRepository(
|
||||
"Product",
|
||||
(params = { transaction: null }) => {
|
||||
const { transaction } = params;
|
||||
|
||||
return new CatalogRepository({
|
||||
transaction,
|
||||
adapter,
|
||||
mapper: createProductMapper(context),
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const listProductsUseCase = new ListProductsUseCase(context);
|
||||
|
||||
return new ListProductsController(
|
||||
{
|
||||
useCase: listProductsUseCase,
|
||||
presenter: listProductsPresenter,
|
||||
},
|
||||
context
|
||||
);
|
||||
};
|
||||
@ -1,71 +0,0 @@
|
||||
import { Product } from "@/contexts/catalog/domain";
|
||||
import { ICatalogContext } from "@/contexts/catalog/infrastructure";
|
||||
import {
|
||||
ICollection,
|
||||
IListProducts_Response_DTO,
|
||||
IListResponse_DTO,
|
||||
} from "@shared/contexts";
|
||||
|
||||
export interface IListProductsPresenter {
|
||||
map: (
|
||||
product: Product,
|
||||
context: ICatalogContext
|
||||
) => IListProducts_Response_DTO;
|
||||
|
||||
mapArray: (
|
||||
products: ICollection<Product>,
|
||||
context: ICatalogContext,
|
||||
params: {
|
||||
page: number;
|
||||
limit: number;
|
||||
}
|
||||
) => IListResponse_DTO<IListProducts_Response_DTO>;
|
||||
}
|
||||
|
||||
export const listProductsPresenter: IListProductsPresenter = {
|
||||
map: (
|
||||
product: Product,
|
||||
context: ICatalogContext
|
||||
): IListProducts_Response_DTO => {
|
||||
console.time("listProductsPresenter.map");
|
||||
|
||||
const result: IListProducts_Response_DTO = {
|
||||
id: product.id.toString(),
|
||||
reference: product.reference.toString(),
|
||||
};
|
||||
|
||||
console.timeEnd("listProductsPresenter.map");
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
mapArray: (
|
||||
products: ICollection<Product>,
|
||||
context: ICatalogContext,
|
||||
params: {
|
||||
page: number;
|
||||
limit: number;
|
||||
}
|
||||
): IListResponse_DTO<IListProducts_Response_DTO> => {
|
||||
console.time("listProductsPresenter.mapArray");
|
||||
|
||||
const { page, limit } = params;
|
||||
|
||||
const totalCount = products.totalCount ?? 0;
|
||||
const items = products.items.map((product: Product) =>
|
||||
listProductsPresenter.map(product, context)
|
||||
);
|
||||
|
||||
const result = {
|
||||
page,
|
||||
per_page: limit,
|
||||
total_pages: Math.ceil(totalCount / limit),
|
||||
total_items: totalCount,
|
||||
items,
|
||||
};
|
||||
|
||||
console.timeEnd("listProductsPresenter.mapArray");
|
||||
|
||||
return result;
|
||||
},
|
||||
};
|
||||
@ -1 +0,0 @@
|
||||
export * from "./ListProducts.presenter";
|
||||
@ -1,9 +1,9 @@
|
||||
import { IApplicationService } from "@/contexts/common/application/services/ApplicationService";
|
||||
import { IRepositoryManager } from "@/contexts/common/domain";
|
||||
import { IFirebirdAdapter } from "@/contexts/common/infrastructure/firebird";
|
||||
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||
|
||||
export interface ICatalogContext {
|
||||
adapter: IFirebirdAdapter;
|
||||
adapter: ISequelizeAdapter;
|
||||
repositoryManager: IRepositoryManager;
|
||||
services: IApplicationService;
|
||||
}
|
||||
|
||||
@ -0,0 +1,62 @@
|
||||
import {
|
||||
ISequelizeMapper,
|
||||
SequelizeMapper,
|
||||
} from "@/contexts/common/infrastructure";
|
||||
import {
|
||||
Description,
|
||||
Quantity,
|
||||
Slug,
|
||||
UniqueID,
|
||||
UnitPrice,
|
||||
} from "@shared/contexts";
|
||||
import { ICatalogContext } from "..";
|
||||
import { Article, IArticleProps } from "../../domain/entities";
|
||||
import {
|
||||
Article_Model,
|
||||
TCreationArticle_Attributes,
|
||||
} from "../sequelize/article.model";
|
||||
|
||||
export interface IArticleMapper
|
||||
extends ISequelizeMapper<
|
||||
Article_Model,
|
||||
TCreationArticle_Attributes,
|
||||
Article
|
||||
> {}
|
||||
|
||||
class ArticleMapper
|
||||
extends SequelizeMapper<Article_Model, TCreationArticle_Attributes, Article>
|
||||
implements IArticleMapper
|
||||
{
|
||||
public constructor(props: { context: ICatalogContext }) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
protected toDomainMappingImpl(source: Article_Model, params: any): Article {
|
||||
const props: IArticleProps = {
|
||||
catalog_name: this.mapsValue(source, "catalog_name", Slug.create),
|
||||
id_article: this.mapsValue(source, "id_article", Description.create),
|
||||
reference: this.mapsValue(source, "reference", Slug.create),
|
||||
family: this.mapsValue(source, "family", Description.create),
|
||||
subfamily: this.mapsValue(source, "subfamily", Description.create),
|
||||
description: this.mapsValue(source, "description", Description.create),
|
||||
points: this.mapsValue(source, "points", Quantity.create),
|
||||
retail_price: this.mapsValue(source, "retail_price", (value: any) =>
|
||||
UnitPrice.create({ amount: value, precision: 4 })
|
||||
),
|
||||
};
|
||||
|
||||
const id = this.mapsValue(source, "id", UniqueID.create);
|
||||
const productOrError = Article.create(props, id);
|
||||
|
||||
if (productOrError.isFailure) {
|
||||
throw productOrError.error;
|
||||
}
|
||||
|
||||
return productOrError.object;
|
||||
}
|
||||
}
|
||||
|
||||
export const createArticleMapper = (context: ICatalogContext): IArticleMapper =>
|
||||
new ArticleMapper({
|
||||
context,
|
||||
});
|
||||
@ -1,47 +0,0 @@
|
||||
import {
|
||||
FirebirdMapper,
|
||||
IFirebirdMapper,
|
||||
} from "@/contexts/common/infrastructure/mappers/FirebirdMapper";
|
||||
import { Description, MoneyValue, UniqueID } from "@shared/contexts";
|
||||
import { ICatalogContext } from "..";
|
||||
import { IProductProps, Product } from "../../domain/entities";
|
||||
import { Product_Model } from "../firebird";
|
||||
|
||||
export interface IProductMapper
|
||||
extends IFirebirdMapper<Product_Model, Product> {}
|
||||
|
||||
class ProductMapper
|
||||
extends FirebirdMapper<Product_Model, Product>
|
||||
implements IProductMapper
|
||||
{
|
||||
public constructor(props: { context: ICatalogContext }) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
protected toDomainMappingImpl(source: Product_Model, params: any): Product {
|
||||
const props: IProductProps = {
|
||||
reference: this.mapsValue(source, "reference", Description.create),
|
||||
family: this.mapsValue(source, "family", Description.create),
|
||||
subfamily: this.mapsValue(source, "subfamily", Description.create),
|
||||
description: this.mapsValue(source, "description", Description.create),
|
||||
points: this.mapsValue(source, "points", Description.create),
|
||||
pvp: this.mapsValue(source, "pvp", (value: any) =>
|
||||
MoneyValue.create({ amount: value })
|
||||
),
|
||||
};
|
||||
|
||||
const id = this.mapsValue(source, "id", UniqueID.create);
|
||||
const productOrError = Product.create(props, id);
|
||||
|
||||
if (productOrError.isFailure) {
|
||||
throw productOrError.error;
|
||||
}
|
||||
|
||||
return productOrError.object;
|
||||
}
|
||||
}
|
||||
|
||||
export const createProductMapper = (context: ICatalogContext): IProductMapper =>
|
||||
new ProductMapper({
|
||||
context,
|
||||
});
|
||||
@ -0,0 +1,76 @@
|
||||
import {
|
||||
CreationOptional,
|
||||
DataTypes,
|
||||
InferAttributes,
|
||||
InferCreationAttributes,
|
||||
Model,
|
||||
Sequelize,
|
||||
} from "sequelize";
|
||||
|
||||
export type TCreationArticle_Attributes =
|
||||
InferCreationAttributes<Article_Model>;
|
||||
|
||||
export class Article_Model extends Model<
|
||||
InferAttributes<Article_Model>,
|
||||
InferCreationAttributes<Article_Model>
|
||||
> {
|
||||
// To avoid table creation
|
||||
/*static async sync(): Promise<any> {
|
||||
return Promise.resolve();
|
||||
}*/
|
||||
|
||||
static associate(connection: Sequelize) {}
|
||||
|
||||
declare id: string;
|
||||
|
||||
declare catalog_name: string;
|
||||
declare id_article: string; // number ??
|
||||
declare reference: CreationOptional<string>;
|
||||
declare family: CreationOptional<string>;
|
||||
declare subfamily: CreationOptional<string>;
|
||||
declare description: CreationOptional<string>;
|
||||
declare points: CreationOptional<number>;
|
||||
declare retail_price: CreationOptional<number>;
|
||||
}
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
Article_Model.init(
|
||||
{
|
||||
id: {
|
||||
type: new DataTypes.UUID(),
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
catalog_name: DataTypes.STRING(),
|
||||
id_article: DataTypes.STRING(),
|
||||
reference: DataTypes.STRING(),
|
||||
family: DataTypes.STRING(),
|
||||
subfamily: DataTypes.STRING(),
|
||||
description: DataTypes.STRING(),
|
||||
points: DataTypes.SMALLINT().UNSIGNED,
|
||||
retail_price: DataTypes.BIGINT(),
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
tableName: "catalog",
|
||||
|
||||
//paranoid: true, // softs deletes
|
||||
timestamps: true,
|
||||
//version: true,
|
||||
|
||||
createdAt: "created_at",
|
||||
updatedAt: "updated_at",
|
||||
deletedAt: "deleted_at",
|
||||
|
||||
indexes: [
|
||||
{ name: "catalog_name_idx", fields: ["catalog_name"] },
|
||||
{ name: "id_article_idx", fields: ["id_article"] },
|
||||
{ name: "family_idx", fields: ["family"] },
|
||||
{ name: "family_subfamily_idx", fields: ["family", "subfamily"] },
|
||||
{ name: "updated_at_idx", fields: ["updated_at"] },
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
return Article_Model;
|
||||
};
|
||||
@ -15,7 +15,7 @@ export interface IDTOMapper<S, D> {
|
||||
params: {
|
||||
page: number;
|
||||
limit: number;
|
||||
},
|
||||
}
|
||||
) => IListResponse_DTO<D>;
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { FirebirdModel } from "@/contexts/catalog/infrastructure/firebird/firebird.model";
|
||||
import { Collection, Entity, Result } from "@shared/contexts";
|
||||
import { ValidationError } from "sequelize";
|
||||
import {
|
||||
@ -6,6 +5,7 @@ import {
|
||||
RequiredFieldMissingError,
|
||||
} from "../../domain/errors";
|
||||
import { InfrastructureError } from "../InfrastructureError";
|
||||
import { FirebirdModel } from "../firebird/firebird.model";
|
||||
|
||||
export interface IFirebirdMapper<
|
||||
TModel extends FirebirdModel,
|
||||
|
||||
@ -20,7 +20,7 @@ import { ISequelizeQueryBuilder } from "./queryBuilder/SequelizeQueryBuilder";
|
||||
|
||||
export interface ISequelizeAdapter extends IAdapter {
|
||||
queryBuilder: ISequelizeQueryBuilder;
|
||||
|
||||
startTransaction: () => SequelizeBusinessTransactionType;
|
||||
getModel: (modelName: string) => any;
|
||||
hasModel: (modelName: string) => boolean;
|
||||
}
|
||||
|
||||
@ -4,7 +4,6 @@ import http from "http";
|
||||
import { assign } from "lodash";
|
||||
import { DateTime, Settings } from "luxon";
|
||||
|
||||
import { createFirebirdAdapter } from "@/contexts/common/infrastructure/firebird";
|
||||
import { createSequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||
import { trace } from "console";
|
||||
import { config } from "../../config";
|
||||
@ -92,13 +91,13 @@ const serverConnection = (conn: any) => {
|
||||
};
|
||||
|
||||
const sequelizeConn = createSequelizeAdapter();
|
||||
const firebirdConn = createFirebirdAdapter();
|
||||
//const firebirdConn = createFirebirdAdapter();
|
||||
|
||||
const server: http.Server = http
|
||||
.createServer(app)
|
||||
.once("listening", () =>
|
||||
process.on("SIGINT", () => {
|
||||
firebirdConn.disconnect();
|
||||
//firebirdConn.disconnect();
|
||||
serverStop(server);
|
||||
})
|
||||
)
|
||||
@ -111,32 +110,32 @@ const server: http.Server = http
|
||||
.on("error", serverError);
|
||||
|
||||
try {
|
||||
firebirdConn.sync().then(() => {
|
||||
sequelizeConn.sync({ force: false, alter: true }).then(() => {
|
||||
// Launch server
|
||||
server.listen(currentState.server.port, () => {
|
||||
const now = DateTime.now();
|
||||
logger.info(
|
||||
`Time: ${now.toLocaleString(DateTime.DATETIME_FULL)} ${now.zoneName}`
|
||||
);
|
||||
logger.info(
|
||||
`Launched in: ${now.diff(currentState.launchedAt).toMillis()} ms`
|
||||
);
|
||||
logger.info(`Environment: ${currentState.environment}`);
|
||||
logger.info(`Process PID: ${process.pid}`);
|
||||
logger.info("To shut down your server, press <CTRL> + C at any time");
|
||||
logger.info(
|
||||
`⚡️ Server: http://${currentState.server.hostname}:${currentState.server.port}`
|
||||
);
|
||||
});
|
||||
//firebirdConn.sync().then(() => {
|
||||
sequelizeConn.sync({ force: false, alter: true }).then(() => {
|
||||
// Launch server
|
||||
server.listen(currentState.server.port, () => {
|
||||
const now = DateTime.now();
|
||||
logger.info(
|
||||
`Time: ${now.toLocaleString(DateTime.DATETIME_FULL)} ${now.zoneName}`
|
||||
);
|
||||
logger.info(
|
||||
`Launched in: ${now.diff(currentState.launchedAt).toMillis()} ms`
|
||||
);
|
||||
logger.info(`Environment: ${currentState.environment}`);
|
||||
logger.info(`Process PID: ${process.pid}`);
|
||||
logger.info("To shut down your server, press <CTRL> + C at any time");
|
||||
logger.info(
|
||||
`⚡️ Server: http://${currentState.server.hostname}:${currentState.server.port}`
|
||||
);
|
||||
});
|
||||
});
|
||||
//});
|
||||
} catch (error) {
|
||||
serverError(error);
|
||||
}
|
||||
|
||||
process.on("uncaughtException", (error: any) => {
|
||||
firebirdConn.disconnect();
|
||||
//firebirdConn.disconnect();
|
||||
logger.error(`${new Date().toUTCString()} uncaughtException:`, error.message);
|
||||
logger.error(error.stack);
|
||||
//process.exit(1);
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import { IMoney_Response_DTO } from "../../../../common";
|
||||
|
||||
export interface IListProducts_Response_DTO {
|
||||
export interface IListArticles_Response_DTO {
|
||||
id: string;
|
||||
catalog_name: string;
|
||||
id_article: string;
|
||||
reference: string;
|
||||
family: string;
|
||||
subfamily: string;
|
||||
description: string;
|
||||
points: number;
|
||||
pvp: IMoney_Response_DTO;
|
||||
retail_price: IMoney_Response_DTO;
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export * from "./IListArticles_Response.dto";
|
||||
@ -1 +0,0 @@
|
||||
export * from "./IListProducts_Response.dto";
|
||||
@ -1 +1 @@
|
||||
export * from "./IListProducts.dto";
|
||||
export * from "./IListArticles.dto";
|
||||
|
||||
@ -40,7 +40,8 @@ export class AddressTitle extends StringValueObject {
|
||||
return Result.fail(
|
||||
handleDomainError(
|
||||
DomainError.INVALID_INPUT_DATA,
|
||||
validationResult.error
|
||||
validationResult.error.message,
|
||||
_options
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -40,7 +40,8 @@ export class AddressType extends StringValueObject {
|
||||
return Result.fail(
|
||||
handleDomainError(
|
||||
DomainError.INVALID_INPUT_DATA,
|
||||
validationResult.error
|
||||
validationResult.error.message,
|
||||
_options
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -38,7 +38,8 @@ export class City extends StringValueObject {
|
||||
return Result.fail(
|
||||
handleDomainError(
|
||||
DomainError.INVALID_INPUT_DATA,
|
||||
validationResult.error
|
||||
validationResult.error.message,
|
||||
_options
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -40,7 +40,8 @@ export class Country extends StringValueObject {
|
||||
return Result.fail(
|
||||
handleDomainError(
|
||||
DomainError.INVALID_INPUT_DATA,
|
||||
validationResult.error
|
||||
validationResult.error.message,
|
||||
_options
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -41,7 +41,8 @@ export class PostalCode extends StringValueObject {
|
||||
return Result.fail(
|
||||
handleDomainError(
|
||||
DomainError.INVALID_INPUT_DATA,
|
||||
validationResult.error
|
||||
validationResult.error.message,
|
||||
_options
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -40,7 +40,8 @@ export class Province extends StringValueObject {
|
||||
return Result.fail(
|
||||
handleDomainError(
|
||||
DomainError.INVALID_INPUT_DATA,
|
||||
validationResult.error
|
||||
validationResult.error.message,
|
||||
_options
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -40,7 +40,8 @@ export class Street extends StringValueObject {
|
||||
return Result.fail(
|
||||
handleDomainError(
|
||||
DomainError.INVALID_INPUT_DATA,
|
||||
validationResult.error
|
||||
validationResult.error.message,
|
||||
_options
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -42,7 +42,8 @@ export class Email extends StringValueObject {
|
||||
return Result.fail(
|
||||
handleDomainError(
|
||||
DomainError.INVALID_INPUT_DATA,
|
||||
validationResult.error
|
||||
validationResult.error.message,
|
||||
_options
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -39,7 +39,8 @@ export class Name extends StringValueObject {
|
||||
return Result.fail(
|
||||
handleDomainError(
|
||||
DomainError.INVALID_INPUT_DATA,
|
||||
validationResult.error
|
||||
validationResult.error.message,
|
||||
_options
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -42,7 +42,8 @@ export class Note extends StringValueObject {
|
||||
return Result.fail(
|
||||
handleDomainError(
|
||||
DomainError.INVALID_INPUT_DATA,
|
||||
validationResult.error
|
||||
validationResult.error.message,
|
||||
_options
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -40,7 +40,8 @@ export class Phone extends StringValueObject {
|
||||
return Result.fail(
|
||||
handleDomainError(
|
||||
DomainError.INVALID_INPUT_DATA,
|
||||
validationResult.error
|
||||
validationResult.error.message,
|
||||
_options
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -27,8 +27,8 @@ export class Filter extends ValueObject<IFilterProps> implements IFilter {
|
||||
return Result.ok<Filter>(
|
||||
new Filter({
|
||||
...filterProps,
|
||||
operator: this.sanitizeOperator(filterProps.operator),
|
||||
}),
|
||||
operator: Filter.sanitizeOperator(filterProps.operator),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -26,9 +26,9 @@ export class Order extends ValueObject<IOrderProps> implements IOrder {
|
||||
|
||||
return Result.ok<Order>(
|
||||
new Order({
|
||||
field: this.sanitize(orderProps.field),
|
||||
type: this.sanitize(orderProps.type),
|
||||
}),
|
||||
field: Order.sanitize(orderProps.field),
|
||||
type: Order.sanitize(orderProps.type),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ export class Order extends ValueObject<IOrderProps> implements IOrder {
|
||||
) {
|
||||
let typeOrError = RuleValidator.validate(
|
||||
RuleValidator.RULE_IS_TYPE_STRING,
|
||||
type,
|
||||
type
|
||||
);
|
||||
|
||||
if (typeOrError.isFailure) {
|
||||
@ -63,7 +63,7 @@ export class Order extends ValueObject<IOrderProps> implements IOrder {
|
||||
) {
|
||||
const fieldOrError = RuleValidator.validate(
|
||||
RuleValidator.RULE_IS_TYPE_STRING,
|
||||
field,
|
||||
field
|
||||
);
|
||||
|
||||
if (fieldOrError.isFailure) {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { Result, ValueObject } from "@shared/contexts";
|
||||
import { DomainError } from "../../../errors";
|
||||
import { Result } from "../../Result";
|
||||
import { ValueObject } from "../../ValueObject";
|
||||
import { Order } from "./Order";
|
||||
import { OrderCollection } from "./OrderCollection";
|
||||
|
||||
@ -43,16 +44,14 @@ export class OrderRoot
|
||||
|
||||
return Result.ok<IOrderRoot>(
|
||||
new OrderRoot({
|
||||
type: this.sanitizeOperator(orderRootProps.type),
|
||||
type: OrderRoot.sanitizeOperator(orderRootProps.type),
|
||||
items: OrderCollection.create(orderRootProps.value),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
protected static validate(OrderRootRoot: IOrderRootProps): Result<any> {
|
||||
throw DomainError.create("NOT IMPLEMENT", {
|
||||
function: "OrderRoot.validate()",
|
||||
});
|
||||
throw DomainError.create("NOT IMPLEMENT", "OrderRoot.validate()");
|
||||
|
||||
/*return Validator.isOneOf(
|
||||
{
|
||||
@ -85,7 +84,7 @@ export class OrderRoot
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return `${this.type} ${this.value.toString()}`;
|
||||
return `${this.type} ${this.items.toString()}`;
|
||||
}
|
||||
|
||||
public toObject(): Record<string, any> {
|
||||
|
||||
@ -14,7 +14,7 @@ export class Slug extends StringValueObject {
|
||||
|
||||
protected static validate(
|
||||
value: UndefinedOr<string>,
|
||||
options: IStringValueObjectOptions,
|
||||
options: IStringValueObjectOptions
|
||||
) {
|
||||
const rule = Joi.string()
|
||||
.allow(null)
|
||||
@ -68,7 +68,7 @@ export class Slug extends StringValueObject {
|
||||
|
||||
public static create(
|
||||
value: UndefinedOr<string>,
|
||||
options: IStringValueObjectOptions = {},
|
||||
options: IStringValueObjectOptions = {}
|
||||
) {
|
||||
const _options = {
|
||||
label: "slug",
|
||||
@ -81,7 +81,8 @@ export class Slug extends StringValueObject {
|
||||
return Result.fail(validationResult.error);
|
||||
}
|
||||
|
||||
return Result.ok<Slug>(new Slug(this.sanitize(validationResult.object)));
|
||||
const slugValue = Slug.sanitize(validationResult.object);
|
||||
return Result.ok<Slug>(new Slug(slugValue));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -50,7 +50,8 @@ export class UniqueID extends NullableValueObject<string> {
|
||||
return Result.fail(
|
||||
handleDomainError(
|
||||
DomainError.INVALID_INPUT_DATA,
|
||||
validationResult.error
|
||||
validationResult.error.message,
|
||||
_options
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ export function handleDomainError2(
|
||||
|
||||
export function handleDomainError(
|
||||
code: string,
|
||||
message?: string,
|
||||
message: string = "",
|
||||
payload?: Record<string, any>
|
||||
): DomainError {
|
||||
return DomainError.create(code, message, payload);
|
||||
|
||||
@ -1 +1,2 @@
|
||||
export * from "./contexts";
|
||||
export * from "./utilities";
|
||||
|
||||
Loading…
Reference in New Issue
Block a user