UNPKG

@carlosbajo/roket-micro

Version:

framework para microservicios con google/pubsub

285 lines (272 loc) 8.99 kB
const Boom = require('boom'); const path = require('path'); const includeAll = require('include-all'); const _ = require('lodash'); const mongoose = require('mongoose'); const Router = require('koa-router'); const Jwt = require('jsonwebtoken'); const loadClass = require('mongoose-class-wrapper'); const access = require('./access'); const gLogger = require('./google-loggin'); const { name } = require(`${process.cwd()}/package.json`); // eslint-disable-line const NotImplemented = Boom.notImplemented; const MethodNotAllowed = Boom.methodNotAllowed; const init = { controllers: {}, routes: {}, modules: {}, services: {}, models: {} }; const schemaOptions = { toObject: { virtuals: true }, toJSON: { virtuals: true }, timestamps: true }; const controllers = () => new Promise((resolve, reject) => { includeAll.optional({ dirname: path.resolve(`${process.cwd()}/api/controllers`), filter: /(.+)\.js$/, }, (err, response) => { if (err) return reject(err); return resolve(response); }); }); const config = () => new Promise((resolve, reject) => { includeAll.optional({ dirname: path.resolve(`${process.cwd()}/api/config`), filter: /(.+)\.js$/, identity: false, useGlobalIdForKeyName: false }, (err, response) => { if (err) return reject(err); return resolve(response); }); }); const modelsSchemas = () => new Promise((resolve, reject) => { includeAll.optional({ dirname: path.resolve(`${process.cwd()}/api/schemas`), filter: /(.+)\.js/, identity: false, useGlobalIdForKeyName: false }, (err, response) => { if (err) return reject(err); return resolve(response); }); }); const modelsClass = () => new Promise((resolve, reject) => { includeAll.optional({ dirname: path.resolve(`${process.cwd()}/api/models`), filter: /(.+)\.js$/, identity: false, useGlobalIdForKeyName: false }, (err, response) => { if (err) return reject(err); return resolve(response); }); }); const validatePolices = async (ctx, plcs) => { for (let index = 0; index < plcs.length; index += 1) { let fnName = plcs[index]; fnName = fnName.replace('fw.', ''); if (config.polices[fnName]) { const response = await config.polices[fnName](ctx); // eslint-disable-line if (!response) return response; } } return true; }; const generateModels = (mdls, classes, datasources) => { const response = {}; Object.keys(mdls).forEach(name => { // eslint-disable-line const model = mdls[name]; const modelName = _.camelCase(name); const modelSchema = new mongoose.Schema(model.properties, schemaOptions); if (classes[modelName]) { modelSchema.plugin(loadClass, classes[modelName]); } response[modelName] = datasources(model.datasource).model(modelName, modelSchema); if (model.softDelete) { const modelDepName = `${modelName}DLT`; const modelDepSchema = model.properties; Object.keys(modelDepSchema).forEach(obj => { if (modelDepSchema[obj].unique) delete modelDepSchema[obj].unique; }); response[modelDepName] = datasources(model.datasource).model(modelDepName, modelDepSchema); } }); return response; }; const getRootRoutes = () => { const router = new Router(); router.get('/', async (ctx) => { ctx.status = 404; return ctx.status; }); router.get('/healthz', async (ctx) => { ctx.status = 200; return ctx.status; }); router.get('/_ah/health', async (ctx) => { ctx.body = { res: 'OK' }; ctx.status = 200; return ctx.status; }); return router; }; const generateRoutes = (routes, ctrls, logger) => { const router = new Router(); Object.keys(routes).forEach(route => { const [verb, uri] = route.split(' '); const definition = routes[route]; if (!ctrls[definition.controller]) { logger.error(`Controlador ${definition.controller} no encontrado`); return; } const action = ctrls[definition.controller][definition.action]; router[_.toLower(verb)](uri, async (ctx) => { try { if (definition.polices) await validatePolices(ctx, definition.polices); return await action(ctx); } catch (ex) { logger.error(ex); ctx.body = Boom.wrap(ex); ctx.status = ctx.body.output.statusCode; if (ctx.body.output.statusCode == 500) ctx.errors.report(); // eslint-disable-line return ctx; } }); }); return router; }; const consumers = () => new Promise((resolve, reject) => { includeAll.optional({ dirname: path.resolve(`${process.cwd()}/api/consumer`), filter: /(.+)\.js$/, identity: false, useGlobalIdForKeyName: false }, (err, response) => { if (err) return reject(err); return resolve(response); }); }); const generateRoutesTemp = (consum, logger, pubsub) => { const router = new Router(); const format = {}; if (!pubsub) return; Object.keys(pubsub).forEach(async topic => { const [type, module, method] = topic.split('.'); const rename = `POST /${type}/${module}/${method}`; format[rename] = { controller: pubsub[topic].consumer, action: pubsub[topic].action, access: pubsub[topic].access }; }); const log = gLogger(logger); Object.keys(format).forEach(route => { const [verb, uri] = route.split(' '); const definition = format[route]; if (!consum[definition.controller]) { logger.error(`Controlador ${definition.controller} no encontrado`); return; } const action = consum[definition.controller][definition.action]; router[_.toLower(verb)](uri, async (ctx) => { try { if (definition.polices) await validatePolices(ctx, definition.polices); let val = ctx.request.body; if (!_.isObject(ctx.request.body)) val = JSON.parse(val); if (val.jwt) { val.usr = Jwt.verify(val.jwt, process.env.JWTSECRET); } if (format[route].access) { const validAccess = access(val.usr, format[route].access); if (!validAccess) { throw Boom.unauthorized('Acceso no autorizado'); } } log(route, val.usr.id, val); return await action(ctx); } catch (ex) { logger.error(ex); ctx.body = Boom.wrap(ex); ctx.status = ctx.body.output.statusCode; if (ctx.body.output.statusCode == 500) ctx.errors.report(); // eslint-disable-line return ctx; } }); }); return router; // eslint-disable-line }; const loadConsumers = async (consumer, pubsub, logger, gPubsub) => { // eslint-disable-line if (!pubsub) { logger.warn('No pubsub found'); return; } const log = gLogger(logger); try { Object.keys(pubsub).forEach(async topic => { const googleTools = await gPubsub.loadSubscription(pubsub, topic); const responseStream = googleTools.consumerClient; const onMessage = message => { message.ack(); let val = message.data; if (!_.isObject(message.data)) val = JSON.parse(val); if (val.jwt) { val.usr = Jwt.verify(val.jwt, process.env.JWTSECRET); } if (pubsub[topic].access) { const validAccess = access(val.usr, pubsub[topic].access); if (!validAccess) { const err = Boom.unauthorized('Acceso no autorizado'); googleTools.topic.publish(JSON.stringify(err)).then(res => logger.error(res)); } } log(topic, val.usr.id, val); consumer[pubsub[topic].consumer][pubsub[topic].action](val); }; const onError = error => logger.error('error on stream consumer', error); responseStream.on('message', onMessage); responseStream.on('error', onError); }); } catch (e) { throw Boom.conflict('Error', e); } }; module.exports = async (app) => { try { app = _.merge(app, init); app.controllers = await controllers(); app.modelsClass = await modelsClass(); const cfg = await config(); app.routes = cfg.routes; app.modelsSchemas = await modelsSchemas(); app.consumers = await consumers(); // setTimeout(() => loadConsumers(app.consumers, cfg.pubsub, app.logger, app.pubsub), 2000); app.models = generateModels(app.modelsSchemas, app.modelsClass, app.datasources); const rootRoutes = getRootRoutes(); const router = generateRoutes(app.routes, app.controllers, app.logger); const routerConsumer = generateRoutesTemp(app.consumers, app.logger, cfg.pubsub); app.use(rootRoutes.routes()); app.use(router.routes()); if (routerConsumer) app.use(routerConsumer.routes()); app.use(router.allowedMethods({ throw: true, notImplemented: () => new NotImplemented(), methodNotAllowed: () => new MethodNotAllowed() })); app.types = mongoose.Types; } catch (exp) { app.logger.warn(exp); } return app; };