UNPKG

@edenjs/cli

Version:

Web Application Framework built on Express.js and Redis

425 lines (360 loc) 10.5 kB
// Require dependencies import sirv from 'sirv'; import polka from 'polka'; import config from 'config'; import multer from 'multer'; import session from 'express-session'; import redirect from '@polka/redirect'; import bodyParser from 'body-parser'; import polkaCompat from 'polka-compat'; import responseTime from 'response-time'; import cookieParser from 'cookie-parser'; import SessionStore from '@edenjs/session-store'; import { v4 as uuid } from 'uuid'; // Require helpers const aclHelper = helper('user/acl'); // Require Eden dependencies import view from './view'; /** * Create Router class */ export default class EdenRouter { /** * Construct Router class */ constructor(eden) { // set eden this.eden = eden; // Set variables this.app = null; this.multer = null; // Bind methods this.build = this.build.bind(this); // Bind private methods this.apiAction = this.apiAction.bind(this); this.initAction = this.initAction.bind(this); this.errorAction = this.errorAction.bind(this); // Bind super private methods this.buildRoute = this.buildRoute.bind(this); this.buildUpload = this.buildUpload.bind(this); // Run build this.building = this.build(); } // ///////////////////////////////////////////////////////////////////////////////////////////// // // Build Methods // // ///////////////////////////////////////////////////////////////////////////////////////////// /** * Build Router * * @async */ async build() { // Log building to debug this.eden.logger.log('debug', 'building eden router', { class : this.constructor.name, }); // create app this.app = polka({ onError : this.errorAction }); // create express app await this.eden.hook('eden.router.app', this.app, () => { // Create server return this.app.listen(this.eden.get('config.port')); }); // Log built to debug this.eden.logger.log('info', `[${this.eden.get('config.host')}:${this.eden.get('config.port')}] server listening`, { class : this.constructor.name, }); // Set multer this.multer = multer(config.get('upload') || { dest : '/tmp', }); // hook multer await this.eden.hook('eden.router.multer', this.multer); // Loop HTTP request types ['use', 'get', 'post', 'push', 'delete', 'all'].forEach((type) => { // Create HTTP request method this[type] = (...typeArgs) => { // Call express HTTP request method this.app[type](...typeArgs); }; }); // create initial route this.app.use(polkaCompat()); this.app.use(this.initAction); this.app.use('api', this.apiAction); // Run eden app hook await this.eden.hook('eden.app', this.app, () => { // initialize SessionStore.initialize(session); // Add express middleware this.app.use(responseTime()); this.app.use(cookieParser(config.get('secret'))); this.app.use(bodyParser.json({ limit : config.get('upload.limit') || '50mb', })); this.app.use(bodyParser.urlencoded({ limit : config.get('upload.limit') || '50mb', extended : true, })); this.app.use(sirv(`${global.appRoot}/www`, { maxAge : config.get('cache.age') || 31536000, // 1Y })); this.app.use(session({ key : config.get('session.key') || 'eden.session.id', genid : uuid, store : new SessionStore({ eden : this.eden, }), secret : config.get('secret'), resave : true, cookie : config.get('session.cookie') || { maxAge : (24 * 60 * 60 * 1000), secure : false, httpOnly : false, }, proxy : true, saveUninitialized : true, })); }); // Set router classes const controllers = Object.keys(this.eden.get('controllers') || {}); // Run eden routes hook await this.eden.hook('eden.router.controllers', controllers, async () => { // Loop router classes for (const key of controllers) { // Load controller await this.eden.init(this.eden.get(`controllers.${key}`)); } }); // Sort routes const routes = []; // add routes controllers.forEach((controller) => { // push routes routes.push(...(this.eden.get(`controllers.${controller}.routes`).map((route) => { // return clone return { ctrl : controller, ...route, }; }))); }); // sort routes routes.sort((a, b) => { // check priority if ((a.priority || 0) < (b.priority || 0)) return 1; if ((a.priority || 0) > (b.priority || 0)) return -1; // return no change return 0; }); // Run eden routes hook await this.eden.hook('eden.router.routes', routes, async () => { // create route map for (const route of routes) { // add to router this.app[route.method.toLowerCase()](route.path, ...(await this.buildRoute(route))); } }); // Log built to debug this.eden.logger.log('debug', 'completed building eden router', { class : this.constructor.name, }); } /** * builds route from eden config * * @param route */ async buildRoute(route) { // Set path let { path, method } = route; // no path if (!path || !method) return; // set args const args = []; // Run route args hook await this.eden.hook('eden.router.route', { args, path, route, }, async () => { // Check upload const upload = this.buildUpload(route); // Push upload to args if (upload) args.push(upload); // Add actual route args.push(async (req, res, next) => { // EDEN ROUTE METHOD req.path = path; req.route = route; // Set title if (route.title) req.title(route.title); // Run acl middleware const aclCheck = await aclHelper.middleware(req, res, route); // Check acl result if (aclCheck === 0) { // Run next return next(); } if (aclCheck === 2) { // Return return null; } // Try catch try { // Get controller const ctrl = await this.eden.get(`controller.${route.ctrl}`); // Try run controller function return ctrl[route.fn](req, res, next); } catch (e) { // Set error this.eden.error(e); // Run next return next(); } }); }); // return array return args; } /** * builds upload from eden config * * @param route */ buildUpload(route) { // Add upload middleware if (route.method !== 'post') return false; // Set type const upType = (route.upload && route.upload.type) || 'array'; // Set middle let upApply = []; // Check type if (route.upload && (route.upload.fields || route.upload.name)) { if (upType === 'array') { // Push name array upApply.push(route.upload.name); } else if (upType === 'single') { // Push single name upApply.push(route.upload.name); } else if (upType === 'fields') { // Push fields upApply = route.upload.fields; } } // Check if any if (upType === 'any') upApply = []; // Create upload middleware return this.multer[upType](...upApply); } // ///////////////////////////////////////////////////////////////////////////////////////////// // // Action Methods // // ///////////////////////////////////////////////////////////////////////////////////////////// /** * initialize eden route action * * @param req * @param res * @param next */ initAction(req, res, next) { // add headers req.set = res.set = req.header = res.header = res.setHeader; // simple header set methods res.head = ''; // to add to head tag res.foot = ''; // to add post body tag res.page = {}; // page variables res.style = ''; // to add to style tag res.class = ''; // body class res.locals = {}; // middleware variables for rendering // status res.status = (status) => { res.__status = status; }; // replace send const send = res.send; res.send = (data, ...args) => { // status if (res.__status) { // send send(data, res.__status); } else { // send default send(data, ...args); } }; // Set header res.header('X-Powered-By', 'EdenJS'); // create render function res.render = async (...args) => { // set view if (typeof args[0] !== 'string') args.unshift(req.route.view); // render res.header('Content-Type', 'text/html; charset=utf-8'); res.end(await view.render({ req, res, next }, ...args)); }; res.json = async (data) => { // send json res.header('Content-Type', 'application/json; charset=utf-8'); // send res.end(JSON.stringify(data)); }; res.redirect = (url) => { redirect(res, url); }; // Set isJSON request req.isJSON = res.isJSON = (req.headers.accept || req.headers.Accept || '').includes('application/json'); // create timer req.timer = { start : new Date().getTime(), route : new Date().getTime(), }; // Check is JSON if (req.isJSON) { // Set header res.header('Content-Type', 'application/json'); } // Run next next(); } /** * API action * * @param req * @param res * @param next */ apiAction(req, res, next) { // Set headers res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); // Run next next(); } /** * Use error handler * * @param {express} app * * @private */ async errorAction(err, req, res) { // check JSON if (req.isJSON) { // send json res.header('Content-Type', 'application/json; charset=utf-8'); } else { // send HTML res.header('Content-Type', 'text/html; charset=utf-8'); } // render res.statusCode = err.code; res.end(await view.render({ req, res }, 'error', { message : err.message || '404 page not found', })); } }