.
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.organizeImports": "explicit",
|
||||||
"source.fixAll.eslint": "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 { 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 Express from "express";
|
||||||
import httpStatus from "http-status";
|
import httpStatus from "http-status";
|
||||||
import passport from "passport";
|
import passport from "passport";
|
||||||
|
|
||||||
function compose(middlewareArray: any[]) {
|
function compose(middlewareArray: any[]) {
|
||||||
if (!middlewareArray.length) {
|
if (!middlewareArray.length) {
|
||||||
return function (
|
return function (req: Express.Request, res: Express.Response, next: Express.NextFunction) {
|
||||||
req: Express.Request,
|
|
||||||
res: Express.Response,
|
|
||||||
next: Express.NextFunction,
|
|
||||||
) {
|
|
||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -18,11 +14,7 @@ function compose(middlewareArray: any[]) {
|
|||||||
const head = middlewareArray[0];
|
const head = middlewareArray[0];
|
||||||
const tail = middlewareArray.slice(1);
|
const tail = middlewareArray.slice(1);
|
||||||
|
|
||||||
return function (
|
return function (req: Express.Request, res: Express.Response, next: Express.NextFunction) {
|
||||||
req: Express.Request,
|
|
||||||
res: Express.Response,
|
|
||||||
next: Express.NextFunction,
|
|
||||||
) {
|
|
||||||
head(req, res, function (err: unknown) {
|
head(req, res, function (err: unknown) {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
compose(tail)(req, res, next);
|
compose(tail)(req, res, next);
|
||||||
@ -37,7 +29,7 @@ export const isLoggedUser = compose([
|
|||||||
(req: Express.Request, res: Express.Response, next: Express.NextFunction) => {
|
(req: Express.Request, res: Express.Response, next: Express.NextFunction) => {
|
||||||
const user = <AuthUser>req.user;
|
const user = <AuthUser>req.user;
|
||||||
if (!user.isUser) {
|
if (!user.isUser) {
|
||||||
return generateExpressErrorResponse(req, res, httpStatus.UNAUTHORIZED);
|
return generateExpressError(req, res, httpStatus.UNAUTHORIZED);
|
||||||
}
|
}
|
||||||
next();
|
next();
|
||||||
},
|
},
|
||||||
@ -48,7 +40,7 @@ export const isAdminUser = compose([
|
|||||||
(req: Express.Request, res: Express.Response, next: Express.NextFunction) => {
|
(req: Express.Request, res: Express.Response, next: Express.NextFunction) => {
|
||||||
const user = <AuthUser>req.user;
|
const user = <AuthUser>req.user;
|
||||||
if (!user.isAdmin) {
|
if (!user.isAdmin) {
|
||||||
return generateExpressErrorResponse(req, res, httpStatus.UNAUTHORIZED);
|
return generateExpressError(req, res, httpStatus.UNAUTHORIZED);
|
||||||
}
|
}
|
||||||
next();
|
next();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -7,19 +7,19 @@ import { Email, Name, UniqueID } from "@shared/contexts";
|
|||||||
import { AuthUser, IAuthUserProps } from "../../domain/entities";
|
import { AuthUser, IAuthUserProps } from "../../domain/entities";
|
||||||
import { IAuthContext } from "../Auth.context";
|
import { IAuthContext } from "../Auth.context";
|
||||||
import {
|
import {
|
||||||
|
AuthUserCreationAttributes,
|
||||||
AuthUser_Model,
|
AuthUser_Model,
|
||||||
TCreationUser_Attributes,
|
|
||||||
} from "../sequelize/authUser.model";
|
} from "../sequelize/authUser.model";
|
||||||
|
|
||||||
export interface IUserMapper
|
export interface IUserMapper
|
||||||
extends ISequelizeMapper<
|
extends ISequelizeMapper<
|
||||||
AuthUser_Model,
|
AuthUser_Model,
|
||||||
TCreationUser_Attributes,
|
AuthUserCreationAttributes,
|
||||||
AuthUser
|
AuthUser
|
||||||
> {}
|
> {}
|
||||||
|
|
||||||
class AuthUserMapper
|
class AuthUserMapper
|
||||||
extends SequelizeMapper<AuthUser_Model, TCreationUser_Attributes, AuthUser>
|
extends SequelizeMapper<AuthUser_Model, AuthUserCreationAttributes, AuthUser>
|
||||||
implements IUserMapper
|
implements IUserMapper
|
||||||
{
|
{
|
||||||
public constructor(props: { context: IAuthContext }) {
|
public constructor(props: { context: IAuthContext }) {
|
||||||
|
|||||||
@ -6,7 +6,8 @@ import {
|
|||||||
Sequelize,
|
Sequelize,
|
||||||
} from "sequelize";
|
} from "sequelize";
|
||||||
|
|
||||||
export type TCreationUser_Attributes = InferCreationAttributes<AuthUser_Model>;
|
export type AuthUserCreationAttributes =
|
||||||
|
InferCreationAttributes<AuthUser_Model>;
|
||||||
|
|
||||||
export class AuthUser_Model extends Model<
|
export class AuthUser_Model extends Model<
|
||||||
InferAttributes<AuthUser_Model>,
|
InferAttributes<AuthUser_Model>,
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { registerCatalogRepository } from "../../../Catalog.repository";
|
|||||||
import { ListArticlesController } from "./ListArticlesController";
|
import { ListArticlesController } from "./ListArticlesController";
|
||||||
import { listArticlesPresenter } from "./presenter";
|
import { listArticlesPresenter } from "./presenter";
|
||||||
|
|
||||||
export const createListArticlesController = (context: ICatalogContext) => {
|
export const listArticlesController = (context: ICatalogContext) => {
|
||||||
registerCatalogRepository(context);
|
registerCatalogRepository(context);
|
||||||
|
|
||||||
return new ListArticlesController(
|
return new ListArticlesController(
|
||||||
@ -12,6 +12,6 @@ export const createListArticlesController = (context: ICatalogContext) => {
|
|||||||
useCase: new ListArticlesUseCase(context),
|
useCase: new ListArticlesUseCase(context),
|
||||||
presenter: listArticlesPresenter,
|
presenter: listArticlesPresenter,
|
||||||
},
|
},
|
||||||
context,
|
context
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { applyMiddleware } from "@/contexts/common/infrastructure/express";
|
import { applyMiddleware } from "@/contexts/common/infrastructure/express";
|
||||||
import Express from "express";
|
import Express from "express";
|
||||||
import { createListArticlesController } from "./controllers";
|
import { listArticlesController } from "./controllers";
|
||||||
|
|
||||||
/*catalogRoutes.get(
|
/*catalogRoutes.get(
|
||||||
"/:articleId",
|
"/:articleId",
|
||||||
@ -15,11 +15,7 @@ export const CatalogRouter = (appRouter: Express.Router) => {
|
|||||||
"/",
|
"/",
|
||||||
applyMiddleware("isLoggedUser"),
|
applyMiddleware("isLoggedUser"),
|
||||||
(req: Express.Request, res: Express.Response, next: Express.NextFunction) =>
|
(req: Express.Request, res: Express.Response, next: Express.NextFunction) =>
|
||||||
createListArticlesController(res.locals["context"]).execute(
|
listArticlesController(res.locals["context"]).execute(req, res, next)
|
||||||
req,
|
|
||||||
res,
|
|
||||||
next,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
appRouter.use("/catalog", catalogRoutes);
|
appRouter.use("/catalog", catalogRoutes);
|
||||||
|
|||||||
@ -16,19 +16,15 @@ import {
|
|||||||
IArticleProps,
|
IArticleProps,
|
||||||
} from "../../domain/entities";
|
} from "../../domain/entities";
|
||||||
import {
|
import {
|
||||||
|
ArticleCreationAttributes,
|
||||||
Article_Model,
|
Article_Model,
|
||||||
TCreationArticle_Attributes,
|
|
||||||
} from "../sequelize/article.model";
|
} from "../sequelize/article.model";
|
||||||
|
|
||||||
export interface IArticleMapper
|
export interface IArticleMapper
|
||||||
extends ISequelizeMapper<
|
extends ISequelizeMapper<Article_Model, ArticleCreationAttributes, Article> {}
|
||||||
Article_Model,
|
|
||||||
TCreationArticle_Attributes,
|
|
||||||
Article
|
|
||||||
> {}
|
|
||||||
|
|
||||||
class ArticleMapper
|
class ArticleMapper
|
||||||
extends SequelizeMapper<Article_Model, TCreationArticle_Attributes, Article>
|
extends SequelizeMapper<Article_Model, ArticleCreationAttributes, Article>
|
||||||
implements IArticleMapper
|
implements IArticleMapper
|
||||||
{
|
{
|
||||||
public constructor(props: { context: ICatalogContext }) {
|
public constructor(props: { context: ICatalogContext }) {
|
||||||
|
|||||||
@ -8,8 +8,7 @@ import {
|
|||||||
Sequelize,
|
Sequelize,
|
||||||
} from "sequelize";
|
} from "sequelize";
|
||||||
|
|
||||||
export type TCreationArticle_Attributes =
|
export type ArticleCreationAttributes = InferCreationAttributes<Article_Model>;
|
||||||
InferCreationAttributes<Article_Model>;
|
|
||||||
|
|
||||||
export class Article_Model extends Model<
|
export class Article_Model extends Model<
|
||||||
InferAttributes<Article_Model>,
|
InferAttributes<Article_Model>,
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { URL } from "url";
|
|||||||
import { IServerError } from "../../domain/errors";
|
import { IServerError } from "../../domain/errors";
|
||||||
import { IController } from "../Controller.interface";
|
import { IController } from "../Controller.interface";
|
||||||
import { InfrastructureError } from "../InfrastructureError";
|
import { InfrastructureError } from "../InfrastructureError";
|
||||||
import { generateExpressErrorResponse } from "./ExpressErrorResponse";
|
import { generateExpressError } from "./ExpressErrorResponse";
|
||||||
|
|
||||||
export abstract class ExpressController implements IController {
|
export abstract class ExpressController implements IController {
|
||||||
protected req: express.Request;
|
protected req: express.Request;
|
||||||
@ -17,19 +17,13 @@ export abstract class ExpressController implements IController {
|
|||||||
|
|
||||||
protected abstract executeImpl(): Promise<void | any>;
|
protected abstract executeImpl(): Promise<void | any>;
|
||||||
|
|
||||||
public execute(
|
public execute(req: express.Request, res: express.Response, next: express.NextFunction): void {
|
||||||
req: express.Request,
|
|
||||||
res: express.Response,
|
|
||||||
next: express.NextFunction,
|
|
||||||
): void {
|
|
||||||
this.req = req;
|
this.req = req;
|
||||||
this.res = res;
|
this.res = res;
|
||||||
this.next = next;
|
this.next = next;
|
||||||
|
|
||||||
this.serverURL = `${
|
this.serverURL = `${
|
||||||
new URL(
|
new URL(`${this.req.protocol}://${this.req.get("host")}${this.req.originalUrl}`).origin
|
||||||
`${this.req.protocol}://${this.req.get("host")}${this.req.originalUrl}`,
|
|
||||||
).origin
|
|
||||||
}/api/v1`;
|
}/api/v1`;
|
||||||
|
|
||||||
this.file = this.req && this.req["file"]; // <-- ????
|
this.file = this.req && this.req["file"]; // <-- ????
|
||||||
@ -100,11 +94,7 @@ export abstract class ExpressController implements IController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public internalServerError(message?: string, error?: IServerError) {
|
public internalServerError(message?: string, error?: IServerError) {
|
||||||
return this._errorResponse(
|
return this._errorResponse(httpStatus.INTERNAL_SERVER_ERROR, message, error);
|
||||||
httpStatus.INTERNAL_SERVER_ERROR,
|
|
||||||
message,
|
|
||||||
error,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public todoError(message?: string) {
|
public todoError(message?: string) {
|
||||||
@ -115,24 +105,15 @@ export abstract class ExpressController implements IController {
|
|||||||
return this._errorResponse(httpStatus.SERVICE_UNAVAILABLE, message);
|
return this._errorResponse(httpStatus.SERVICE_UNAVAILABLE, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _jsonResponse(
|
private _jsonResponse(statusCode: number, jsonPayload: any): express.Response<any> {
|
||||||
statusCode: number,
|
|
||||||
jsonPayload: any,
|
|
||||||
): express.Response<any> {
|
|
||||||
return this.res.status(statusCode).json(jsonPayload).send();
|
return this.res.status(statusCode).json(jsonPayload).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _errorResponse(
|
private _errorResponse(
|
||||||
statusCode: number,
|
statusCode: number,
|
||||||
message?: string,
|
message?: string,
|
||||||
error?: Error | InfrastructureError,
|
error?: Error | InfrastructureError
|
||||||
): express.Response<IError_Response_DTO> {
|
): express.Response<IError_Response_DTO> {
|
||||||
return generateExpressErrorResponse(
|
return generateExpressError(this.req, this.res, statusCode, message, error);
|
||||||
this.req,
|
|
||||||
this.res,
|
|
||||||
statusCode,
|
|
||||||
message,
|
|
||||||
error,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,15 @@
|
|||||||
import {
|
import { IErrorExtra_Response_DTO, IError_Response_DTO } from "@shared/contexts";
|
||||||
IErrorExtra_Response_DTO,
|
|
||||||
IError_Response_DTO,
|
|
||||||
} from "@shared/contexts";
|
|
||||||
import Express from "express";
|
import Express from "express";
|
||||||
import { UseCaseError } from "../../application";
|
import { UseCaseError } from "../../application";
|
||||||
import { InfrastructureError } from "../InfrastructureError";
|
import { InfrastructureError } from "../InfrastructureError";
|
||||||
import { ProblemDocument, ProblemDocumentExtension } from "./ProblemDocument";
|
import { ProblemDocument, ProblemDocumentExtension } from "./ProblemDocument";
|
||||||
|
|
||||||
export const generateExpressErrorResponse = (
|
export const generateExpressError = (
|
||||||
req: Express.Request,
|
req: Express.Request,
|
||||||
res: Express.Response,
|
res: Express.Response,
|
||||||
statusCode: number,
|
statusCode: number,
|
||||||
message?: string,
|
message?: string,
|
||||||
error?: Error | InfrastructureError,
|
error?: Error | InfrastructureError
|
||||||
): Express.Response<IError_Response_DTO> => {
|
): Express.Response<IError_Response_DTO> => {
|
||||||
const context = {
|
const context = {
|
||||||
user: res.locals.user || undefined,
|
user: res.locals.user || undefined,
|
||||||
@ -23,7 +20,7 @@ export const generateExpressErrorResponse = (
|
|||||||
|
|
||||||
const extension = new ProblemDocumentExtension({
|
const extension = new ProblemDocumentExtension({
|
||||||
context,
|
context,
|
||||||
extra: error ? { ...generateExpressError(error) } : {},
|
extra: error ? { ..._extractExtraInfoFromError(error) } : {},
|
||||||
});
|
});
|
||||||
|
|
||||||
const jsonPayload = new ProblemDocument(
|
const jsonPayload = new ProblemDocument(
|
||||||
@ -32,15 +29,13 @@ export const generateExpressErrorResponse = (
|
|||||||
detail: message,
|
detail: message,
|
||||||
instance: req.baseUrl,
|
instance: req.baseUrl,
|
||||||
},
|
},
|
||||||
extension,
|
extension
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(statusCode).json(jsonPayload).send();
|
return res.status(statusCode).json(jsonPayload).send();
|
||||||
};
|
};
|
||||||
|
|
||||||
function generateExpressError(
|
function _extractExtraInfoFromError(error: Error | InfrastructureError): IErrorExtra_Response_DTO {
|
||||||
error: Error | InfrastructureError,
|
|
||||||
): IErrorExtra_Response_DTO {
|
|
||||||
const useCaseError = error as UseCaseError;
|
const useCaseError = error as UseCaseError;
|
||||||
|
|
||||||
const payload = Array.isArray(useCaseError.payload)
|
const payload = Array.isArray(useCaseError.payload)
|
||||||
|
|||||||
@ -1,37 +1,27 @@
|
|||||||
import { Collection, Entity, ICollection, Result } from "@shared/contexts";
|
import { Collection, Entity, ICollection, Result } from "@shared/contexts";
|
||||||
import { Model, ValidationError } from "sequelize";
|
import { Model, ValidationError } from "sequelize";
|
||||||
import {
|
import { FieldValueError, RequiredFieldMissingError } from "../../domain/errors";
|
||||||
FieldValueError,
|
|
||||||
RequiredFieldMissingError,
|
|
||||||
} from "../../domain/errors";
|
|
||||||
import { InfrastructureError } from "../InfrastructureError";
|
import { InfrastructureError } from "../InfrastructureError";
|
||||||
|
|
||||||
|
export type MapperParamsType = Record<string, any>;
|
||||||
|
|
||||||
export interface ISequelizeMapper<
|
export interface ISequelizeMapper<
|
||||||
TModel extends Model,
|
TModel extends Model,
|
||||||
TModelAttributes,
|
TModelAttributes,
|
||||||
TEntity extends Entity<any>,
|
TEntity extends Entity<any>,
|
||||||
> {
|
> {
|
||||||
mapToDomain(source: TModel, params?: Record<string, any>): TEntity;
|
mapToDomain(source: TModel, params?: MapperParamsType): TEntity;
|
||||||
|
mapArrayToDomain(source: TModel[], params?: MapperParamsType): Collection<TEntity>;
|
||||||
mapArrayToDomain(
|
|
||||||
source: TModel[],
|
|
||||||
params?: Record<string, any>
|
|
||||||
): Collection<TEntity>;
|
|
||||||
|
|
||||||
mapArrayAndCountToDomain(
|
mapArrayAndCountToDomain(
|
||||||
source: TModel[],
|
source: TModel[],
|
||||||
totalCount: number,
|
totalCount: number,
|
||||||
params?: Record<string, any>
|
params?: MapperParamsType
|
||||||
): Collection<TEntity>;
|
): Collection<TEntity>;
|
||||||
|
|
||||||
mapToPersistence(
|
mapToPersistence(source: TEntity, params?: MapperParamsType): TModelAttributes;
|
||||||
source: TEntity,
|
|
||||||
params?: Record<string, any>
|
|
||||||
): TModelAttributes;
|
|
||||||
|
|
||||||
mapCollectionToPersistence(
|
mapCollectionToPersistence(
|
||||||
source: ICollection<TEntity>,
|
source: ICollection<TEntity>,
|
||||||
params?: Record<string, any>
|
params?: MapperParamsType
|
||||||
): TModelAttributes[];
|
): TModelAttributes[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,64 +33,49 @@ export abstract class SequelizeMapper<
|
|||||||
{
|
{
|
||||||
public constructor(protected props: any) {}
|
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);
|
return this.toDomainMappingImpl(source, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
public mapArrayToDomain(
|
public mapArrayToDomain(source: TModel[], params?: MapperParamsType): Collection<TEntity> {
|
||||||
source: TModel[],
|
return this.mapArrayAndCountToDomain(source, source ? source.length : 0, params);
|
||||||
params?: Record<string, any>
|
|
||||||
): Collection<TEntity> {
|
|
||||||
return this.mapArrayAndCountToDomain(
|
|
||||||
source,
|
|
||||||
source ? source.length : 0,
|
|
||||||
params
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public mapArrayAndCountToDomain(
|
public mapArrayAndCountToDomain(
|
||||||
source: TModel[],
|
source: TModel[],
|
||||||
totalCount: number,
|
totalCount: number,
|
||||||
params?: Record<string, any>
|
params?: MapperParamsType
|
||||||
): Collection<TEntity> {
|
): Collection<TEntity> {
|
||||||
const items = source
|
const items = source
|
||||||
? source.map((value, index: number) =>
|
? source.map((value, index: number) => this.toDomainMappingImpl!(value, { index, ...params }))
|
||||||
this.toDomainMappingImpl!(value, { index, ...params })
|
|
||||||
)
|
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
return new Collection(items, totalCount);
|
return new Collection(items, totalCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public mapToPersistence(
|
public mapToPersistence(source: TEntity, params?: MapperParamsType): TModelAttributes {
|
||||||
source: TEntity,
|
|
||||||
params?: Record<string, any>
|
|
||||||
): TModelAttributes {
|
|
||||||
return this.toPersistenceMappingImpl(source, params);
|
return this.toPersistenceMappingImpl(source, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
public mapCollectionToPersistence(
|
public mapCollectionToPersistence(
|
||||||
source: ICollection<TEntity>,
|
source: ICollection<TEntity>,
|
||||||
params?: Record<string, any>
|
params?: MapperParamsType
|
||||||
): TModelAttributes[] {
|
): TModelAttributes[] {
|
||||||
return source.items.map((value: TEntity, index: number) =>
|
return source.items.map((value: TEntity, index: number) =>
|
||||||
this.toPersistenceMappingImpl!(value, { index, ...params })
|
this.toPersistenceMappingImpl!(value, { index, ...params })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected toDomainMappingImpl(
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
source: TModel,
|
protected toDomainMappingImpl(source: TModel, params?: MapperParamsType): TEntity {
|
||||||
params?: Record<string, any>
|
|
||||||
): TEntity {
|
|
||||||
throw InfrastructureError.create(
|
throw InfrastructureError.create(
|
||||||
InfrastructureError.UNEXCEPTED_ERROR,
|
InfrastructureError.UNEXCEPTED_ERROR,
|
||||||
'Method "toDomainMappingImpl" not implemented!'
|
'Method "toDomainMappingImpl" not implemented!'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected toPersistenceMappingImpl(
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
source: TEntity,
|
protected toPersistenceMappingImpl(source: TEntity, params?: MapperParamsType): TModelAttributes {
|
||||||
params?: Record<string, any>
|
|
||||||
): TModelAttributes {
|
|
||||||
throw InfrastructureError.create(
|
throw InfrastructureError.create(
|
||||||
InfrastructureError.UNEXCEPTED_ERROR,
|
InfrastructureError.UNEXCEPTED_ERROR,
|
||||||
'Method "toPersistenceMappingImpl" not implemented!'
|
'Method "toPersistenceMappingImpl" not implemented!'
|
||||||
@ -118,20 +93,24 @@ export abstract class SequelizeMapper<
|
|||||||
protected mapsValue(
|
protected mapsValue(
|
||||||
row: TModel,
|
row: TModel,
|
||||||
key: string,
|
key: string,
|
||||||
customMapFn: (
|
customMapFn: (value: any, params: MapperParamsType) => Result<any, Error>,
|
||||||
value: any,
|
params: MapperParamsType = {
|
||||||
params: Record<string, any>
|
|
||||||
) => Result<any, Error>,
|
|
||||||
params: Record<string, any> = {
|
|
||||||
defaultValue: null,
|
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") {
|
if (!row || typeof row !== "object") {
|
||||||
console.debug(
|
console.debug(`Data row has not keys! Key ${key} not exists in data row!`);
|
||||||
`Data row has not keys! Key ${key} not exists in data row!`
|
|
||||||
);
|
|
||||||
} else if (!Object.hasOwn(row.dataValues, key)) {
|
} else if (!Object.hasOwn(row.dataValues, key)) {
|
||||||
console.debug(`Key ${key} not exists in data row!`);
|
console.debug(`Key ${key} not exists in data row!`);
|
||||||
} else {
|
} else {
|
||||||
@ -143,16 +122,46 @@ export abstract class SequelizeMapper<
|
|||||||
if (valueOrError.isFailure) {
|
if (valueOrError.isFailure) {
|
||||||
this.handleFailure(valueOrError.error, key);
|
this.handleFailure(valueOrError.error, key);
|
||||||
}
|
}
|
||||||
return valueOrError.object;
|
return valueOrError.object;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
protected mapsAssociation(
|
protected mapsAssociation(
|
||||||
row: TModel,
|
row: TModel,
|
||||||
associationName: string,
|
associationName: string,
|
||||||
customMapper: any,
|
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(
|
throw InfrastructureError.create(
|
||||||
InfrastructureError.UNEXCEPTED_ERROR,
|
InfrastructureError.UNEXCEPTED_ERROR,
|
||||||
'Custom mapper undefined at "mapsAssociation"!'
|
'Custom mapper undefined at "mapsAssociation"!'
|
||||||
@ -195,13 +204,10 @@ export abstract class SequelizeMapper<
|
|||||||
if (associatedDataOrError.isFailure) {
|
if (associatedDataOrError.isFailure) {
|
||||||
this.handleFailure(associatedDataOrError.error, associationName);
|
this.handleFailure(associatedDataOrError.error, associationName);
|
||||||
}
|
}
|
||||||
return associatedDataOrError.object;
|
return associatedDataOrError.object;*/
|
||||||
|
|
||||||
//const associatedData = row[association.accessors.get]();
|
|
||||||
//return associatedData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleFailure(error: Error, key: string) {
|
private _handleFailure(error: Error, key: string) {
|
||||||
if (error instanceof ValidationError) {
|
if (error instanceof ValidationError) {
|
||||||
this.handleInvalidFieldError(key, error);
|
this.handleInvalidFieldError(key, error);
|
||||||
} else {
|
} 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 { Email, Name, UniqueID } from "@shared/contexts";
|
||||||
import { IUserProps, User } from "../../domain";
|
import { IUserProps, User } from "../../domain";
|
||||||
import { IUserContext } from "../User.context";
|
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
|
export interface IUserMapper
|
||||||
extends ISequelizeMapper<User_Model, TCreationUser_Attributes, User> {}
|
extends ISequelizeMapper<User_Model, UserCreationAttributes, User> {}
|
||||||
|
|
||||||
class UserMapper
|
class UserMapper
|
||||||
extends SequelizeMapper<User_Model, TCreationUser_Attributes, User>
|
extends SequelizeMapper<User_Model, UserCreationAttributes, User>
|
||||||
implements IUserMapper
|
implements IUserMapper
|
||||||
{
|
{
|
||||||
public constructor(props: { context: IUserContext }) {
|
public constructor(props: { context: IUserContext }) {
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import {
|
|||||||
Sequelize,
|
Sequelize,
|
||||||
} from "sequelize";
|
} from "sequelize";
|
||||||
|
|
||||||
export type TCreationUser_Attributes = InferCreationAttributes<User_Model>;
|
export type UserCreationAttributes = InferCreationAttributes<User_Model>;
|
||||||
|
|
||||||
export class User_Model extends Model<
|
export class User_Model extends Model<
|
||||||
InferAttributes<User_Model>,
|
InferAttributes<User_Model>,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { AuthRouter } from "@/contexts/auth";
|
import { AuthRouter } from "@/contexts/auth";
|
||||||
import { CatalogRouter } from "@/contexts/catalog";
|
import { CatalogRouter } from "@/contexts/catalog";
|
||||||
import { createMiddlewareMap } from "@/contexts/common/infrastructure/express";
|
import { createMiddlewareMap } from "@/contexts/common/infrastructure/express";
|
||||||
|
import { DealerRouter } from "@/contexts/sales/infrastructure/express";
|
||||||
import { UserRouter } from "@/contexts/users";
|
import { UserRouter } from "@/contexts/users";
|
||||||
import Express from "express";
|
import Express from "express";
|
||||||
import { createContextMiddleware } from "./context.middleware";
|
import { createContextMiddleware } from "./context.middleware";
|
||||||
@ -12,28 +13,21 @@ export const v1Routes = () => {
|
|||||||
res.send("Hello world!");
|
res.send("Hello world!");
|
||||||
});
|
});
|
||||||
|
|
||||||
routes.use(
|
routes.use((req: Express.Request, res: Express.Response, next: Express.NextFunction) => {
|
||||||
(
|
res.locals["context"] = createContextMiddleware();
|
||||||
req: Express.Request,
|
res.locals["middlewares"] = createMiddlewareMap();
|
||||||
res: Express.Response,
|
|
||||||
next: Express.NextFunction,
|
|
||||||
) => {
|
|
||||||
res.locals["context"] = createContextMiddleware();
|
|
||||||
res.locals["middlewares"] = createMiddlewareMap();
|
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
},
|
});
|
||||||
);
|
|
||||||
routes.use((req, res, next) => {
|
routes.use((req, res, next) => {
|
||||||
console.log(
|
console.log(`[${new Date().toLocaleTimeString()}] Incoming request to ${req.path}`);
|
||||||
`[${new Date().toLocaleTimeString()}] Incoming request to ${req.path}`,
|
|
||||||
);
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
AuthRouter(routes);
|
AuthRouter(routes);
|
||||||
UserRouter(routes);
|
UserRouter(routes);
|
||||||
CatalogRouter(routes);
|
CatalogRouter(routes);
|
||||||
|
DealerRouter(routes);
|
||||||
|
|
||||||
return routes;
|
return routes;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,4 +2,5 @@ export * from "./common";
|
|||||||
|
|
||||||
export * from "./auth";
|
export * from "./auth";
|
||||||
export * from "./catalog";
|
export * from "./catalog";
|
||||||
|
export * from "./sales";
|
||||||
export * from "./users";
|
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