.
This commit is contained in:
parent
c2550e57f2
commit
50a6001252
@ -1,11 +0,0 @@
|
||||
{
|
||||
"printWidth": 130,
|
||||
"tabWidth": 4,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "all",
|
||||
"bracketSpacing": true,
|
||||
"jsxBracketSameLine": true,
|
||||
"arrowParens": "always"
|
||||
}
|
||||
13
.prettierrc
Normal file
13
.prettierrc
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"bracketSpacing": true,
|
||||
"useTabs": false,
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"jsxSingleQuote": true,
|
||||
"jsxBracketSameLine": false,
|
||||
"arrowParens": "always",
|
||||
"rcVerbose": true
|
||||
}
|
||||
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
@ -3,5 +3,10 @@
|
||||
"source.organizeImports": "explicit",
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
"editor.formatOnSave": true
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnPaste": false,
|
||||
"prettier.useEditorConfig": false,
|
||||
"prettier.useTabs": false,
|
||||
"prettier.configPath": ".prettierrc"
|
||||
}
|
||||
|
||||
@ -1,16 +1,12 @@
|
||||
import { AuthUser } from "@/contexts/auth/domain";
|
||||
import { generateExpressErrorResponse } from "@/contexts/common/infrastructure/express/ExpressErrorResponse";
|
||||
import { generateExpressError } from "@/contexts/common/infrastructure/express/ExpressErrorResponse";
|
||||
import Express from "express";
|
||||
import httpStatus from "http-status";
|
||||
import passport from "passport";
|
||||
|
||||
function compose(middlewareArray: any[]) {
|
||||
if (!middlewareArray.length) {
|
||||
return function (
|
||||
req: Express.Request,
|
||||
res: Express.Response,
|
||||
next: Express.NextFunction,
|
||||
) {
|
||||
return function (req: Express.Request, res: Express.Response, next: Express.NextFunction) {
|
||||
next();
|
||||
};
|
||||
}
|
||||
@ -18,11 +14,7 @@ function compose(middlewareArray: any[]) {
|
||||
const head = middlewareArray[0];
|
||||
const tail = middlewareArray.slice(1);
|
||||
|
||||
return function (
|
||||
req: Express.Request,
|
||||
res: Express.Response,
|
||||
next: Express.NextFunction,
|
||||
) {
|
||||
return function (req: Express.Request, res: Express.Response, next: Express.NextFunction) {
|
||||
head(req, res, function (err: unknown) {
|
||||
if (err) return next(err);
|
||||
compose(tail)(req, res, next);
|
||||
@ -37,7 +29,7 @@ export const isLoggedUser = compose([
|
||||
(req: Express.Request, res: Express.Response, next: Express.NextFunction) => {
|
||||
const user = <AuthUser>req.user;
|
||||
if (!user.isUser) {
|
||||
return generateExpressErrorResponse(req, res, httpStatus.UNAUTHORIZED);
|
||||
return generateExpressError(req, res, httpStatus.UNAUTHORIZED);
|
||||
}
|
||||
next();
|
||||
},
|
||||
@ -48,7 +40,7 @@ export const isAdminUser = compose([
|
||||
(req: Express.Request, res: Express.Response, next: Express.NextFunction) => {
|
||||
const user = <AuthUser>req.user;
|
||||
if (!user.isAdmin) {
|
||||
return generateExpressErrorResponse(req, res, httpStatus.UNAUTHORIZED);
|
||||
return generateExpressError(req, res, httpStatus.UNAUTHORIZED);
|
||||
}
|
||||
next();
|
||||
},
|
||||
|
||||
@ -7,19 +7,19 @@ import { Email, Name, UniqueID } from "@shared/contexts";
|
||||
import { AuthUser, IAuthUserProps } from "../../domain/entities";
|
||||
import { IAuthContext } from "../Auth.context";
|
||||
import {
|
||||
AuthUserCreationAttributes,
|
||||
AuthUser_Model,
|
||||
TCreationUser_Attributes,
|
||||
} from "../sequelize/authUser.model";
|
||||
|
||||
export interface IUserMapper
|
||||
extends ISequelizeMapper<
|
||||
AuthUser_Model,
|
||||
TCreationUser_Attributes,
|
||||
AuthUserCreationAttributes,
|
||||
AuthUser
|
||||
> {}
|
||||
|
||||
class AuthUserMapper
|
||||
extends SequelizeMapper<AuthUser_Model, TCreationUser_Attributes, AuthUser>
|
||||
extends SequelizeMapper<AuthUser_Model, AuthUserCreationAttributes, AuthUser>
|
||||
implements IUserMapper
|
||||
{
|
||||
public constructor(props: { context: IAuthContext }) {
|
||||
|
||||
@ -6,7 +6,8 @@ import {
|
||||
Sequelize,
|
||||
} from "sequelize";
|
||||
|
||||
export type TCreationUser_Attributes = InferCreationAttributes<AuthUser_Model>;
|
||||
export type AuthUserCreationAttributes =
|
||||
InferCreationAttributes<AuthUser_Model>;
|
||||
|
||||
export class AuthUser_Model extends Model<
|
||||
InferAttributes<AuthUser_Model>,
|
||||
|
||||
@ -4,7 +4,7 @@ import { registerCatalogRepository } from "../../../Catalog.repository";
|
||||
import { ListArticlesController } from "./ListArticlesController";
|
||||
import { listArticlesPresenter } from "./presenter";
|
||||
|
||||
export const createListArticlesController = (context: ICatalogContext) => {
|
||||
export const listArticlesController = (context: ICatalogContext) => {
|
||||
registerCatalogRepository(context);
|
||||
|
||||
return new ListArticlesController(
|
||||
@ -12,6 +12,6 @@ export const createListArticlesController = (context: ICatalogContext) => {
|
||||
useCase: new ListArticlesUseCase(context),
|
||||
presenter: listArticlesPresenter,
|
||||
},
|
||||
context,
|
||||
context
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { applyMiddleware } from "@/contexts/common/infrastructure/express";
|
||||
import Express from "express";
|
||||
import { createListArticlesController } from "./controllers";
|
||||
import { listArticlesController } from "./controllers";
|
||||
|
||||
/*catalogRoutes.get(
|
||||
"/:articleId",
|
||||
@ -15,11 +15,7 @@ export const CatalogRouter = (appRouter: Express.Router) => {
|
||||
"/",
|
||||
applyMiddleware("isLoggedUser"),
|
||||
(req: Express.Request, res: Express.Response, next: Express.NextFunction) =>
|
||||
createListArticlesController(res.locals["context"]).execute(
|
||||
req,
|
||||
res,
|
||||
next,
|
||||
),
|
||||
listArticlesController(res.locals["context"]).execute(req, res, next)
|
||||
);
|
||||
|
||||
appRouter.use("/catalog", catalogRoutes);
|
||||
|
||||
@ -16,19 +16,15 @@ import {
|
||||
IArticleProps,
|
||||
} from "../../domain/entities";
|
||||
import {
|
||||
ArticleCreationAttributes,
|
||||
Article_Model,
|
||||
TCreationArticle_Attributes,
|
||||
} from "../sequelize/article.model";
|
||||
|
||||
export interface IArticleMapper
|
||||
extends ISequelizeMapper<
|
||||
Article_Model,
|
||||
TCreationArticle_Attributes,
|
||||
Article
|
||||
> {}
|
||||
extends ISequelizeMapper<Article_Model, ArticleCreationAttributes, Article> {}
|
||||
|
||||
class ArticleMapper
|
||||
extends SequelizeMapper<Article_Model, TCreationArticle_Attributes, Article>
|
||||
extends SequelizeMapper<Article_Model, ArticleCreationAttributes, Article>
|
||||
implements IArticleMapper
|
||||
{
|
||||
public constructor(props: { context: ICatalogContext }) {
|
||||
|
||||
@ -8,8 +8,7 @@ import {
|
||||
Sequelize,
|
||||
} from "sequelize";
|
||||
|
||||
export type TCreationArticle_Attributes =
|
||||
InferCreationAttributes<Article_Model>;
|
||||
export type ArticleCreationAttributes = InferCreationAttributes<Article_Model>;
|
||||
|
||||
export class Article_Model extends Model<
|
||||
InferAttributes<Article_Model>,
|
||||
|
||||
@ -5,7 +5,7 @@ import { URL } from "url";
|
||||
import { IServerError } from "../../domain/errors";
|
||||
import { IController } from "../Controller.interface";
|
||||
import { InfrastructureError } from "../InfrastructureError";
|
||||
import { generateExpressErrorResponse } from "./ExpressErrorResponse";
|
||||
import { generateExpressError } from "./ExpressErrorResponse";
|
||||
|
||||
export abstract class ExpressController implements IController {
|
||||
protected req: express.Request;
|
||||
@ -17,19 +17,13 @@ export abstract class ExpressController implements IController {
|
||||
|
||||
protected abstract executeImpl(): Promise<void | any>;
|
||||
|
||||
public execute(
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
next: express.NextFunction,
|
||||
): void {
|
||||
public execute(req: express.Request, res: express.Response, next: express.NextFunction): void {
|
||||
this.req = req;
|
||||
this.res = res;
|
||||
this.next = next;
|
||||
|
||||
this.serverURL = `${
|
||||
new URL(
|
||||
`${this.req.protocol}://${this.req.get("host")}${this.req.originalUrl}`,
|
||||
).origin
|
||||
new URL(`${this.req.protocol}://${this.req.get("host")}${this.req.originalUrl}`).origin
|
||||
}/api/v1`;
|
||||
|
||||
this.file = this.req && this.req["file"]; // <-- ????
|
||||
@ -100,11 +94,7 @@ export abstract class ExpressController implements IController {
|
||||
}
|
||||
|
||||
public internalServerError(message?: string, error?: IServerError) {
|
||||
return this._errorResponse(
|
||||
httpStatus.INTERNAL_SERVER_ERROR,
|
||||
message,
|
||||
error,
|
||||
);
|
||||
return this._errorResponse(httpStatus.INTERNAL_SERVER_ERROR, message, error);
|
||||
}
|
||||
|
||||
public todoError(message?: string) {
|
||||
@ -115,24 +105,15 @@ export abstract class ExpressController implements IController {
|
||||
return this._errorResponse(httpStatus.SERVICE_UNAVAILABLE, message);
|
||||
}
|
||||
|
||||
private _jsonResponse(
|
||||
statusCode: number,
|
||||
jsonPayload: any,
|
||||
): express.Response<any> {
|
||||
private _jsonResponse(statusCode: number, jsonPayload: any): express.Response<any> {
|
||||
return this.res.status(statusCode).json(jsonPayload).send();
|
||||
}
|
||||
|
||||
private _errorResponse(
|
||||
statusCode: number,
|
||||
message?: string,
|
||||
error?: Error | InfrastructureError,
|
||||
error?: Error | InfrastructureError
|
||||
): express.Response<IError_Response_DTO> {
|
||||
return generateExpressErrorResponse(
|
||||
this.req,
|
||||
this.res,
|
||||
statusCode,
|
||||
message,
|
||||
error,
|
||||
);
|
||||
return generateExpressError(this.req, this.res, statusCode, message, error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,18 +1,15 @@
|
||||
import {
|
||||
IErrorExtra_Response_DTO,
|
||||
IError_Response_DTO,
|
||||
} from "@shared/contexts";
|
||||
import { IErrorExtra_Response_DTO, IError_Response_DTO } from "@shared/contexts";
|
||||
import Express from "express";
|
||||
import { UseCaseError } from "../../application";
|
||||
import { InfrastructureError } from "../InfrastructureError";
|
||||
import { ProblemDocument, ProblemDocumentExtension } from "./ProblemDocument";
|
||||
|
||||
export const generateExpressErrorResponse = (
|
||||
export const generateExpressError = (
|
||||
req: Express.Request,
|
||||
res: Express.Response,
|
||||
statusCode: number,
|
||||
message?: string,
|
||||
error?: Error | InfrastructureError,
|
||||
error?: Error | InfrastructureError
|
||||
): Express.Response<IError_Response_DTO> => {
|
||||
const context = {
|
||||
user: res.locals.user || undefined,
|
||||
@ -23,7 +20,7 @@ export const generateExpressErrorResponse = (
|
||||
|
||||
const extension = new ProblemDocumentExtension({
|
||||
context,
|
||||
extra: error ? { ...generateExpressError(error) } : {},
|
||||
extra: error ? { ..._extractExtraInfoFromError(error) } : {},
|
||||
});
|
||||
|
||||
const jsonPayload = new ProblemDocument(
|
||||
@ -32,15 +29,13 @@ export const generateExpressErrorResponse = (
|
||||
detail: message,
|
||||
instance: req.baseUrl,
|
||||
},
|
||||
extension,
|
||||
extension
|
||||
);
|
||||
|
||||
return res.status(statusCode).json(jsonPayload).send();
|
||||
};
|
||||
|
||||
function generateExpressError(
|
||||
error: Error | InfrastructureError,
|
||||
): IErrorExtra_Response_DTO {
|
||||
function _extractExtraInfoFromError(error: Error | InfrastructureError): IErrorExtra_Response_DTO {
|
||||
const useCaseError = error as UseCaseError;
|
||||
|
||||
const payload = Array.isArray(useCaseError.payload)
|
||||
|
||||
@ -1,37 +1,27 @@
|
||||
import { Collection, Entity, ICollection, Result } from "@shared/contexts";
|
||||
import { Model, ValidationError } from "sequelize";
|
||||
import {
|
||||
FieldValueError,
|
||||
RequiredFieldMissingError,
|
||||
} from "../../domain/errors";
|
||||
import { FieldValueError, RequiredFieldMissingError } from "../../domain/errors";
|
||||
import { InfrastructureError } from "../InfrastructureError";
|
||||
|
||||
export type MapperParamsType = Record<string, any>;
|
||||
|
||||
export interface ISequelizeMapper<
|
||||
TModel extends Model,
|
||||
TModelAttributes,
|
||||
TEntity extends Entity<any>,
|
||||
> {
|
||||
mapToDomain(source: TModel, params?: Record<string, any>): TEntity;
|
||||
|
||||
mapArrayToDomain(
|
||||
source: TModel[],
|
||||
params?: Record<string, any>
|
||||
): Collection<TEntity>;
|
||||
|
||||
mapToDomain(source: TModel, params?: MapperParamsType): TEntity;
|
||||
mapArrayToDomain(source: TModel[], params?: MapperParamsType): Collection<TEntity>;
|
||||
mapArrayAndCountToDomain(
|
||||
source: TModel[],
|
||||
totalCount: number,
|
||||
params?: Record<string, any>
|
||||
params?: MapperParamsType
|
||||
): Collection<TEntity>;
|
||||
|
||||
mapToPersistence(
|
||||
source: TEntity,
|
||||
params?: Record<string, any>
|
||||
): TModelAttributes;
|
||||
|
||||
mapToPersistence(source: TEntity, params?: MapperParamsType): TModelAttributes;
|
||||
mapCollectionToPersistence(
|
||||
source: ICollection<TEntity>,
|
||||
params?: Record<string, any>
|
||||
params?: MapperParamsType
|
||||
): TModelAttributes[];
|
||||
}
|
||||
|
||||
@ -43,64 +33,49 @@ export abstract class SequelizeMapper<
|
||||
{
|
||||
public constructor(protected props: any) {}
|
||||
|
||||
public mapToDomain(source: TModel, params?: Record<string, any>): TEntity {
|
||||
public mapToDomain(source: TModel, params?: MapperParamsType): TEntity {
|
||||
return this.toDomainMappingImpl(source, params);
|
||||
}
|
||||
|
||||
public mapArrayToDomain(
|
||||
source: TModel[],
|
||||
params?: Record<string, any>
|
||||
): Collection<TEntity> {
|
||||
return this.mapArrayAndCountToDomain(
|
||||
source,
|
||||
source ? source.length : 0,
|
||||
params
|
||||
);
|
||||
public mapArrayToDomain(source: TModel[], params?: MapperParamsType): Collection<TEntity> {
|
||||
return this.mapArrayAndCountToDomain(source, source ? source.length : 0, params);
|
||||
}
|
||||
|
||||
public mapArrayAndCountToDomain(
|
||||
source: TModel[],
|
||||
totalCount: number,
|
||||
params?: Record<string, any>
|
||||
params?: MapperParamsType
|
||||
): Collection<TEntity> {
|
||||
const items = source
|
||||
? source.map((value, index: number) =>
|
||||
this.toDomainMappingImpl!(value, { index, ...params })
|
||||
)
|
||||
? source.map((value, index: number) => this.toDomainMappingImpl!(value, { index, ...params }))
|
||||
: [];
|
||||
|
||||
return new Collection(items, totalCount);
|
||||
}
|
||||
|
||||
public mapToPersistence(
|
||||
source: TEntity,
|
||||
params?: Record<string, any>
|
||||
): TModelAttributes {
|
||||
public mapToPersistence(source: TEntity, params?: MapperParamsType): TModelAttributes {
|
||||
return this.toPersistenceMappingImpl(source, params);
|
||||
}
|
||||
|
||||
public mapCollectionToPersistence(
|
||||
source: ICollection<TEntity>,
|
||||
params?: Record<string, any>
|
||||
params?: MapperParamsType
|
||||
): TModelAttributes[] {
|
||||
return source.items.map((value: TEntity, index: number) =>
|
||||
this.toPersistenceMappingImpl!(value, { index, ...params })
|
||||
);
|
||||
}
|
||||
|
||||
protected toDomainMappingImpl(
|
||||
source: TModel,
|
||||
params?: Record<string, any>
|
||||
): TEntity {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
protected toDomainMappingImpl(source: TModel, params?: MapperParamsType): TEntity {
|
||||
throw InfrastructureError.create(
|
||||
InfrastructureError.UNEXCEPTED_ERROR,
|
||||
'Method "toDomainMappingImpl" not implemented!'
|
||||
);
|
||||
}
|
||||
|
||||
protected toPersistenceMappingImpl(
|
||||
source: TEntity,
|
||||
params?: Record<string, any>
|
||||
): TModelAttributes {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
protected toPersistenceMappingImpl(source: TEntity, params?: MapperParamsType): TModelAttributes {
|
||||
throw InfrastructureError.create(
|
||||
InfrastructureError.UNEXCEPTED_ERROR,
|
||||
'Method "toPersistenceMappingImpl" not implemented!'
|
||||
@ -118,20 +93,24 @@ export abstract class SequelizeMapper<
|
||||
protected mapsValue(
|
||||
row: TModel,
|
||||
key: string,
|
||||
customMapFn: (
|
||||
value: any,
|
||||
params: Record<string, any>
|
||||
) => Result<any, Error>,
|
||||
params: Record<string, any> = {
|
||||
customMapFn: (value: any, params: MapperParamsType) => Result<any, Error>,
|
||||
params: MapperParamsType = {
|
||||
defaultValue: null,
|
||||
}
|
||||
) {
|
||||
let value = params.defaultValue;
|
||||
const value = row?.dataValues[key] ?? params.defaultValue;
|
||||
const valueOrError = customMapFn(value, params);
|
||||
|
||||
if (valueOrError.isFailure) {
|
||||
this._handleFailure(valueOrError.error, key);
|
||||
}
|
||||
|
||||
return valueOrError.object;
|
||||
|
||||
/*let value = params.defaultValue;
|
||||
|
||||
if (!row || typeof row !== "object") {
|
||||
console.debug(
|
||||
`Data row has not keys! Key ${key} not exists in data row!`
|
||||
);
|
||||
console.debug(`Data row has not keys! Key ${key} not exists in data row!`);
|
||||
} else if (!Object.hasOwn(row.dataValues, key)) {
|
||||
console.debug(`Key ${key} not exists in data row!`);
|
||||
} else {
|
||||
@ -143,16 +122,46 @@ export abstract class SequelizeMapper<
|
||||
if (valueOrError.isFailure) {
|
||||
this.handleFailure(valueOrError.error, key);
|
||||
}
|
||||
return valueOrError.object;
|
||||
return valueOrError.object;*/
|
||||
}
|
||||
|
||||
protected mapsAssociation(
|
||||
row: TModel,
|
||||
associationName: string,
|
||||
customMapper: any,
|
||||
params: Record<string, any> = {}
|
||||
params: MapperParamsType = {}
|
||||
) {
|
||||
if (!customMapper) {
|
||||
if (!customMapper)
|
||||
throw InfrastructureError.create(
|
||||
InfrastructureError.UNEXCEPTED_ERROR,
|
||||
'Custom mapper undefined at "mapsAssociation"!'
|
||||
);
|
||||
|
||||
const { filter, ...otherParams } = params;
|
||||
let associationRows = row?.dataValues[associationName] ?? [];
|
||||
|
||||
if (filter)
|
||||
associationRows = Array.isArray(associationRows)
|
||||
? associationRows.filter(filter)
|
||||
: filter(associationRows);
|
||||
|
||||
const customMapFn = Array.isArray(associationRows)
|
||||
? customMapper.mapArrayToDomain
|
||||
: customMapper.mapToDomain;
|
||||
|
||||
if (!customMapFn)
|
||||
throw InfrastructureError.create(
|
||||
InfrastructureError.UNEXCEPTED_ERROR,
|
||||
'Custom mapper function undefined at "mapsAssociation"!'
|
||||
);
|
||||
|
||||
const associatedDataOrError = customMapFn(associationRows, otherParams);
|
||||
|
||||
if (associatedDataOrError.isFailure)
|
||||
this._handleFailure(associatedDataOrError.error, associationName);
|
||||
return associatedDataOrError.object;
|
||||
|
||||
/*if (!customMapper) {
|
||||
throw InfrastructureError.create(
|
||||
InfrastructureError.UNEXCEPTED_ERROR,
|
||||
'Custom mapper undefined at "mapsAssociation"!'
|
||||
@ -195,13 +204,10 @@ export abstract class SequelizeMapper<
|
||||
if (associatedDataOrError.isFailure) {
|
||||
this.handleFailure(associatedDataOrError.error, associationName);
|
||||
}
|
||||
return associatedDataOrError.object;
|
||||
|
||||
//const associatedData = row[association.accessors.get]();
|
||||
//return associatedData;
|
||||
return associatedDataOrError.object;*/
|
||||
}
|
||||
|
||||
private handleFailure(error: Error, key: string) {
|
||||
private _handleFailure(error: Error, key: string) {
|
||||
if (error instanceof ValidationError) {
|
||||
this.handleInvalidFieldError(key, error);
|
||||
} else {
|
||||
|
||||
143
server/src/contexts/sales/application/CreateDealer.useCase.ts
Normal file
143
server/src/contexts/sales/application/CreateDealer.useCase.ts
Normal file
@ -0,0 +1,143 @@
|
||||
import { IUseCase, IUseCaseError, UseCaseError } from "@/contexts/common/application";
|
||||
import { IRepositoryManager, Password } from "@/contexts/common/domain";
|
||||
import { IInfrastructureError } from "@/contexts/common/infrastructure";
|
||||
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||
import {
|
||||
DomainError,
|
||||
Email,
|
||||
ICreateDealer_Request_DTO,
|
||||
IDomainError,
|
||||
Name,
|
||||
Result,
|
||||
UniqueID,
|
||||
ensureIdIsValid,
|
||||
ensureNameIsValid,
|
||||
} from "@shared/contexts";
|
||||
import { Dealer, IDealerRepository } from "../domain";
|
||||
|
||||
export type CreateDealerResponseOrError =
|
||||
| Result<never, IUseCaseError> // Misc errors (value objects)
|
||||
| Result<Dealer, never>; // Success!
|
||||
|
||||
export class CreateDealerUseCase
|
||||
implements IUseCase<ICreateDealer_Request_DTO, Promise<CreateDealerResponseOrError>>
|
||||
{
|
||||
private _adapter: ISequelizeAdapter;
|
||||
private _repositoryManager: IRepositoryManager;
|
||||
|
||||
constructor(props: { adapter: ISequelizeAdapter; repositoryManager: IRepositoryManager }) {
|
||||
this._adapter = props.adapter;
|
||||
this._repositoryManager = props.repositoryManager;
|
||||
}
|
||||
|
||||
async execute(request: ICreateDealer_Request_DTO) {
|
||||
const { id, name } = request;
|
||||
|
||||
// Validaciones de datos
|
||||
const idOrError = ensureIdIsValid(id);
|
||||
if (idOrError.isFailure) {
|
||||
const message = idOrError.error.message; //`Dealer ID ${dealerDTO.id} is not valid`;
|
||||
return Result.fail(
|
||||
UseCaseError.create(UseCaseError.INVALID_INPUT_DATA, message, [{ path: "id" }])
|
||||
);
|
||||
}
|
||||
|
||||
const nameOrError = ensureNameIsValid(name);
|
||||
if (nameOrError.isFailure) {
|
||||
const message = nameOrError.error.message; //`Dealer ID ${dealerDTO.id} is not valid`;
|
||||
return Result.fail(
|
||||
UseCaseError.create(UseCaseError.INVALID_INPUT_DATA, message, [{ path: "name" }])
|
||||
);
|
||||
}
|
||||
|
||||
// Comprobar que no existe un usuario previo con esos datos
|
||||
const dealerRepository = this._getDealerRepository();
|
||||
|
||||
const idExists = await dealerRepository().exists(idOrError.object);
|
||||
if (idExists) {
|
||||
const message = `Another dealer with same ID exists`;
|
||||
return Result.fail(
|
||||
UseCaseError.create(UseCaseError.RESOURCE_ALREADY_EXITS, message, {
|
||||
path: "id",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Crear dealer
|
||||
const dealerOrError = this._tryCreateDealerInstance(request, idOrError.object);
|
||||
|
||||
if (dealerOrError.isFailure) {
|
||||
const { error: domainError } = dealerOrError;
|
||||
let errorCode = "";
|
||||
let message = "";
|
||||
|
||||
switch (domainError.code) {
|
||||
case DomainError.INVALID_INPUT_DATA:
|
||||
errorCode = UseCaseError.INVALID_INPUT_DATA;
|
||||
message = "El usuario tiene algún dato erróneo.";
|
||||
break;
|
||||
|
||||
default:
|
||||
errorCode = UseCaseError.UNEXCEPTED_ERROR;
|
||||
message = domainError.message;
|
||||
break;
|
||||
}
|
||||
|
||||
return Result.fail(UseCaseError.create(errorCode, message, domainError));
|
||||
}
|
||||
|
||||
return this._saveDealer(dealerOrError.object);
|
||||
}
|
||||
|
||||
private async _saveDealer(dealer: Dealer) {
|
||||
// Guardar el contacto
|
||||
const transaction = this._adapter.startTransaction();
|
||||
const dealerRepository = this._getDealerRepository();
|
||||
let dealerRepo: IDealerRepository;
|
||||
|
||||
try {
|
||||
await transaction.complete(async (t) => {
|
||||
dealerRepo = dealerRepository({ transaction: t });
|
||||
await dealerRepo.create(dealer);
|
||||
});
|
||||
|
||||
return Result.ok<Dealer>(dealer);
|
||||
} catch (error: unknown) {
|
||||
const _error = error as IInfrastructureError;
|
||||
return Result.fail(UseCaseError.create(UseCaseError.REPOSITORY_ERROR, _error.message));
|
||||
}
|
||||
}
|
||||
|
||||
private _tryCreateDealerInstance(
|
||||
dealerDTO: ICreateDealer_Request_DTO,
|
||||
dealerId: UniqueID
|
||||
): Result<Dealer, IDomainError> {
|
||||
const nameOrError = Name.create(dealerDTO.name);
|
||||
if (nameOrError.isFailure) {
|
||||
return Result.fail(nameOrError.error);
|
||||
}
|
||||
|
||||
const emailOrError = Email.create(dealerDTO.email);
|
||||
if (emailOrError.isFailure) {
|
||||
return Result.fail(emailOrError.error);
|
||||
}
|
||||
|
||||
const passwordOrError = Password.createFromPlainTextPassword(dealerDTO.password);
|
||||
if (passwordOrError.isFailure) {
|
||||
return Result.fail(passwordOrError.error);
|
||||
}
|
||||
|
||||
return Dealer.create(
|
||||
{
|
||||
name: nameOrError.object,
|
||||
email: emailOrError.object,
|
||||
password: passwordOrError.object,
|
||||
},
|
||||
dealerId
|
||||
);
|
||||
}
|
||||
|
||||
private _getDealerRepository() {
|
||||
return this._repositoryManager.getRepository<IDealerRepository>("Dealer");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
import {
|
||||
IUseCase,
|
||||
IUseCaseError,
|
||||
IUseCaseRequest,
|
||||
UseCaseError,
|
||||
} from "@/contexts/common/application/useCases";
|
||||
import { IRepositoryManager } from "@/contexts/common/domain";
|
||||
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||
import { Result, UniqueID } from "@shared/contexts";
|
||||
import { IDealerRepository } from "../domain";
|
||||
|
||||
export interface IDeleteDealerUseCaseRequest extends IUseCaseRequest {
|
||||
id: UniqueID;
|
||||
}
|
||||
|
||||
export type DeleteDealerResponseOrError =
|
||||
| Result<never, IUseCaseError> // Misc errors (value objects)
|
||||
| Result<void, never>; // Success!
|
||||
|
||||
export class DeleteDealerUseCase
|
||||
implements IUseCase<IDeleteDealerUseCaseRequest, Promise<DeleteDealerResponseOrError>>
|
||||
{
|
||||
private _adapter: ISequelizeAdapter;
|
||||
private _repositoryManager: IRepositoryManager;
|
||||
|
||||
constructor(props: { adapter: ISequelizeAdapter; repositoryManager: IRepositoryManager }) {
|
||||
this._adapter = props.adapter;
|
||||
this._repositoryManager = props.repositoryManager;
|
||||
}
|
||||
|
||||
private getRepositoryByName<T>(name: string) {
|
||||
return this._repositoryManager.getRepository<T>(name);
|
||||
}
|
||||
|
||||
async execute(request: IDeleteDealerUseCaseRequest): Promise<DeleteDealerResponseOrError> {
|
||||
const { id: dealerId } = request;
|
||||
|
||||
const transaction = this._adapter.startTransaction();
|
||||
const dealerRepoBuilder = this.getRepositoryByName<IDealerRepository>("Dealer");
|
||||
|
||||
try {
|
||||
await transaction.complete(async (t) => {
|
||||
const invoiceRepo = dealerRepoBuilder({ transaction: t });
|
||||
await invoiceRepo.removeById(dealerId);
|
||||
});
|
||||
|
||||
return Result.ok<void>();
|
||||
} catch (error: unknown) {
|
||||
//const _error = error as IInfrastructureError;
|
||||
return Result.fail(
|
||||
UseCaseError.create(UseCaseError.REPOSITORY_ERROR, "Error al eliminar el usuario")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
71
server/src/contexts/sales/application/GetDealer.useCase.ts
Normal file
71
server/src/contexts/sales/application/GetDealer.useCase.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import {
|
||||
IUseCase,
|
||||
IUseCaseError,
|
||||
IUseCaseRequest,
|
||||
UseCaseError,
|
||||
} from "@/contexts/common/application/useCases";
|
||||
import { IRepositoryManager } from "@/contexts/common/domain";
|
||||
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||
import { Result, UniqueID } from "@shared/contexts";
|
||||
import { IDealerRepository } from "../domain";
|
||||
|
||||
import { IInfrastructureError } from "@/contexts/common/infrastructure";
|
||||
import { Dealer } from "../domain/entities/Dealer";
|
||||
|
||||
export interface IGetDealerUseCaseRequest extends IUseCaseRequest {
|
||||
id: UniqueID;
|
||||
}
|
||||
|
||||
export type GetDealerResponseOrError =
|
||||
| Result<never, IUseCaseError> // Misc errors (value objects)
|
||||
| Result<Dealer, never>; // Success!
|
||||
|
||||
export class GetDealerUseCase
|
||||
implements IUseCase<IGetDealerUseCaseRequest, Promise<GetDealerResponseOrError>>
|
||||
{
|
||||
private _adapter: ISequelizeAdapter;
|
||||
private _repositoryManager: IRepositoryManager;
|
||||
|
||||
constructor(props: { adapter: ISequelizeAdapter; repositoryManager: IRepositoryManager }) {
|
||||
this._adapter = props.adapter;
|
||||
this._repositoryManager = props.repositoryManager;
|
||||
}
|
||||
|
||||
private getRepositoryByName<T>(name: string) {
|
||||
return this._repositoryManager.getRepository<T>(name);
|
||||
}
|
||||
|
||||
async execute(request: IGetDealerUseCaseRequest): Promise<GetDealerResponseOrError> {
|
||||
const { id } = request;
|
||||
|
||||
// Validación de datos
|
||||
// No hay en este caso
|
||||
|
||||
return await this.findDealer(id);
|
||||
}
|
||||
|
||||
private async findDealer(id: UniqueID) {
|
||||
const transaction = this._adapter.startTransaction();
|
||||
const dealerRepoBuilder = this.getRepositoryByName<IDealerRepository>("Dealer");
|
||||
|
||||
let dealer: Dealer | null = null;
|
||||
|
||||
try {
|
||||
await transaction.complete(async (t) => {
|
||||
const dealerRepo = dealerRepoBuilder({ transaction: t });
|
||||
dealer = await dealerRepo.getById(id);
|
||||
});
|
||||
|
||||
if (!dealer) {
|
||||
return Result.fail(UseCaseError.create(UseCaseError.NOT_FOUND_ERROR, "Dealer not found"));
|
||||
}
|
||||
|
||||
return Result.ok<Dealer>(dealer!);
|
||||
} catch (error: unknown) {
|
||||
const _error = error as IInfrastructureError;
|
||||
return Result.fail(
|
||||
UseCaseError.create(UseCaseError.REPOSITORY_ERROR, "Error al consultar el usuario", _error)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
57
server/src/contexts/sales/application/ListDealers.useCase.ts
Normal file
57
server/src/contexts/sales/application/ListDealers.useCase.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { IUseCase, IUseCaseError, UseCaseError } from "@/contexts/common/application/useCases";
|
||||
import { IRepositoryManager } from "@/contexts/common/domain";
|
||||
import { Collection, ICollection, IQueryCriteria, Result } from "@shared/contexts";
|
||||
|
||||
import { IInfrastructureError } from "@/contexts/common/infrastructure";
|
||||
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||
import { Dealer } from "../domain";
|
||||
import { IDealerRepository } from "../domain/repository";
|
||||
|
||||
export interface IListDealersParams {
|
||||
queryCriteria: IQueryCriteria;
|
||||
}
|
||||
|
||||
export type ListDealersResult =
|
||||
| Result<never, IUseCaseError> // Misc errors (value objects)
|
||||
| Result<ICollection<Dealer>, never>; // Success!
|
||||
|
||||
export class ListDealersUseCase
|
||||
implements IUseCase<IListDealersParams, Promise<ListDealersResult>>
|
||||
{
|
||||
private _adapter: ISequelizeAdapter;
|
||||
private _repositoryManager: IRepositoryManager;
|
||||
|
||||
constructor(props: { adapter: ISequelizeAdapter; repositoryManager: IRepositoryManager }) {
|
||||
this._adapter = props.adapter;
|
||||
this._repositoryManager = props.repositoryManager;
|
||||
}
|
||||
|
||||
private getRepositoryByName<T>(name: string) {
|
||||
return this._repositoryManager.getRepository<T>(name);
|
||||
}
|
||||
|
||||
async execute(params: Partial<IListDealersParams>): Promise<ListDealersResult> {
|
||||
const { queryCriteria } = params;
|
||||
|
||||
return this.findDealers(queryCriteria);
|
||||
}
|
||||
|
||||
private async findDealers(queryCriteria) {
|
||||
const transaction = this._adapter.startTransaction();
|
||||
const dealerRepoBuilder = this.getRepositoryByName<IDealerRepository>("Dealer");
|
||||
|
||||
let dealers: ICollection<Dealer> = new Collection();
|
||||
|
||||
try {
|
||||
await transaction.complete(async (t) => {
|
||||
dealers = await dealerRepoBuilder({ transaction: t }).findAll(queryCriteria);
|
||||
});
|
||||
return Result.ok(dealers);
|
||||
} catch (error: unknown) {
|
||||
const _error = error as IInfrastructureError;
|
||||
return Result.fail(
|
||||
UseCaseError.create(UseCaseError.REPOSITORY_ERROR, "Error al listar los usurios", _error)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
134
server/src/contexts/sales/application/UpdateDealer.useCase.ts
Normal file
134
server/src/contexts/sales/application/UpdateDealer.useCase.ts
Normal file
@ -0,0 +1,134 @@
|
||||
import {
|
||||
IUseCase,
|
||||
IUseCaseError,
|
||||
IUseCaseRequest,
|
||||
UseCaseError,
|
||||
} from "@/contexts/common/application";
|
||||
import { IRepositoryManager, Password } from "@/contexts/common/domain";
|
||||
import { IInfrastructureError } from "@/contexts/common/infrastructure";
|
||||
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||
import {
|
||||
DomainError,
|
||||
Email,
|
||||
IDomainError,
|
||||
IUpdateDealer_Request_DTO,
|
||||
Name,
|
||||
Result,
|
||||
UniqueID,
|
||||
} from "@shared/contexts";
|
||||
import { Dealer, IDealerRepository } from "../domain";
|
||||
|
||||
export interface IUpdateDealerUseCaseRequest extends IUseCaseRequest {
|
||||
id: UniqueID;
|
||||
dealerDTO: IUpdateDealer_Request_DTO;
|
||||
}
|
||||
|
||||
export type UpdateDealerResponseOrError =
|
||||
| Result<never, IUseCaseError> // Misc errors (value objects)
|
||||
| Result<Dealer, never>; // Success!
|
||||
|
||||
export class UpdateDealerUseCase
|
||||
implements IUseCase<IUpdateDealerUseCaseRequest, Promise<UpdateDealerResponseOrError>>
|
||||
{
|
||||
private _adapter: ISequelizeAdapter;
|
||||
private _repositoryManager: IRepositoryManager;
|
||||
|
||||
constructor(props: { adapter: ISequelizeAdapter; repositoryManager: IRepositoryManager }) {
|
||||
this._adapter = props.adapter;
|
||||
this._repositoryManager = props.repositoryManager;
|
||||
}
|
||||
|
||||
async execute(request: IUpdateDealerUseCaseRequest): Promise<UpdateDealerResponseOrError> {
|
||||
const { id, dealerDTO } = request;
|
||||
const dealerRepository = this._getDealerRepository();
|
||||
|
||||
// Comprobar que existe el dealer
|
||||
const idExists = await dealerRepository().exists(id);
|
||||
if (!idExists) {
|
||||
const message = `Dealer ID not found`;
|
||||
return Result.fail(
|
||||
UseCaseError.create(UseCaseError.NOT_FOUND_ERROR, message, {
|
||||
path: "id",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Crear usuario
|
||||
const dealerOrError = this._tryCreateDealerInstance(dealerDTO, id);
|
||||
|
||||
if (dealerOrError.isFailure) {
|
||||
const { error: domainError } = dealerOrError;
|
||||
let errorCode = "";
|
||||
let message = "";
|
||||
|
||||
switch (domainError.code) {
|
||||
// Errores manuales
|
||||
case DomainError.INVALID_INPUT_DATA:
|
||||
errorCode = UseCaseError.INVALID_INPUT_DATA;
|
||||
message = "El usuario tiene algún dato erróneo.";
|
||||
break;
|
||||
|
||||
default:
|
||||
errorCode = UseCaseError.UNEXCEPTED_ERROR;
|
||||
message = domainError.message;
|
||||
break;
|
||||
}
|
||||
|
||||
return Result.fail(UseCaseError.create(errorCode, message, domainError));
|
||||
}
|
||||
|
||||
return this._updateDealer(dealerOrError.object);
|
||||
}
|
||||
|
||||
private async _updateDealer(dealer: Dealer) {
|
||||
// Guardar el contacto
|
||||
const transaction = this._adapter.startTransaction();
|
||||
const dealerRepository = this._getDealerRepository();
|
||||
let dealerRepo: IDealerRepository;
|
||||
|
||||
try {
|
||||
await transaction.complete(async (t) => {
|
||||
dealerRepo = dealerRepository({ transaction: t });
|
||||
await dealerRepo.update(dealer);
|
||||
});
|
||||
|
||||
return Result.ok<Dealer>(dealer);
|
||||
} catch (error: unknown) {
|
||||
const _error = error as IInfrastructureError;
|
||||
return Result.fail(UseCaseError.create(UseCaseError.REPOSITORY_ERROR, _error.message));
|
||||
}
|
||||
}
|
||||
|
||||
private _tryCreateDealerInstance(
|
||||
dealerDTO: IUpdateDealer_Request_DTO,
|
||||
dealerId: UniqueID
|
||||
): Result<Dealer, IDomainError> {
|
||||
const nameOrError = Name.create(dealerDTO.name);
|
||||
if (nameOrError.isFailure) {
|
||||
return Result.fail(nameOrError.error);
|
||||
}
|
||||
|
||||
const emailOrError = Email.create(dealerDTO.email);
|
||||
if (emailOrError.isFailure) {
|
||||
return Result.fail(emailOrError.error);
|
||||
}
|
||||
|
||||
const passwordOrError = Password.createFromPlainTextPassword(dealerDTO.password);
|
||||
if (passwordOrError.isFailure) {
|
||||
return Result.fail(passwordOrError.error);
|
||||
}
|
||||
|
||||
return Dealer.create(
|
||||
{
|
||||
name: nameOrError.object,
|
||||
email: emailOrError.object,
|
||||
password: passwordOrError.object,
|
||||
},
|
||||
dealerId
|
||||
);
|
||||
}
|
||||
|
||||
private _getDealerRepository() {
|
||||
return this._repositoryManager.getRepository<IDealerRepository>("Dealer");
|
||||
}
|
||||
}
|
||||
5
server/src/contexts/sales/application/index.ts
Normal file
5
server/src/contexts/sales/application/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from "./CreateDealer.useCase";
|
||||
export * from "./DeleteDealer.useCase";
|
||||
export * from "./GetDealer.useCase";
|
||||
export * from "./ListDealers.useCase";
|
||||
export * from "./UpdateDealer.useCase";
|
||||
21
server/src/contexts/sales/domain/entities/Dealer.ts
Normal file
21
server/src/contexts/sales/domain/entities/Dealer.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { AggregateRoot, IDomainError, Name, Result, UniqueID } from "@shared/contexts";
|
||||
|
||||
export interface IDealerProps {
|
||||
name: Name;
|
||||
}
|
||||
|
||||
export interface IDealer {
|
||||
id: UniqueID;
|
||||
name: Name;
|
||||
}
|
||||
|
||||
export class Dealer extends AggregateRoot<IDealerProps> implements IDealer {
|
||||
public static create(props: IDealerProps, id?: UniqueID): Result<Dealer, IDomainError> {
|
||||
const user = new Dealer(props, id);
|
||||
return Result.ok<Dealer>(user);
|
||||
}
|
||||
|
||||
get name(): Name {
|
||||
return this.props.name;
|
||||
}
|
||||
}
|
||||
1
server/src/contexts/sales/domain/entities/index.ts
Normal file
1
server/src/contexts/sales/domain/entities/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./Dealer";
|
||||
2
server/src/contexts/sales/domain/index.ts
Normal file
2
server/src/contexts/sales/domain/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./entities";
|
||||
export * from "./repository";
|
||||
@ -0,0 +1,14 @@
|
||||
import { IRepository } from "@/contexts/common/domain";
|
||||
import { ICollection, IQueryCriteria, UniqueID } from "@shared/contexts";
|
||||
import { Dealer } from "../entities";
|
||||
|
||||
export interface IDealerRepository extends IRepository<any> {
|
||||
exists(id: UniqueID): Promise<boolean>;
|
||||
create(dealer: Dealer): Promise<void>;
|
||||
update(dealer: Dealer): Promise<void>;
|
||||
|
||||
getById(id: UniqueID): Promise<Dealer | null>;
|
||||
findAll(queryCriteria?: IQueryCriteria): Promise<ICollection<Dealer>>;
|
||||
|
||||
removeById(id: UniqueID): Promise<void>;
|
||||
}
|
||||
1
server/src/contexts/sales/domain/repository/index.ts
Normal file
1
server/src/contexts/sales/domain/repository/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./DealerRepository.interface";
|
||||
@ -0,0 +1,85 @@
|
||||
import { ISequelizeAdapter, SequelizeRepository } from "@/contexts/common/infrastructure/sequelize";
|
||||
import { ICollection, IQueryCriteria, UniqueID } from "@shared/contexts";
|
||||
import { Transaction } from "sequelize";
|
||||
|
||||
import { IDealerRepository } from "../domain";
|
||||
import { Dealer } from "../domain/entities";
|
||||
import { ISalesContext } from "./Sales.context";
|
||||
import { IDealerMapper, createDealerMapper } from "./mappers";
|
||||
|
||||
export type QueryParams = {
|
||||
pagination: Record<string, any>;
|
||||
filters: Record<string, any>;
|
||||
};
|
||||
|
||||
export class DealerRepository extends SequelizeRepository<Dealer> implements IDealerRepository {
|
||||
protected mapper: IDealerMapper;
|
||||
|
||||
public constructor(props: {
|
||||
mapper: IDealerMapper;
|
||||
adapter: ISequelizeAdapter;
|
||||
transaction: Transaction;
|
||||
}) {
|
||||
const { adapter, mapper, transaction } = props;
|
||||
super({ adapter, transaction });
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
public async exists(id: UniqueID): Promise<boolean> {
|
||||
return this._exists("Dealer_Model", "id", id.toPrimitive());
|
||||
}
|
||||
|
||||
public async create(user: Dealer): Promise<void> {
|
||||
const userData = this.mapper.mapToPersistence(user);
|
||||
await this._save("Dealer_Model", user.id, userData);
|
||||
}
|
||||
|
||||
public async update(user: Dealer): Promise<void> {
|
||||
const userData = this.mapper.mapToPersistence(user);
|
||||
|
||||
// borrando y luego creando
|
||||
// await this.removeById(user.id, true);
|
||||
await this._save("Dealer_Model", user.id, userData, {});
|
||||
}
|
||||
|
||||
public async getById(id: UniqueID): Promise<Dealer | null> {
|
||||
const rawDealer: any = await this._getById("Dealer_Model", id);
|
||||
|
||||
if (!rawDealer === true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.mapper.mapToDomain(rawDealer);
|
||||
}
|
||||
|
||||
public async findAll(queryCriteria?: IQueryCriteria): Promise<ICollection<any>> {
|
||||
const { rows, count } = await this._findAll(
|
||||
"Dealer_Model",
|
||||
queryCriteria
|
||||
/*{
|
||||
include: [], // esto es para quitar las asociaciones al hacer la consulta
|
||||
}*/
|
||||
);
|
||||
|
||||
return this.mapper.mapArrayAndCountToDomain(rows, count);
|
||||
}
|
||||
|
||||
public async removeById(id: UniqueID, force: boolean = false): Promise<void> {
|
||||
return this._removeById("Dealer_Model", id);
|
||||
}
|
||||
}
|
||||
|
||||
export const registerDealerRepository = (context: ISalesContext) => {
|
||||
const adapter = context.adapter;
|
||||
const repoManager = context.repositoryManager;
|
||||
|
||||
repoManager.registerRepository("Dealer", (params = { transaction: null }) => {
|
||||
const { transaction } = params;
|
||||
|
||||
return new DealerRepository({
|
||||
transaction,
|
||||
adapter,
|
||||
mapper: createDealerMapper(context),
|
||||
});
|
||||
});
|
||||
};
|
||||
32
server/src/contexts/sales/infrastructure/Sales.context.ts
Normal file
32
server/src/contexts/sales/infrastructure/Sales.context.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { IRepositoryManager, RepositoryManager } from "@/contexts/common/domain";
|
||||
import {
|
||||
ISequelizeAdapter,
|
||||
createSequelizeAdapter,
|
||||
} from "@/contexts/common/infrastructure/sequelize";
|
||||
|
||||
export interface ISalesContext {
|
||||
adapter: ISequelizeAdapter;
|
||||
repositoryManager: IRepositoryManager;
|
||||
//services: IApplicationService;
|
||||
}
|
||||
|
||||
export class SalesContext {
|
||||
private static instance: SalesContext | null = null;
|
||||
|
||||
public static getInstance(): ISalesContext {
|
||||
if (!SalesContext.instance) {
|
||||
SalesContext.instance = new SalesContext({
|
||||
adapter: createSequelizeAdapter(),
|
||||
repositoryManager: RepositoryManager.getInstance(),
|
||||
});
|
||||
}
|
||||
|
||||
return SalesContext.instance.context;
|
||||
}
|
||||
|
||||
private context: ISalesContext;
|
||||
|
||||
private constructor(context: ISalesContext) {
|
||||
this.context = context;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,119 @@
|
||||
import { IUseCaseError, UseCaseError } from "@/contexts/common/application";
|
||||
import { IServerError } from "@/contexts/common/domain/errors";
|
||||
import { IInfrastructureError, InfrastructureError } from "@/contexts/common/infrastructure";
|
||||
import { ExpressController } from "@/contexts/common/infrastructure/express";
|
||||
import { CreateDealerUseCase } from "@/contexts/sales/application";
|
||||
import { Dealer } from "@/contexts/sales/domain/entities";
|
||||
import {
|
||||
ICreateDealer_Request_DTO,
|
||||
ICreateDealer_Response_DTO,
|
||||
ensureCreateDealer_Request_DTOIsValid,
|
||||
} from "@shared/contexts";
|
||||
import { ISalesContext } from "../../../Sales.context";
|
||||
import { ICreateDealerPresenter } from "./presenter";
|
||||
|
||||
export class CreateDealerController extends ExpressController {
|
||||
private useCase: CreateDealerUseCase;
|
||||
private presenter: ICreateDealerPresenter;
|
||||
private context: ISalesContext;
|
||||
|
||||
constructor(
|
||||
props: {
|
||||
useCase: CreateDealerUseCase;
|
||||
presenter: ICreateDealerPresenter;
|
||||
},
|
||||
context: ISalesContext
|
||||
) {
|
||||
super();
|
||||
|
||||
const { useCase, presenter } = props;
|
||||
this.useCase = useCase;
|
||||
this.presenter = presenter;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
async executeImpl() {
|
||||
try {
|
||||
const dealerDTO: ICreateDealer_Request_DTO = this.req.body;
|
||||
|
||||
// Validaciones de DTO
|
||||
const dealerDTOOrError = ensureCreateDealer_Request_DTOIsValid(dealerDTO);
|
||||
|
||||
if (dealerDTOOrError.isFailure) {
|
||||
const errorMessage = "Dealer data not valid";
|
||||
const infraError = InfrastructureError.create(
|
||||
InfrastructureError.INVALID_INPUT_DATA,
|
||||
errorMessage,
|
||||
dealerDTOOrError.error
|
||||
);
|
||||
return this.invalidInputError(errorMessage, infraError);
|
||||
}
|
||||
|
||||
// Llamar al caso de uso
|
||||
const result = await this.useCase.execute(dealerDTO);
|
||||
|
||||
if (result.isFailure) {
|
||||
return this._handleExecuteError(result.error);
|
||||
}
|
||||
|
||||
const dealer = <Dealer>result.object;
|
||||
|
||||
return this.created<ICreateDealer_Response_DTO>(this.presenter.map(dealer, this.context));
|
||||
} catch (e: unknown) {
|
||||
return this.fail(e as IServerError);
|
||||
}
|
||||
}
|
||||
|
||||
private _handleExecuteError(error: IUseCaseError) {
|
||||
let errorMessage: string;
|
||||
let infraError: IInfrastructureError;
|
||||
|
||||
switch (error.code) {
|
||||
case UseCaseError.INVALID_INPUT_DATA:
|
||||
errorMessage = "Dealer data not valid";
|
||||
infraError = InfrastructureError.create(
|
||||
InfrastructureError.INVALID_INPUT_DATA,
|
||||
errorMessage,
|
||||
error
|
||||
);
|
||||
return this.invalidInputError(errorMessage, infraError);
|
||||
break;
|
||||
|
||||
case UseCaseError.RESOURCE_ALREADY_EXITS:
|
||||
errorMessage = "Dealer already exists";
|
||||
|
||||
infraError = InfrastructureError.create(
|
||||
InfrastructureError.RESOURCE_ALREADY_REGISTERED,
|
||||
errorMessage,
|
||||
error
|
||||
);
|
||||
return this.conflictError(errorMessage, infraError);
|
||||
break;
|
||||
|
||||
case UseCaseError.REPOSITORY_ERROR:
|
||||
errorMessage = "Error saving dealer";
|
||||
infraError = InfrastructureError.create(
|
||||
InfrastructureError.UNEXCEPTED_ERROR,
|
||||
errorMessage,
|
||||
error
|
||||
);
|
||||
return this.conflictError(errorMessage, infraError);
|
||||
break;
|
||||
|
||||
case UseCaseError.UNEXCEPTED_ERROR:
|
||||
errorMessage = error.message;
|
||||
|
||||
infraError = InfrastructureError.create(
|
||||
InfrastructureError.UNEXCEPTED_ERROR,
|
||||
errorMessage,
|
||||
error
|
||||
);
|
||||
return this.internalServerError(errorMessage, infraError);
|
||||
break;
|
||||
|
||||
default:
|
||||
errorMessage = error.message;
|
||||
return this.clientError(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
import { CreateDealerUseCase } from "@/contexts/sales/application";
|
||||
import Express from "express";
|
||||
import { registerDealerRepository } from "../../../Dealer.repository";
|
||||
import { ISalesContext } from "../../../Sales.context";
|
||||
import { CreateDealerController } from "./CreateDealer.controller";
|
||||
import { CreateDealerPresenter } from "./presenter";
|
||||
|
||||
export const createDealerController = (
|
||||
req: Express.Request,
|
||||
res: Express.Response,
|
||||
next: Express.NextFunction
|
||||
) => {
|
||||
const context: ISalesContext = res.locals.context;
|
||||
|
||||
registerDealerRepository(context);
|
||||
return new CreateDealerController(
|
||||
{
|
||||
useCase: new CreateDealerUseCase(context),
|
||||
presenter: CreateDealerPresenter,
|
||||
},
|
||||
context
|
||||
).execute(req, res, next);
|
||||
};
|
||||
@ -0,0 +1,16 @@
|
||||
import { Dealer } from "@/contexts/sales/domain/entities";
|
||||
import { ISalesContext } from "@/contexts/sales/infrastructure/Sales.context";
|
||||
import { ICreateDealer_Response_DTO } from "@shared/contexts";
|
||||
|
||||
export interface ICreateDealerPresenter {
|
||||
map: (dealer: Dealer, context: ISalesContext) => ICreateDealer_Response_DTO;
|
||||
}
|
||||
|
||||
export const CreateDealerPresenter: ICreateDealerPresenter = {
|
||||
map: (dealer: Dealer, context: ISalesContext): ICreateDealer_Response_DTO => {
|
||||
return {
|
||||
id: dealer.id.toString(),
|
||||
name: dealer.name.toString(),
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export * from "./CreateDealer.presenter";
|
||||
@ -0,0 +1,84 @@
|
||||
import { IUseCaseError, UseCaseError } from "@/contexts/common/application/useCases";
|
||||
import { IServerError } from "@/contexts/common/domain/errors";
|
||||
import { IInfrastructureError, InfrastructureError } from "@/contexts/common/infrastructure";
|
||||
import { ExpressController } from "@/contexts/common/infrastructure/express";
|
||||
import { DeleteDealerUseCase } from "@/contexts/sales/application";
|
||||
import { ensureIdIsValid } from "@shared/contexts";
|
||||
import { ISalesContext } from "../../../Sales.context";
|
||||
|
||||
export class DeleteDealerController extends ExpressController {
|
||||
private useCase: DeleteDealerUseCase;
|
||||
private context: ISalesContext;
|
||||
|
||||
constructor(props: { useCase: DeleteDealerUseCase }, context: ISalesContext) {
|
||||
super();
|
||||
|
||||
const { useCase } = props;
|
||||
this.useCase = useCase;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
async executeImpl(): Promise<any> {
|
||||
try {
|
||||
const { dealerId } = this.req.params;
|
||||
|
||||
// Validar ID
|
||||
const dealerIdOrError = ensureIdIsValid(dealerId);
|
||||
if (dealerIdOrError.isFailure) {
|
||||
const errorMessage = "Dealer ID is not valid";
|
||||
const infraError = InfrastructureError.create(
|
||||
InfrastructureError.INVALID_INPUT_DATA,
|
||||
errorMessage,
|
||||
dealerIdOrError.error
|
||||
);
|
||||
return this.invalidInputError(errorMessage, infraError);
|
||||
}
|
||||
|
||||
// Llamar al caso de uso
|
||||
const result = await this.useCase.execute({
|
||||
id: dealerIdOrError.object,
|
||||
});
|
||||
|
||||
if (result.isFailure) {
|
||||
return this._handleExecuteError(result.error);
|
||||
}
|
||||
return this.noContent();
|
||||
} catch (e: unknown) {
|
||||
return this.fail(e as IServerError);
|
||||
}
|
||||
}
|
||||
|
||||
private _handleExecuteError(error: IUseCaseError) {
|
||||
let errorMessage: string;
|
||||
let infraError: IInfrastructureError;
|
||||
|
||||
switch (error.code) {
|
||||
case UseCaseError.NOT_FOUND_ERROR:
|
||||
errorMessage = "Dealer not found";
|
||||
|
||||
infraError = InfrastructureError.create(
|
||||
InfrastructureError.RESOURCE_NOT_FOUND_ERROR,
|
||||
errorMessage,
|
||||
error
|
||||
);
|
||||
|
||||
return this.notFoundError(errorMessage, infraError);
|
||||
break;
|
||||
|
||||
case UseCaseError.UNEXCEPTED_ERROR:
|
||||
errorMessage = error.message;
|
||||
|
||||
infraError = InfrastructureError.create(
|
||||
InfrastructureError.UNEXCEPTED_ERROR,
|
||||
errorMessage,
|
||||
error
|
||||
);
|
||||
return this.internalServerError(errorMessage, infraError);
|
||||
break;
|
||||
|
||||
default:
|
||||
errorMessage = error.message;
|
||||
return this.clientError(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
import { DeleteDealerUseCase } from "@/contexts/sales/application";
|
||||
import Express from "express";
|
||||
import { registerDealerRepository } from "../../../Dealer.repository";
|
||||
import { ISalesContext } from "../../../Sales.context";
|
||||
import { DeleteDealerController } from "./DeleteDealer.controller";
|
||||
|
||||
export const deleteDealerController = (
|
||||
req: Express.Request,
|
||||
res: Express.Response,
|
||||
next: Express.NextFunction
|
||||
) => {
|
||||
const context: ISalesContext = res.locals.context;
|
||||
|
||||
registerDealerRepository(context);
|
||||
return new DeleteDealerController(
|
||||
{
|
||||
useCase: new DeleteDealerUseCase(context),
|
||||
},
|
||||
context
|
||||
).execute(req, res, next);
|
||||
};
|
||||
@ -0,0 +1,97 @@
|
||||
import { IUseCaseError, UseCaseError } from "@/contexts/common/application/useCases";
|
||||
import { ExpressController } from "@/contexts/common/infrastructure/express";
|
||||
|
||||
import { IServerError } from "@/contexts/common/domain/errors";
|
||||
import { IInfrastructureError, InfrastructureError } from "@/contexts/common/infrastructure";
|
||||
import { GetDealerUseCase } from "@/contexts/sales/application";
|
||||
import { Dealer } from "@/contexts/sales/domain";
|
||||
import { IGetDealerResponse_DTO, ensureIdIsValid } from "@shared/contexts";
|
||||
import { ISalesContext } from "../../../Sales.context";
|
||||
import { IGetDealerPresenter } from "./presenter/GetDealer.presenter";
|
||||
|
||||
export class GetDealerController extends ExpressController {
|
||||
private useCase: GetDealerUseCase;
|
||||
private presenter: IGetDealerPresenter;
|
||||
private context: ISalesContext;
|
||||
|
||||
constructor(
|
||||
props: {
|
||||
useCase: GetDealerUseCase;
|
||||
presenter: IGetDealerPresenter;
|
||||
},
|
||||
context: ISalesContext
|
||||
) {
|
||||
super();
|
||||
|
||||
const { useCase, presenter } = props;
|
||||
this.useCase = useCase;
|
||||
this.presenter = presenter;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
async executeImpl(): Promise<any> {
|
||||
const { dealerId } = this.req.params;
|
||||
|
||||
// Validar ID
|
||||
const dealerIdOrError = ensureIdIsValid(dealerId);
|
||||
if (dealerIdOrError.isFailure) {
|
||||
const errorMessage = "Dealer ID is not valid";
|
||||
const infraError = InfrastructureError.create(
|
||||
InfrastructureError.INVALID_INPUT_DATA,
|
||||
errorMessage,
|
||||
dealerIdOrError.error
|
||||
);
|
||||
return this.invalidInputError(errorMessage, infraError);
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await this.useCase.execute({
|
||||
id: dealerIdOrError.object,
|
||||
});
|
||||
|
||||
if (result.isFailure) {
|
||||
return this._handleExecuteError(result.error);
|
||||
}
|
||||
|
||||
const dealer = <Dealer>result.object;
|
||||
|
||||
return this.ok<IGetDealerResponse_DTO>(this.presenter.map(dealer, this.context));
|
||||
} catch (e: unknown) {
|
||||
return this.fail(e as IServerError);
|
||||
}
|
||||
}
|
||||
|
||||
private _handleExecuteError(error: IUseCaseError) {
|
||||
let errorMessage: string;
|
||||
let infraError: IInfrastructureError;
|
||||
|
||||
switch (error.code) {
|
||||
case UseCaseError.NOT_FOUND_ERROR:
|
||||
errorMessage = "Dealer not found";
|
||||
|
||||
infraError = InfrastructureError.create(
|
||||
InfrastructureError.RESOURCE_NOT_FOUND_ERROR,
|
||||
errorMessage,
|
||||
error
|
||||
);
|
||||
|
||||
return this.notFoundError(errorMessage, infraError);
|
||||
break;
|
||||
|
||||
case UseCaseError.UNEXCEPTED_ERROR:
|
||||
errorMessage = error.message;
|
||||
|
||||
infraError = InfrastructureError.create(
|
||||
InfrastructureError.UNEXCEPTED_ERROR,
|
||||
errorMessage,
|
||||
error
|
||||
);
|
||||
return this.internalServerError(errorMessage, infraError);
|
||||
break;
|
||||
|
||||
default:
|
||||
errorMessage = error.message;
|
||||
return this.clientError(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
import { GetDealerUseCase } from "@/contexts/sales/application";
|
||||
import Express from "express";
|
||||
import { registerDealerRepository } from "../../../Dealer.repository";
|
||||
import { ISalesContext } from "../../../Sales.context";
|
||||
import { GetDealerController } from "./GetDealer.controller";
|
||||
import { GetDealerPresenter } from "./presenter";
|
||||
|
||||
export const getDealerController = (
|
||||
req: Express.Request,
|
||||
res: Express.Response,
|
||||
next: Express.NextFunction
|
||||
) => {
|
||||
const context: ISalesContext = res.locals.context;
|
||||
|
||||
registerDealerRepository(context);
|
||||
|
||||
return new GetDealerController(
|
||||
{
|
||||
useCase: new GetDealerUseCase(context),
|
||||
presenter: GetDealerPresenter,
|
||||
},
|
||||
context
|
||||
).execute(req, res, next);
|
||||
};
|
||||
@ -0,0 +1,15 @@
|
||||
import { Dealer } from "../../../../../domain";
|
||||
import { ISalesContext } from "../../../../Sales.context";
|
||||
|
||||
export interface IGetDealerPresenter {
|
||||
map: (dealer: Dealer, context: ISalesContext) => IGetDealerResponse_DTO;
|
||||
}
|
||||
|
||||
export const GetDealerPresenter: IGetDealerPresenter = {
|
||||
map: (dealer: Dealer, context: ISalesContext): IGetDealerResponse_DTO => {
|
||||
return {
|
||||
id: dealer.id.toString(),
|
||||
name: dealer.name.toString(),
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export * from "./GetDealer.presenter";
|
||||
@ -0,0 +1,5 @@
|
||||
export * from "./createDealer";
|
||||
export * from "./deleteDealer";
|
||||
export * from "./getDealer";
|
||||
export * from "./listDealers";
|
||||
export * from "./updateDealer";
|
||||
@ -0,0 +1,84 @@
|
||||
import Joi from "joi";
|
||||
|
||||
import { QueryCriteriaService } from "@/contexts/common/application/services";
|
||||
import { IServerError } from "@/contexts/common/domain/errors";
|
||||
import { ExpressController } from "@/contexts/common/infrastructure/express";
|
||||
import { ListDealersResult, ListDealersUseCase } from "@/contexts/sales/application";
|
||||
import { Dealer } from "@/contexts/sales/domain";
|
||||
import {
|
||||
ICollection,
|
||||
IListDealers_Response_DTO,
|
||||
IListResponse_DTO,
|
||||
IQueryCriteria,
|
||||
Result,
|
||||
RuleValidator,
|
||||
} from "@shared/contexts";
|
||||
import { ISalesContext } from "../../../Sales.context";
|
||||
import { IListDealersPresenter } from "./presenter";
|
||||
|
||||
export class ListDealersController extends ExpressController {
|
||||
private useCase: ListDealersUseCase;
|
||||
private presenter: IListDealersPresenter;
|
||||
private context: ISalesContext;
|
||||
|
||||
constructor(
|
||||
props: {
|
||||
useCase: ListDealersUseCase;
|
||||
presenter: IListDealersPresenter;
|
||||
},
|
||||
context: ISalesContext
|
||||
) {
|
||||
super();
|
||||
|
||||
const { useCase, presenter } = props;
|
||||
this.useCase = useCase;
|
||||
this.presenter = presenter;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
protected validateQuery(query): Result<any> {
|
||||
const schema = Joi.object({
|
||||
page: Joi.number().optional(),
|
||||
limit: Joi.number().optional(),
|
||||
$sort_by: Joi.string().optional(),
|
||||
$filters: Joi.string().optional(),
|
||||
q: Joi.string().optional(),
|
||||
}).optional();
|
||||
|
||||
return RuleValidator.validate(schema, query);
|
||||
}
|
||||
|
||||
async executeImpl() {
|
||||
const queryOrError = this.validateQuery(this.req.query);
|
||||
if (queryOrError.isFailure) {
|
||||
return this.clientError(queryOrError.error.message);
|
||||
}
|
||||
|
||||
const queryParams = queryOrError.object;
|
||||
|
||||
try {
|
||||
const queryCriteria: IQueryCriteria = QueryCriteriaService.parse(queryParams);
|
||||
|
||||
console.log(queryCriteria);
|
||||
|
||||
const result: ListDealersResult = await this.useCase.execute({
|
||||
queryCriteria,
|
||||
});
|
||||
|
||||
if (result.isFailure) {
|
||||
return this.clientError(result.error.message);
|
||||
}
|
||||
|
||||
const dealers = <ICollection<Dealer>>result.object;
|
||||
|
||||
return this.ok<IListResponse_DTO<IListDealers_Response_DTO>>(
|
||||
this.presenter.mapArray(dealers, this.context, {
|
||||
page: queryCriteria.pagination.offset,
|
||||
limit: queryCriteria.pagination.limit,
|
||||
})
|
||||
);
|
||||
} catch (e: unknown) {
|
||||
return this.fail(e as IServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
import { ListDealersUseCase } from "@/contexts/sales/application";
|
||||
import Express from "express";
|
||||
import { registerDealerRepository } from "../../../Dealer.repository";
|
||||
import { ISalesContext } from "../../../Sales.context";
|
||||
import { ListDealersController } from "./ListDealers.controller";
|
||||
import { listDealersPresenter } from "./presenter/ListDealers.presenter";
|
||||
|
||||
export const listDealersController = (
|
||||
req: Express.Request,
|
||||
res: Express.Response,
|
||||
next: Express.NextFunction
|
||||
) => {
|
||||
const context: ISalesContext = res.locals.context;
|
||||
|
||||
registerDealerRepository(context);
|
||||
return new ListDealersController(
|
||||
{
|
||||
useCase: new ListDealersUseCase(context),
|
||||
presenter: listDealersPresenter,
|
||||
},
|
||||
context
|
||||
).execute(req, res, next);
|
||||
};
|
||||
@ -0,0 +1,50 @@
|
||||
import { Dealer } from "@/contexts/sales/domain";
|
||||
import { ISalesContext } from "@/contexts/sales/infrastructure/Sales.context";
|
||||
import { ICollection, IListDealers_Response_DTO, IListResponse_DTO } from "@shared/contexts";
|
||||
|
||||
export interface IListDealersPresenter {
|
||||
map: (dealer: Dealer, context: ISalesContext) => IListDealers_Response_DTO;
|
||||
|
||||
mapArray: (
|
||||
dealers: ICollection<Dealer>,
|
||||
context: ISalesContext,
|
||||
params: {
|
||||
page: number;
|
||||
limit: number;
|
||||
}
|
||||
) => IListResponse_DTO<IListDealers_Response_DTO>;
|
||||
}
|
||||
|
||||
export const listDealersPresenter: IListDealersPresenter = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
map: (dealer: Dealer, context: ISalesContext): IListDealers_Response_DTO => {
|
||||
return {
|
||||
id: dealer.id.toString(),
|
||||
name: dealer.name.toString(),
|
||||
};
|
||||
},
|
||||
|
||||
mapArray: (
|
||||
dealers: ICollection<Dealer>,
|
||||
context: ISalesContext,
|
||||
params: {
|
||||
page: number;
|
||||
limit: number;
|
||||
}
|
||||
): IListResponse_DTO<IListDealers_Response_DTO> => {
|
||||
const { page, limit } = params;
|
||||
|
||||
const totalCount = dealers.totalCount ?? 0;
|
||||
const items = dealers.items.map((dealer: Dealer) => listDealersPresenter.map(dealer, context));
|
||||
|
||||
const result = {
|
||||
page,
|
||||
per_page: limit,
|
||||
total_pages: Math.ceil(totalCount / limit),
|
||||
total_items: totalCount,
|
||||
items,
|
||||
};
|
||||
|
||||
return result;
|
||||
},
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export * from "./ListDealers.presenter";
|
||||
@ -0,0 +1,138 @@
|
||||
import { IUseCaseError, UseCaseError } from "@/contexts/common/application";
|
||||
import { IServerError } from "@/contexts/common/domain/errors";
|
||||
import { IInfrastructureError, InfrastructureError } from "@/contexts/common/infrastructure";
|
||||
import { ExpressController } from "@/contexts/common/infrastructure/express";
|
||||
import { UpdateDealerResponseOrError, UpdateDealerUseCase } from "@/contexts/sales/application";
|
||||
import { Dealer } from "@/contexts/sales/domain";
|
||||
import {
|
||||
IUpdateDealer_Request_DTO,
|
||||
IUpdateDealer_Response_DTO,
|
||||
ensureIdIsValid,
|
||||
ensureUpdateDealer_Request_DTOIsValid,
|
||||
} from "@shared/contexts";
|
||||
import { ISalesContext } from "../../../Sales.context";
|
||||
import { IUpdateDealerPresenter } from "./presenter/UpdateDealer.presenter";
|
||||
|
||||
export class UpdateDealerController extends ExpressController {
|
||||
private useCase: UpdateDealerUseCase;
|
||||
private presenter: IUpdateDealerPresenter;
|
||||
private context: ISalesContext;
|
||||
|
||||
constructor(
|
||||
props: {
|
||||
useCase: UpdateDealerUseCase;
|
||||
presenter: IUpdateDealerPresenter;
|
||||
},
|
||||
context: ISalesContext
|
||||
) {
|
||||
super();
|
||||
|
||||
const { useCase, presenter } = props;
|
||||
this.useCase = useCase;
|
||||
this.presenter = presenter;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
async executeImpl() {
|
||||
try {
|
||||
const { dealerId } = this.req.params;
|
||||
const dealerDTO: IUpdateDealer_Request_DTO = this.req.body;
|
||||
|
||||
// Validar ID
|
||||
const dealerIdOrError = ensureIdIsValid(dealerId);
|
||||
if (dealerIdOrError.isFailure) {
|
||||
const errorMessage = "Dealer ID is not valid";
|
||||
const infraError = InfrastructureError.create(
|
||||
InfrastructureError.INVALID_INPUT_DATA,
|
||||
errorMessage,
|
||||
dealerIdOrError.error
|
||||
);
|
||||
return this.invalidInputError(errorMessage, infraError);
|
||||
}
|
||||
|
||||
// Validar DTO de datos
|
||||
const dealerDTOOrError = ensureUpdateDealer_Request_DTOIsValid(dealerDTO);
|
||||
|
||||
if (dealerDTOOrError.isFailure) {
|
||||
const errorMessage = "Dealer data not valid";
|
||||
const infraError = InfrastructureError.create(
|
||||
InfrastructureError.INVALID_INPUT_DATA,
|
||||
errorMessage,
|
||||
dealerDTOOrError.error
|
||||
);
|
||||
return this.invalidInputError(errorMessage, infraError);
|
||||
}
|
||||
|
||||
// Llamar al caso de uso
|
||||
const result: UpdateDealerResponseOrError = await this.useCase.execute({
|
||||
id: dealerIdOrError.object,
|
||||
dealerDTO,
|
||||
});
|
||||
|
||||
if (result.isFailure) {
|
||||
return this._handleExecuteError(result.error);
|
||||
}
|
||||
|
||||
const dealer = <Dealer>result.object;
|
||||
|
||||
return this.ok<IUpdateDealer_Response_DTO>(this.presenter.map(dealer, this.context));
|
||||
} catch (e: unknown) {
|
||||
return this.fail(e as IServerError);
|
||||
}
|
||||
}
|
||||
|
||||
private _handleExecuteError(error: IUseCaseError) {
|
||||
let errorMessage: string;
|
||||
let infraError: IInfrastructureError;
|
||||
|
||||
switch (error.code) {
|
||||
case UseCaseError.NOT_FOUND_ERROR:
|
||||
errorMessage = "Dealer not found";
|
||||
|
||||
infraError = InfrastructureError.create(
|
||||
InfrastructureError.RESOURCE_NOT_FOUND_ERROR,
|
||||
errorMessage,
|
||||
error
|
||||
);
|
||||
|
||||
return this.notFoundError(errorMessage, infraError);
|
||||
break;
|
||||
|
||||
case UseCaseError.INVALID_INPUT_DATA:
|
||||
errorMessage = "Dealer data not valid";
|
||||
|
||||
infraError = InfrastructureError.create(
|
||||
InfrastructureError.INVALID_INPUT_DATA,
|
||||
"Datos del cliente a actulizar erróneos",
|
||||
error
|
||||
);
|
||||
return this.invalidInputError(errorMessage, infraError);
|
||||
break;
|
||||
|
||||
case UseCaseError.REPOSITORY_ERROR:
|
||||
errorMessage = "Error updating dealer";
|
||||
infraError = InfrastructureError.create(
|
||||
InfrastructureError.UNEXCEPTED_ERROR,
|
||||
errorMessage,
|
||||
error
|
||||
);
|
||||
return this.conflictError(errorMessage, infraError);
|
||||
break;
|
||||
|
||||
case UseCaseError.UNEXCEPTED_ERROR:
|
||||
errorMessage = error.message;
|
||||
|
||||
infraError = InfrastructureError.create(
|
||||
InfrastructureError.UNEXCEPTED_ERROR,
|
||||
errorMessage,
|
||||
error
|
||||
);
|
||||
return this.internalServerError(errorMessage, infraError);
|
||||
break;
|
||||
|
||||
default:
|
||||
errorMessage = error.message;
|
||||
return this.clientError(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
import { UpdateDealerUseCase } from "@/contexts/sales/application";
|
||||
import Express from "express";
|
||||
import { registerDealerRepository } from "../../../Dealer.repository";
|
||||
import { ISalesContext } from "../../../Sales.context";
|
||||
import { UpdateDealerController } from "./UpdateDealer.controller";
|
||||
import { UpdateDealerPresenter } from "./presenter/UpdateDealer.presenter";
|
||||
|
||||
export const updateDealerController = (
|
||||
req: Express.Request,
|
||||
res: Express.Response,
|
||||
next: Express.NextFunction
|
||||
) => {
|
||||
const context: ISalesContext = res.locals.context;
|
||||
|
||||
registerDealerRepository(context);
|
||||
return new UpdateDealerController(
|
||||
{
|
||||
useCase: new UpdateDealerUseCase(context),
|
||||
presenter: UpdateDealerPresenter,
|
||||
},
|
||||
context
|
||||
).execute(req, res, next);
|
||||
};
|
||||
@ -0,0 +1,16 @@
|
||||
import { Dealer } from "@/contexts/sales/domain";
|
||||
import { ISalesContext } from "@/contexts/sales/infrastructure/Sales.context";
|
||||
import { IUpdateDealer_Response_DTO } from "@shared/contexts";
|
||||
|
||||
export interface IUpdateDealerPresenter {
|
||||
map: (dealer: Dealer, context: ISalesContext) => IUpdateDealer_Response_DTO;
|
||||
}
|
||||
|
||||
export const UpdateDealerPresenter: IUpdateDealerPresenter = {
|
||||
map: (dealer: Dealer, context: ISalesContext): IUpdateDealer_Response_DTO => {
|
||||
return {
|
||||
id: dealer.id.toString(),
|
||||
name: dealer.name.toString(),
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export * from "./UpdateDealer.presenter";
|
||||
@ -0,0 +1 @@
|
||||
export * from "./routes";
|
||||
21
server/src/contexts/sales/infrastructure/express/routes.ts
Normal file
21
server/src/contexts/sales/infrastructure/express/routes.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { applyMiddleware } from "@/contexts/common/infrastructure/express";
|
||||
import Express from "express";
|
||||
import {
|
||||
createDealerController,
|
||||
deleteDealerController,
|
||||
getDealerController,
|
||||
updateDealerController,
|
||||
} from "./controllers";
|
||||
import { listDealersController } from "./controllers/listDealers";
|
||||
|
||||
export const DealerRouter = (appRouter: Express.Router) => {
|
||||
const dealerRoutes: Express.Router = Express.Router({ mergeParams: true });
|
||||
|
||||
dealerRoutes.get("/", applyMiddleware("isAdminUser"), listDealersController);
|
||||
dealerRoutes.get("/:dealerId", applyMiddleware("isAdminUser"), getDealerController);
|
||||
dealerRoutes.post("/", applyMiddleware("isAdminUser"), createDealerController);
|
||||
dealerRoutes.put("/:dealerId", applyMiddleware("isAdminUser"), updateDealerController);
|
||||
dealerRoutes.delete("/:dealerId", applyMiddleware("isAdminUser"), deleteDealerController);
|
||||
|
||||
appRouter.use("/dealers", dealerRoutes);
|
||||
};
|
||||
1
server/src/contexts/sales/infrastructure/index.ts
Normal file
1
server/src/contexts/sales/infrastructure/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./Sales.context";
|
||||
@ -0,0 +1,55 @@
|
||||
import {
|
||||
ISequelizeMapper,
|
||||
MapperParamsType,
|
||||
SequelizeMapper,
|
||||
} from "@/contexts/common/infrastructure";
|
||||
import { Name, UniqueID } from "@shared/contexts";
|
||||
import { Dealer, IDealerProps } from "../../domain/entities";
|
||||
import { ISalesContext } from "../Sales.context";
|
||||
import { DealerCreationAttributes, DealerStatus, Dealer_Model } from "../sequelize";
|
||||
|
||||
export interface IDealerMapper
|
||||
extends ISequelizeMapper<Dealer_Model, DealerCreationAttributes, Dealer> {}
|
||||
|
||||
class DealerMapper
|
||||
extends SequelizeMapper<Dealer_Model, DealerCreationAttributes, Dealer>
|
||||
implements IDealerMapper
|
||||
{
|
||||
public constructor(props: { context: ISalesContext }) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
protected toDomainMappingImpl(source: Dealer_Model, params: any): Dealer {
|
||||
const props: IDealerProps = {
|
||||
name: this.mapsValue(source, "name", Name.create),
|
||||
};
|
||||
|
||||
const id = this.mapsValue(source, "id", UniqueID.create);
|
||||
const userOrError = Dealer.create(props, id);
|
||||
|
||||
if (userOrError.isFailure) {
|
||||
throw userOrError.error;
|
||||
}
|
||||
|
||||
return userOrError.object;
|
||||
}
|
||||
|
||||
protected toPersistenceMappingImpl(source: Dealer, params?: MapperParamsType | undefined) {
|
||||
return {
|
||||
id: source.id.toPrimitive(),
|
||||
id_contact: undefined,
|
||||
name: source.name.toPrimitive(),
|
||||
contact_information: "",
|
||||
default_payment_method: "",
|
||||
default_notes: "",
|
||||
default_legal_terms: "",
|
||||
default_quote_validity: "",
|
||||
status: DealerStatus.ACTIVE,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const createDealerMapper = (context: ISalesContext): IDealerMapper =>
|
||||
new DealerMapper({
|
||||
context,
|
||||
});
|
||||
@ -0,0 +1 @@
|
||||
export * from "./dealer.mapper";
|
||||
@ -0,0 +1,103 @@
|
||||
import {
|
||||
DataTypes,
|
||||
InferAttributes,
|
||||
InferCreationAttributes,
|
||||
Model,
|
||||
Op,
|
||||
Sequelize,
|
||||
} from "sequelize";
|
||||
|
||||
export enum DealerStatus {
|
||||
ACTIVE = "active",
|
||||
BLOCKED = "blocked",
|
||||
}
|
||||
|
||||
export type DealerCreationAttributes = InferCreationAttributes<Dealer_Model>;
|
||||
|
||||
export class Dealer_Model extends Model<
|
||||
InferAttributes<Dealer_Model>,
|
||||
InferCreationAttributes<Dealer_Model>
|
||||
> {
|
||||
// To avoid table creation
|
||||
/*static async sync(): Promise<any> {
|
||||
return Promise.resolve();
|
||||
}*/
|
||||
|
||||
static associate(connection: Sequelize) {}
|
||||
|
||||
declare id: string;
|
||||
declare id_contact?: string; // number ??
|
||||
declare name: string;
|
||||
declare contact_information: string;
|
||||
declare default_payment_method: string;
|
||||
declare default_notes: string;
|
||||
declare default_legal_terms: string;
|
||||
declare default_quote_validity: string;
|
||||
declare status: DealerStatus;
|
||||
}
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
Dealer_Model.init(
|
||||
{
|
||||
id: {
|
||||
type: new DataTypes.UUID(),
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
id_contact: {
|
||||
type: DataTypes.BIGINT().UNSIGNED,
|
||||
allowNull: true,
|
||||
},
|
||||
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
contact_information: DataTypes.STRING,
|
||||
default_payment_method: DataTypes.STRING,
|
||||
default_notes: DataTypes.STRING,
|
||||
default_legal_terms: DataTypes.STRING,
|
||||
default_quote_validity: DataTypes.STRING,
|
||||
|
||||
status: {
|
||||
type: DataTypes.ENUM(...Object.values(DealerStatus)),
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
tableName: "dealers",
|
||||
|
||||
paranoid: true, // softs deletes
|
||||
timestamps: true,
|
||||
//version: true,
|
||||
|
||||
createdAt: "created_at",
|
||||
updatedAt: "updated_at",
|
||||
deletedAt: "deleted_at",
|
||||
|
||||
indexes: [
|
||||
{ name: "id_contact_idx", fields: ["id_contact"] },
|
||||
{ name: "status_idx", fields: ["status"] },
|
||||
],
|
||||
|
||||
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
|
||||
scopes: {
|
||||
quickSearch(value) {
|
||||
return {
|
||||
where: {
|
||||
[Op.or]: {
|
||||
name: {
|
||||
[Op.like]: `%${value}%`,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return Dealer_Model;
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export * from "./dealer.model";
|
||||
@ -6,13 +6,13 @@ import {
|
||||
import { Email, Name, UniqueID } from "@shared/contexts";
|
||||
import { IUserProps, User } from "../../domain";
|
||||
import { IUserContext } from "../User.context";
|
||||
import { TCreationUser_Attributes, User_Model } from "../sequelize/user.model";
|
||||
import { UserCreationAttributes, User_Model } from "../sequelize/user.model";
|
||||
|
||||
export interface IUserMapper
|
||||
extends ISequelizeMapper<User_Model, TCreationUser_Attributes, User> {}
|
||||
extends ISequelizeMapper<User_Model, UserCreationAttributes, User> {}
|
||||
|
||||
class UserMapper
|
||||
extends SequelizeMapper<User_Model, TCreationUser_Attributes, User>
|
||||
extends SequelizeMapper<User_Model, UserCreationAttributes, User>
|
||||
implements IUserMapper
|
||||
{
|
||||
public constructor(props: { context: IUserContext }) {
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
Sequelize,
|
||||
} from "sequelize";
|
||||
|
||||
export type TCreationUser_Attributes = InferCreationAttributes<User_Model>;
|
||||
export type UserCreationAttributes = InferCreationAttributes<User_Model>;
|
||||
|
||||
export class User_Model extends Model<
|
||||
InferAttributes<User_Model>,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { AuthRouter } from "@/contexts/auth";
|
||||
import { CatalogRouter } from "@/contexts/catalog";
|
||||
import { createMiddlewareMap } from "@/contexts/common/infrastructure/express";
|
||||
import { DealerRouter } from "@/contexts/sales/infrastructure/express";
|
||||
import { UserRouter } from "@/contexts/users";
|
||||
import Express from "express";
|
||||
import { createContextMiddleware } from "./context.middleware";
|
||||
@ -12,28 +13,21 @@ export const v1Routes = () => {
|
||||
res.send("Hello world!");
|
||||
});
|
||||
|
||||
routes.use(
|
||||
(
|
||||
req: Express.Request,
|
||||
res: Express.Response,
|
||||
next: Express.NextFunction,
|
||||
) => {
|
||||
res.locals["context"] = createContextMiddleware();
|
||||
res.locals["middlewares"] = createMiddlewareMap();
|
||||
routes.use((req: Express.Request, res: Express.Response, next: Express.NextFunction) => {
|
||||
res.locals["context"] = createContextMiddleware();
|
||||
res.locals["middlewares"] = createMiddlewareMap();
|
||||
|
||||
return next();
|
||||
},
|
||||
);
|
||||
return next();
|
||||
});
|
||||
routes.use((req, res, next) => {
|
||||
console.log(
|
||||
`[${new Date().toLocaleTimeString()}] Incoming request to ${req.path}`,
|
||||
);
|
||||
console.log(`[${new Date().toLocaleTimeString()}] Incoming request to ${req.path}`);
|
||||
next();
|
||||
});
|
||||
|
||||
AuthRouter(routes);
|
||||
UserRouter(routes);
|
||||
CatalogRouter(routes);
|
||||
DealerRouter(routes);
|
||||
|
||||
return routes;
|
||||
};
|
||||
|
||||
@ -2,4 +2,5 @@ export * from "./common";
|
||||
|
||||
export * from "./auth";
|
||||
export * from "./catalog";
|
||||
export * from "./sales";
|
||||
export * from "./users";
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
import Joi from "joi";
|
||||
import { Result, RuleValidator } from "../../../../common";
|
||||
|
||||
export interface ICreateDealer_Request_DTO {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export function ensureCreateDealer_Request_DTOIsValid(dealerDTO: ICreateDealer_Request_DTO) {
|
||||
const schema = Joi.object({
|
||||
id: Joi.string(),
|
||||
name: Joi.string(),
|
||||
}).unknown(true);
|
||||
|
||||
const result = RuleValidator.validate<ICreateDealer_Request_DTO>(schema, dealerDTO);
|
||||
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
}
|
||||
|
||||
return Result.ok(true);
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
export interface ICreateDealer_Response_DTO {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./ICreateDealer_Request.dto";
|
||||
export * from "./ICreateDealer_Response.dto";
|
||||
@ -0,0 +1,22 @@
|
||||
import Joi from "joi";
|
||||
import { Result, RuleValidator } from "../../../../common";
|
||||
|
||||
export interface ICreateDealer_Request_DTO {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export function ensureCreateDealer_Request_DTOIsValid(dealerDTO: ICreateDealer_Request_DTO) {
|
||||
const schema = Joi.object({
|
||||
id: Joi.string(),
|
||||
name: Joi.string(),
|
||||
}).unknown(true);
|
||||
|
||||
const result = RuleValidator.validate<ICreateDealer_Request_DTO>(schema, dealerDTO);
|
||||
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
}
|
||||
|
||||
return Result.ok(true);
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
export interface IGetDealerResponse_DTO {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export * from "./IGetDealer_Response.dto";
|
||||
@ -0,0 +1,4 @@
|
||||
export interface IListDealers_Response_DTO {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export * from "./IListDealers_Response.dto";
|
||||
@ -0,0 +1,20 @@
|
||||
import Joi from "joi";
|
||||
import { Result, RuleValidator } from "../../../../common";
|
||||
|
||||
export interface IUpdateDealer_Request_DTO {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export function ensureUpdateDealer_Request_DTOIsValid(dealerDTO: IUpdateDealer_Request_DTO) {
|
||||
const schema = Joi.object({
|
||||
name: Joi.string(),
|
||||
}).unknown(true);
|
||||
|
||||
const result = RuleValidator.validate<IUpdateDealer_Request_DTO>(schema, dealerDTO);
|
||||
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
}
|
||||
|
||||
return Result.ok(true);
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
export interface IUpdateDealer_Response_DTO {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./IUpdateDealer_Request.dto";
|
||||
export * from "./IUpdateDealer_Response.dto";
|
||||
4
shared/lib/contexts/sales/application/dto/index.ts
Normal file
4
shared/lib/contexts/sales/application/dto/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from "./CreateDealer.dto";
|
||||
export * from "./GetDealer.dto";
|
||||
export * from "./IListDealers.dto";
|
||||
export * from "./UpdateDealer.dto";
|
||||
1
shared/lib/contexts/sales/application/index.ts
Normal file
1
shared/lib/contexts/sales/application/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./dto";
|
||||
1
shared/lib/contexts/sales/index.ts
Normal file
1
shared/lib/contexts/sales/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./application";
|
||||
@ -1,10 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"baseUrl": "./lib/*" /* Base directory to resolve non-absolute module names. */,
|
||||
"allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user