@edenjs/cli
Version:
Web Application Framework built on Express.js, Redis and RiotJS
494 lines (413 loc) • 11.3 kB
JavaScript
// Require dependencies
const http = require('http');
const uuid = require('uuid');
const multer = require('multer');
const express = require('express');
const session = require('express-session');
const bodyParser = require('body-parser');
const responseTime = require('response-time');
const cookieParser = require('cookie-parser');
// Require class dependencies
const SessionStore = require('@edenjs/session-store');
// Require local dependencies
const eden = require('eden');
const config = require('config');
// Require helpers
const aclHelper = helper('user/acl');
// Require cache
const routes = cache('routes');
const classes = cache('classes');
// Require Eden dependencies
const view = require('./view');
/**
* Create Router class
*/
class EdenRouter {
/**
* Construct Router class
*/
constructor() {
// Set variables
this.app = null;
this.server = null;
this.multer = null;
this.router = null;
// Bind methods
this.build = this.build.bind(this);
// Bind private methods
this._api = this._api.bind(this);
this._view = this._view.bind(this);
this._error = this._error.bind(this);
this._default = this._default.bind(this);
// Bind super private methods
this.__error = this.__error.bind(this);
this.__rotue = this.__route.bind(this);
this.__upload = this.__upload.bind(this);
// Run build
this.building = this.build();
}
/**
* Build Router
*
* @async
*/
async build() {
// Log building to debug
eden.logger.log('debug', 'building eden router', {
class : 'EdenRouter',
});
// Set express
this.app = express();
// create express app
await eden.hook('eden.router.create', this.app);
// Set port
this.app.set('port', eden.port);
// Create server
this.server = http.createServer(this.app);
// create express app
await eden.hook('eden.server.create', this.server);
// Listen to port
this.server.listen(eden.port, eden.host);
// Set server event handlers
this.server.on('error', this.__error);
// Set multer
this.multer = multer(config.get('upload') || {
dest : '/tmp',
});
// 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);
};
});
// Set express build methods
const methods = ['_default', '_api', '_view', '_router', '_error'];
// Loop methods
for (let i = 0; i < methods.length; i += 1) {
// Run express build method
await this[methods[i]](this.app);
}
// create express app
await eden.hook('eden.server.complete', this.server);
// create express app
await eden.hook('eden.router.complete', this.app);
// Log built to debug
eden.logger.log('debug', 'completed building eden router', {
class : 'EdenRouter',
});
}
/**
* Adds defaults to given express app
*
* @param {express} app
*
* @private
*
* @async
*/
async _default(app) {
// Add request middleware
app.use((req, res, next) => {
// Set header
res.header('X-Powered-By', 'EdenFrame');
// Set isJSON request
const isJSON = (req.headers.accept || req.headers.Accept || '').includes('application/json');
req.isJSON = isJSON;
res.isJSON = isJSON;
res.locals.isJSON = isJSON;
// Set url
res.locals.url = req.originalUrl;
// Check is JSON
if (res.isJSON) {
// Set header
res.header('Content-Type', 'application/json');
} else {
// Set header
res.header('Link', [
`<${config.get('cdn.url') || '/'}public/css/app.min.css${config.get('version') ? `?v=${config.get('version')}` : ''}>; rel=preload; as=style`,
`<${config.get('cdn.url') || '/'}public/js/app.min.js${config.get('version') ? `?v=${config.get('version')}` : ''}>; rel=preload; as=script`,
].join(','));
}
// Set route timer start
res.locals.timer = {
start : new Date().getTime(),
};
// Set locals response and page
res.locals.res = res;
res.locals.page = {};
// Run next
next();
});
// Run eden app hook
await eden.hook('eden.app', app, () => {
// initialize
SessionStore.initialize(session);
// Add express middleware
app.use(responseTime());
app.use(cookieParser(config.get('secret')));
app.use(bodyParser.json({
limit : config.get('upload.limit') || '50mb',
}));
app.use(bodyParser.urlencoded({
limit : config.get('upload.limit') || '50mb',
extended : true,
}));
app.use(express.static(`${global.appRoot}/data/www`));
app.use(session({
key : config.get('session.key') || 'eden.session.id',
genid : uuid,
store : new SessionStore({
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,
}));
});
}
/**
* Api functionality
*
* @param {express} app
*
* @private
*/
_api(app) {
// Set on all api routes
app.all('/api/*', (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();
});
}
/**
* View engine
*
* @param {express} app
*
* @private
*/
_view(app) {
// Set view engine
app.set('views', `${global.appRoot}/data/cache/views`);
app.set('view engine', 'tag');
// Do view engine
app.engine('tag', view.render);
}
/**
* Build router
*
* @param {express} app
*
* @private
*
* @async
*/
async _router(app) {
// Create express router
this.router = new express.Router();
// Set router classes
const controllerClasses = Object.keys(classes).map(key => classes[key]).filter((c) => {
// check thread
return !c.cluster || c.cluster.includes(eden.label);
}).sort((a, b) => (b.priority || 0) - (a.priority || 0));
// Loop router classes
for (const controller of controllerClasses) {
// Load controller
await eden.controller(controller.file);
}
// Run eden routes hook
await eden.hook('eden.routes', routes);
// Sort routes
routes.sort((a, b) => (b.priority || 0) - (a.priority || 0));
// Loop routes
for (let i = 0; i < routes.length; i += 1) {
// Run route
await this.__route(routes[i]);
}
// Use router on route
app.use('/', this.router);
}
/**
* Use error handler
*
* @param {express} app
*
* @private
*/
_error(app) {
// Use 404 handler
app.use((req, res) => {
// Set status 404
res.status(404);
// Render 404 page
res.render('error', {
message : '404 page not found',
});
});
}
/**
* Creates route
*
* @param {object} route
*
* @private
*
* @async
*/
async __route(route) {
// no path
if (!route.path || !route.method) return;
// Set path
let { path } = route;
// Add to path
path = path.length > 1 ? path.replace(/\/$/, '') : path;
// Create route arguements
const args = [path];
// Run route args hook
await eden.hook('eden.route.args', {
args,
path,
route,
});
// Push timer arg
args.push((req, res, next) => {
// Set route timer
res.locals.timer.route = new Date().getTime();
// Run next
next();
});
// Check upload
const upload = this.__upload(route);
// Push upload to args
if (upload) args.push(upload);
// Add actual route
args.push(async (req, res, next) => {
// Set route
res.locals.path = path;
// Set route
res.locals.route = route;
// Set title
if (route.title) req.title(route.title);
// Run acl middleware
const aclCheck = await aclHelper.middleware(req, res);
// Alter render function
const { render } = res;
// Create new render function
res.render = (...renderArgs) => {
// Load view
if (typeof renderArgs[0] !== 'string' && route.view) {
// UnShift Array
renderArgs.unshift(route.view);
}
// Apply to render
render.apply(res, renderArgs);
};
// Check acl result
if (aclCheck === 0) {
// Run next
return next();
} if (aclCheck === 2) {
// Return
return null;
}
// Try catch
try {
// Get controller
const controller = await eden.controller(route.file);
// Try run controller function
return controller[route.fn](req, res, next);
} catch (e) {
// Set error
eden.error(e);
// Run next
return next();
}
});
// Call router function
this.router[route.method](...args);
}
/**
* Upload method check
*
* @param {object} route
*
* @returns {*}
*
* @private
*/
__upload(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);
}
/**
* On error function
*
* @param {Error} error
*/
__error(error) {
// Check syscall
if (error.syscall !== 'listen') {
// Throw error
eden.error(error);
}
// Set bind
const bind = typeof this.app.get('port') === 'string' ? `Pipe ${this.app.get('port')}` : `Port ${this.app.get('port')}`;
// Check error code
if (error.code === 'EACCES') {
// Log access error to error
eden.logger.log('error', `${bind} requires elevated privileges`, {
class : 'EdenRouter',
});
// Exit process
process.exit(1);
} else if (error.code === 'EADDRINUSE') {
// Log address in use to error
eden.logger.log('error', `${bind} is already in use`, {
class : 'EdenRouter',
});
// Exit
process.exit(1);
} else {
// Throw error with eden
eden.error(error);
}
}
}
/**
* Export EdenRouter class
*
* @type {EdenRouter}
*/
module.exports = EdenRouter;