UNPKG

adba

Version:
271 lines (270 loc) 11.8 kB
"use strict"; /** * @file express-router.ts * @description This file provides functionalities for creating an Express router with dynamic routes based on ORM models. */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.modifyDefinedRoutes = modifyDefinedRoutes; exports.addTableAlias = addTableAlias; exports.routesObject = routesObject; exports.listRoutes = listRoutes; exports.default = expressRouter; const objection_1 = require("objection"); const express_1 = __importDefault(require("express")); const moment_1 = __importDefault(require("moment")); const change_case_all_1 = require("change-case-all"); const uuid_1 = require("uuid"); const dbl_utils_1 = require("dbl-utils"); const controller_1 = __importDefault(require("./controller")); const controller_2 = __importDefault(require("./controller")); const status_codes_1 = __importDefault(require("./status-codes")); const aliasing = {}; /** * Predefined RESTful routes mapped to controller actions. */ const definedREST = { 'GET /': 'list', 'POST /': 'list', 'PUT /': 'insert', 'PATCH /': 'update', 'DELETE /': 'delete', 'GET /:id': 'selectById', 'PATCH /:id': 'update', 'DELETE /:id': 'delete', }; /** * Modifies predefined routes by either adding new ones or removing existing ones. * @param defs - The definitions to be added or removed. * @param remove - Whether to remove the definitions. */ function modifyDefinedRoutes(defs, remove = false) { if (remove && Array.isArray(defs)) { defs.forEach((k) => (delete definedREST[k])); } else { Object.assign(definedREST, defs); } } /** * Adds alias mappings for table names. * @param alias - The alias mappings. */ function addTableAlias(alias) { Object.assign(aliasing, alias); } /** * Prepares the routes object by building route entries for each model. * @param routesObj - The routes object to populate. * @param TheModel - The model for which to create routes. * @param controllers - The controllers associated with the models. * @param includeTable - Whether to include the table in route definitions. */ function prepareRoutesObj(routesObj, TheModel, controllers, includeTable) { if (includeTable === false) return; const tableName = TheModel.tableName; const TheController = Object.values(controllers).find(C => { const ctrl = new C(objection_1.Model); return ctrl.Model.tableName === tableName; }) || controller_1.default; if (includeTable === true) { Object.entries(definedREST).forEach(([service, action]) => { buildRoutesObj(routesObj, tableName, service, action, TheController, TheModel); }); return; } const { defaultAction = 'includes' } = includeTable, rest = __rest(includeTable, ["defaultAction"]); if (defaultAction === 'excludes') { Object.entries(rest).forEach(([service, action]) => { if (!action) return; if (action === true) action = definedREST[service]; buildRoutesObj(routesObj, tableName, service, action, TheController, TheModel); }); } else { const cpRest = Object.assign({}, rest); Object.entries(definedREST).forEach(([service, action]) => { if (rest[service] === false) { delete cpRest[service]; return; } if (typeof rest[service] === 'string') { action = rest[service]; delete cpRest[service]; } buildRoutesObj(routesObj, tableName, service, action, TheController, TheModel); }); // Add custom routes not defined in definedREST Object.entries(cpRest).forEach(([service, action]) => { if (!action) return; if (action === true) action = definedREST[service]; buildRoutesObj(routesObj, tableName, service, action, TheController, TheModel); }); } } /** * Constructs the routes object with respective service paths, actions, and controllers. * @param routesObj - Object to store constructed routes. * @param tableName - The table name for the model. * @param service - The HTTP method and endpoint. * @param action - The action to be performed. * @param TheController - The controller class. * @param TheModel - The model class. */ function buildRoutesObj(routesObj, tableName, service, action, TheController, TheModel) { const [m, path] = service.split(' '); const slugTableName = aliasing[tableName] || (0, change_case_all_1.kebabCase)(tableName); const method = m.toUpperCase(); const servicePath = `/${slugTableName}${path}`; routesObj[`${method} ${servicePath}`] = [method, servicePath, action, TheController, TheModel]; } /** * Generates an object containing routes for each model and controller based on provided configuration. * @param models - The models to generate routes for. * @param controllers - The associated controllers for each model. * @param config - The configuration for including or excluding routes. * @returns The constructed routes object. */ function routesObject(models, controllers = {}, config = {}) { const routesObj = {}; if (config.filters) { const _a = config.filters, { defaultAction = 'includes' } = _a, rest = __rest(_a, ["defaultAction"]); if (defaultAction === 'excludes') { Object.entries(rest).forEach(([tableName, includeTable]) => { if (!includeTable) return; const TheModel = Object.values(models).find(M => M.tableName === tableName); if (!TheModel) throw new Error(`Model for **${tableName}** Not Found`); prepareRoutesObj(routesObj, TheModel, controllers, includeTable); }); } else { // Include logic Object.values(models).forEach(TheModel => { const includeTable = rest[TheModel.tableName] !== undefined ? rest[TheModel.tableName] : (rest['*'] !== undefined ? rest['*'] : true); prepareRoutesObj(routesObj, TheModel, controllers, includeTable); }); } } else { Object.values(models).forEach(TheModel => { prepareRoutesObj(routesObj, TheModel, controllers, true); }); } return routesObj; } /** * Lists the routes currently defined in the Express router. * @param router - The Express router. * @returns An array of strings representing each route method and path. */ function listRoutes(router) { const routes = []; router.stack.forEach((middleware) => { if (middleware.route) { // It's a defined route const method = Object.keys(middleware.route.methods)[0].toUpperCase(); const routePath = middleware.route.path; routes.push(`${method} ${routePath}`); } }); return routes; } /** * Main function to configure an Express router with dynamic routes. * @param routesObject - The routes object containing route definitions. * @param config - Configuration options for the router. * @returns The configured Express router. */ function expressRouter(routesObject, { router = express_1.default.Router(), beforeProcess = (tn, a, data, i) => data, afterProcess = (tn, a, data, i) => data, debugLog = false } = {}) { Object.values(routesObject).forEach((value) => { const [method, path, action, TheController, TheModel] = value; const routerMethod = (router[method.toLowerCase()]).bind(router); routerMethod(path, (req, res, next) => __awaiter(this, void 0, void 0, function* () { const protocol = req.protocol; const host = req.get('host'); const _idx = (0, uuid_1.v4)(); console.log(''); console.log(''); console.time(_idx); console.group((0, moment_1.default)().format('YYYY-MM-DD HH:mm:ss')); console.log(_idx, '>', `${method} ${protocol}://${host}${req.originalUrl}`, controller_2.default.name, action); if (debugLog) { console.debug('HEADERS', req.headers); console.debug('PARAMS', req.params); console.debug('QUERY', req.query); console.debug('BODY', req.body); console.debug('COOKIES', req.cookies); } try { const controller = new TheController(TheModel); const all = Object.assign(Object.assign(Object.assign({}, req.body || {}), (0, dbl_utils_1.unflatten)(req.query || {})), req.params || {}); const ctrlAction = controller[action].bind(controller); const inputData = yield beforeProcess(controller.Model.tableName, action, all, _idx); const outputData = yield ctrlAction(inputData); const payload = yield afterProcess(controller.Model.tableName, action, outputData, _idx); // Check if the output data is an error or if it needs a special response if (payload instanceof Error) throw payload; if (!payload) throw (0, status_codes_1.default)(503); else res.status(payload.status).json(payload); if (debugLog) { console.debug('RESPONSE', payload); } } catch (error) { if (error instanceof Error) { console.error(error); const e = error; const code = e.statusCode || e.code || 0; const payload = (0, status_codes_1.default)(500, code); payload.data = error.message; res.status(500).json(payload); } else if (typeof error.status === 'number') { res.status(error.status).json(error); } } console.timeEnd(_idx); console.groupEnd(); })); }); // Setup a default GET route that lists all available routes router.get('/', (req, res) => { const availableRoutes = listRoutes(router); const success = (0, status_codes_1.default)(200); success.data = availableRoutes; res.status(success.status).json(success); }); return router; }