@hicoder/express-core
Version:
Restful API exposure middleware for Express and Mongoose based framework. Provide Rest API automatically based on Mongoose schema. It can also work with angular-core to develop end to end MEAN stack web applications.
368 lines (327 loc) • 10 kB
JavaScript
const mongoose = require('mongoose');
const express = require('express');
const createError = require('http-errors');
const { processViewStr } = require('./view-str');
const {
moveRouterStackTailToHead,
moveRouterStackTailForward,
archiveDocument,
} = require('../util');
const RestController = require('./rest_controller');
const RestRouter = require('./rest_sub_router');
const historySchemaDef = require('../model/model.history');
const PredefinedPatchFields = {
muser_id: { type: String, index: true },
mmodule_name: { type: String, index: true },
permissionTags: { type: [{ type: String }], required: false, },
};
const _setModuleName = function (name) {
return function (req, res, next) {
req.mddsModuleName = name;
return next();
};
};
const registerSchemas = function (
schemas,
restController,
db_app_name,
db_module_name,
moduleName,
patch,
owner,
permissionStore
) {
for (let schemaName in schemas) {
const schemaDef = schemas[schemaName];
let name = schemaName.toLowerCase();
let api;
if ('api' in schemas[schemaName]) {
api = schemas[schemaName].api;
} else {
api = 'LCRUDA';
}
const tags = schemaDef.tags;
const schm = schemaDef.schema;
const mraBE = schemaDef.mraBE || {};
let collectionName = mraBE.collection;
let model;
if (schm) {
// apply archive
schm.plugin(archiveDocument);
const patchFields = schemaDef.patch || patch || [];
for (const p of patchFields) {
if (p in PredefinedPatchFields) {
const f = {};
f[p] = PredefinedPatchFields[p];
schm.add(f);
} else {
console.warn(
'Warning: ignore patching. Field is not a predefined patch fields:',
p
);
}
}
schm.set('toObject', { getters: false, virtuals: true });
schm.set('toJSON', { getters: false, virtuals: true });
if (collectionName) {
model = mongoose.model(schemaName, schm, collectionName); //model uses given name and given collection
} else {
collectionName = '';
if (db_app_name) {
collectionName += `${db_app_name}_`;
}
if (db_module_name) {
collectionName += `${db_module_name}_`;
}
collectionName += name;
model = mongoose.model(schemaName, schm, collectionName); //model uses given name
}
}
//schemaDef.views in [briefView, detailView, CreateView, EditView, SearchView] sequence
const views = [];
const schemaViews = schemaDef.views;
if (schemaViews) {
for (let view of schemaDef.views) {
view = processViewStr(view);
//console.log("====view", view);
views.push(view);
}
}
//pass pure view string to register.
if (schm) {
// apply owner
const ownerConfig = schemaDef.owner || owner;
// tell controller to use save so mongoose logic of plugin can be triggered
schm.options.useSaveInsteadOfUpdate = true; //this is a special indicator to controller to use save.
restController.register(
schemaName,
schm,
views,
model,
moduleName,
ownerConfig,
mraBE,
tags
);
}
if (permissionStore && api) {
permissionStore.registerResource(schemaName, moduleName);
}
}
};
const meanRestExpressRouter = function (sysDef, moduleName, authConfig) {
const expressRouter = express.Router();
const restController = new RestController();
expressRouter.restController = restController;
if (!moduleName) moduleName = randomString(10);
const DB_CONFIG = sysDef.DB_CONFIG;
let db_app_name, db_module_name;
if (DB_CONFIG) {
db_app_name = DB_CONFIG.APP_NAME;
db_module_name = DB_CONFIG.MODULE_NAME;
}
if (!db_app_name || !db_module_name) {
throw new Error(
`APP Name and Module Name not provided for database. Please provide "DB_CONFIG" for your schema definition in module ${moduleName}.`
);
}
db_app_name = db_app_name.toLowerCase();
db_module_name = db_module_name.toLowerCase();
const setModuleName = _setModuleName(moduleName);
expressRouter.use(setModuleName);
let patch = []; //extra fields patching to the schema
// schema level patch and owner
if (sysDef.config && sysDef.config.patch) {
patch = sysDef.config.patch;
}
let owner = { enable: false, type: 'user' };
if (sysDef.config && sysDef.config.owner) {
owner = sysDef.config.owner;
}
let authzFunc;
let permissionStore;
if (authConfig) {
let authnFunc = authConfig.authnFunc;
if (authnFunc) expressRouter.use(authnFunc);
authzFunc = authConfig.authzFunc;
permissionStore = authConfig.permissionStore;
if (!sysDef.authz) sysDef.authz = {};
permissionStore.registerAuthz(moduleName, sysDef.authz);
let setModuleAuthz = authConfig.setPermissionFunc;
expressRouter.use(setModuleAuthz);
}
//console.log("*******sysDef", sysDef)
let schemas = sysDef.schemas || {};
expressRouter.schemas = schemas;
registerSchemas(
schemas,
restController,
db_app_name,
db_module_name,
moduleName,
patch,
owner,
permissionStore
);
// Find if history is enabled for any of the schemas.
let enableHistory = false;
for (let schemaName in schemas) {
let schemaDef = schemas[schemaName];
if (schemaDef.mraBE) {
if (schemaDef.mraBE.enableHistory) {
enableHistory = true;
} else {
schemaDef.mraBE.enableHistory = false;
}
} else {
schemaDef.mraBE = {
enableHistory: false,
};
}
}
// register BE only schemas,
const schemasBE = sysDef.schemasBE || {};
for (let schemaName in schemasBE) {
const schemaDef = schemasBE[schemaName][0]; // first element
const collectionName = schemasBE[schemaName][1]; // second element
// set mraBE collection
if (schemaDef.mraBE) {
schemaDef.mraBE.collection = collectionName;
} else {
schemaDef.mraBE = {
collection: collectionName,
};
}
schemasBE[schemaName] = schemaDef;
}
if (enableHistory) {
schemasBE['MddsCoreHistory'] = historySchemaDef;
}
// undefined - don't use patch, owner and not set permission in permissionStore for BE only schemas.
registerSchemas(
schemasBE,
restController,
db_app_name,
db_module_name,
moduleName,
undefined,
undefined,
undefined
);
let sub_routes = [];
for (let schemaName in schemas) {
const schemaDef = schemas[schemaName];
const name = schemaName.toLowerCase();
let api;
if ('api' in schemas[schemaName]) {
api = schemas[schemaName].api;
} else {
api = 'LCRUDA';
}
const mraBE = schemaDef.mraBE || {};
const filters = mraBE.filters || {};
const zInterfaces = mraBE.zInterfaces || {};
if (!api) continue;
sub_routes.push('/' + name);
if (!schemaDef.schema) continue;
restRouter = RestRouter(
restController,
schemaName,
authzFunc,
api,
filters,
zInterfaces
);
expressRouter.use('/' + name, restRouter);
}
expressRouter.get('/', (req, res, next) => {
res.send(sub_routes);
});
expressRouter.mdds = {
sub_routes: sub_routes,
authzFunc: authzFunc,
};
expressRouter.sub_routes = sub_routes;
//not supported api
expressRouter.use(function (req, res, next) {
next(createError(404));
});
//error handler
expressRouter.use(function (err, req, res, next) {
const e = { error: err.message, status: err.status || 500 };
if (req.app.get('env') === 'development') {
e.details = err.stack;
}
// render the error page
res.status(err.status || 500);
return res.json(e);
});
expressRouter.setEmailer = function (emailer, info) {
if (!restController.mddsProperties) {
restController.mddsProperties = {};
}
// no predefined template at this time.
// emailer.populateTemplatesToDB(templates);
let schemas = expressRouter.schemas;
for (let schemaName in schemas) {
const schemaDef = schemas[schemaName];
const mraBE = schemaDef.mraBE || {};
const emailerConf = mraBE.emailer || {};
const templates = emailerConf.templates || [];
const replacement = emailerConf.replacement || {};
const hooks = emailerConf.hooks || {};
if (templates.length > 0) {
emailer.populateTemplatesToDB(templates);
}
}
restController.mddsProperties.emailer = emailer;
// no predefined object at this time.
restController.mddsProperties.emailerObj = {};
if (info) {
restController.mddsProperties.emailerObj = info;
}
};
expressRouter.setMddsProperty = function (key, value) {
if (!restController.mddsProperties) {
restController.mddsProperties = {};
}
restController.mddsProperties[key] = value;
};
return expressRouter;
};
const _setSchemaName = function (name) {
return function (req, res, next) {
req.meanRestSchemaName = name;
return next();
};
};
meanRestExpressRouter.Hook = function (
expressRouter,
subRouterName,
subRouter
) {
const routPath = '/' + subRouterName;
if (
!expressRouter.mdds ||
!expressRouter.mdds.sub_routes.includes(routPath)
) {
console.warn(
'Hook subrouter failed. Not predefined in system definition: ' +
subRouterName
);
return expressRouter;
}
setSchemaName = _setSchemaName(subRouterName);
subRouter.use(setSchemaName);
let num = 1;
if (expressRouter.mdds && expressRouter.mdds.authzFunc) {
subRouter.use(expressRouter.mdds.authzFunc);
num++;
}
subRouter = moveRouterStackTailToHead(subRouter, num); //move forward to head
expressRouter.use(routPath, subRouter);
//move forward before error handling and subrouts get
expressRouter = moveRouterStackTailForward(expressRouter, 3);
return expressRouter;
};
module.exports = meanRestExpressRouter;