141 lines
3.3 KiB
TypeScript
141 lines
3.3 KiB
TypeScript
import { Response, Request, NextFunction } from 'express';
|
|
import { matchedData, validationResult } from 'express-validator';
|
|
import accepts from 'accepts';
|
|
import { isArray, drop, first, camelCase, snakeCase, omit, set, get } from 'lodash';
|
|
import { mapKeysDeep } from 'utils';
|
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
|
|
|
export default class BaseController {
|
|
/**
|
|
* Converts plain object keys to cameCase style.
|
|
* @param {Object} data
|
|
*/
|
|
protected dataToCamelCase(data) {
|
|
return mapKeysDeep(data, (v, k) => camelCase(k));
|
|
}
|
|
|
|
/**
|
|
* Matches the body data from validation schema.
|
|
* @param {Request} req
|
|
* @param options
|
|
*/
|
|
protected matchedBodyData(req: Request, options: any = {}) {
|
|
const data = matchedData(req, {
|
|
locations: ['body'],
|
|
includeOptionals: true,
|
|
...omit(options, ['locations']), // override any propery except locations.
|
|
});
|
|
return this.dataToCamelCase(data);
|
|
}
|
|
|
|
/**
|
|
* Matches the query data from validation schema.
|
|
* @param {Request} req
|
|
*/
|
|
protected matchedQueryData(req: Request) {
|
|
const data = matchedData(req, {
|
|
locations: ['query'],
|
|
});
|
|
return this.dataToCamelCase(data);
|
|
}
|
|
|
|
/**
|
|
* Validate validation schema middleware.
|
|
* @param {Request} req
|
|
* @param {Response} res
|
|
* @param {NextFunction} next
|
|
*/
|
|
protected validationResult(req: Request, res: Response, next: NextFunction) {
|
|
const validationErrors = validationResult(req);
|
|
|
|
if (!validationErrors.isEmpty()) {
|
|
return res.boom.badData(null, {
|
|
code: 'validation_error',
|
|
...validationErrors,
|
|
});
|
|
}
|
|
next();
|
|
}
|
|
|
|
/**
|
|
* Sets localization to response object by the given path.
|
|
* @param {Response} response -
|
|
* @param {string} path -
|
|
* @param {Request} req -
|
|
*/
|
|
private setLocalizationByPath(
|
|
response: any,
|
|
path: string,
|
|
req: Request,
|
|
) {
|
|
const DOT = '.';
|
|
|
|
if (isArray(response)) {
|
|
response.forEach((va) => {
|
|
const currentPath = first(path.split(DOT));
|
|
const value = get(va, currentPath);
|
|
|
|
if (isArray(value)) {
|
|
const nextPath = drop(path.split(DOT)).join(DOT);
|
|
this.setLocalizationByPath(value, nextPath, req);
|
|
} else {
|
|
set(va, path, req.__(value));
|
|
}
|
|
})
|
|
} else {
|
|
const value = get(response, path);
|
|
set(response, path, req.__(value));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Transform the given data to response.
|
|
* @param {any} data
|
|
*/
|
|
protected transfromToResponse(
|
|
data: any,
|
|
translatable?: string | string[],
|
|
req?: Request
|
|
) {
|
|
const response = mapKeysDeep(data, (v, k) => snakeCase(k));
|
|
|
|
if (translatable) {
|
|
const translatables = Array.isArray(translatable)
|
|
? translatable
|
|
: [translatable];
|
|
|
|
translatables.forEach((path) => {
|
|
this.setLocalizationByPath(response, path, req);
|
|
});
|
|
}
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* Async middleware.
|
|
* @param {function} callback
|
|
*/
|
|
protected asyncMiddleware(callback) {
|
|
return asyncMiddleware(callback);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {Request} req
|
|
* @returns
|
|
*/
|
|
protected accepts(req) {
|
|
return accepts(req);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {Request} req
|
|
* @param {string[]} types
|
|
* @returns {string}
|
|
*/
|
|
protected acceptTypes(req: Request, types: string[]) {
|
|
return this.accepts(req).types(types);
|
|
}
|
|
}
|