UNPKG

glad

Version:

A robust Node Js API framework.

360 lines (328 loc) 11.7 kB
let { log, chalk } = require('../namespace/console'); let { error,warn } = chalk; let Server = require('../classes/server'); let Router = require('../classes/router'); let Initializer = require('./initialize'); let redis = require("redis"); let session = require('express-session'); let sessionStore = require('express-sessions'); let { extend } = require('../namespace/object'); let Project = require('../classes/project'); let { join } = require('path'); let RequestEnd = require('./after-request'); let Cache = require('../namespace/cache'); let exposeModelsGlobally = require('./expose-models-globally'); let RequestIdentifier = require('./request-identifier'); const _debug = require('debug'); const debug = Symbol('debug'); const debugNamespace = Symbol('debugNamespace'); Promise.promisifyAll(redis.RedisClient.prototype); Promise.promisifyAll(redis.Multi.prototype); module.exports = class Boot { constructor (cwd = false) { this[debugNamespace] = _debug('glad'); this[debug]('Boot:constructor'); this.project = new Project(cwd); this.project.initialize(); } [debug] (msg) { this[debugNamespace]('Boot: %s', msg); } exec () { Promise.each([ this.createServer, this.connectToRedis, this.gladCache, this.session, this.attachSessionToWebsockets, this.createRouter, this.init, this.connectToMongo, this.id, this.disablePoweredBy, this.setViewEngine, this.getMiddleware, this.getHooks, this.after, this.middleware, this.initializeWaterline, this.drawSocketIo, this.draw, this.exposeModels ], exec => exec.call(this)) .then(() => this.server.listen()) .catch(err => log(err)); } createServer () { this[debug]('createServer'); return new Promise( resolve => { this.server = new Server(this.project); global.Glad.server = this.server; resolve(); }); } connectToRedis () { this[debug]('connectToRedis'); return new Promise( resolve => { let { config } = this.project; this.server.redis = redis.createClient(config.redis); resolve(); }); } gladCache () { this[debug]('gladCache'); Glad.cache = new Cache(this.server, this.project); if (Glad.cache.disabled) { chalk.info("Cache: DISABLED"); } else { chalk.ok("Cache: ENABLED"); } return new Promise.resolve(); } init () { this[debug]('init'); return new Initializer(this.project, this.server).initialize(); } connectToMongo () { this[debug]('connectToMongo'); let { config } = this.project; if (config.orm === 'mongoose' && config.mongodb) { return new Promise( resolve => { let mongoose = require('mongoose'); let url = 'mongodb://' + config.mongodb.host + ':' + config.mongodb.port + '/' + config.mongodb.database; if (!mongoose.connection.db) { mongoose.connect(config.mongodb.url || url, { useNewUrlParser: true }); } resolve(); }); } } id () { this[debug]('id'); return new Promise( resolve => { let identifier = new RequestIdentifier(this.project); this.server.app.use(identifier.id.bind(identifier)); resolve(); }); } disablePoweredBy () { this[debug]('disablePoweredBy'); this.server.app.disable('x-powered-by'); return Promise.resolve(); } getMiddleware () { this[debug]('getMiddleware'); return new Promise( resolve => { this.project.middleware = require(join(this.project.projectPath, 'middleware')); resolve(); }); } getHooks () { this[debug]('getHooks'); this.project.hooks = require(join(this.project.projectPath, 'hooks')); return Promise.resolve(); } // THIS NEEDS TO BE MOVED IN THE ROUTER CHAIN vvvvvvvvvvv after () { this[debug]('after'); return new Promise( resolve => { let after = new RequestEnd(this.project); this.server.app.use(after.end.bind(after)); resolve(); }); } /** * If there is a session key in the config then implement sessions based on the config. * Otherwise it is assumed that a roll your own implementation or no sessions will be used. */ session () { this[debug]('session'); return new Promise( resolve => { let { config } = this.project; let userSessionModule; try { userSessionModule = require(join(this.project.projectPath, '/session')); } catch (err) { userSessionModule = false; } if (config.session) { let options = extend({ instance: this.server.redis, storage: 'redis' }, config.session); this._sessions = session({ secret: config.cookie.secret, resave: config.cookie.resave || false, store: new sessionStore(options), saveUninitialized: true, cookie : config.cookie, name : config.cookie.name }); this.server.app.use( (req, res, next) => { let debugMiddleware = _debug('glad'); debugMiddleware('Session:middleware'); if (userSessionModule) { debugMiddleware('Session:middleware: using session.js'); userSessionModule(req, res, next).then(result => { if (result) { debugMiddleware('Session:middleware: session.js > Use Glad Session'); this._sessions(req, res, next); } else { debugMiddleware('middleware: session.js > Not using Session'); } }); } else { debugMiddleware('middleware: Using Glad Session'); this._sessions(req, res, next); } }); } resolve(); }); } /** * If there is a session key in the config then attach the session to websocket events. * Otherwise look for setupWebsockets in the config, if it exists, pass the setup over to there and waith for that promise to resolve. */ attachSessionToWebsockets () { this[debug]('attachSessionToWebsockets'); return new Promise( resolve => { let { config } = this.project; if (config.session) { this.server.websockets.use((socket, next) => { this._sessions(socket.request, socket.request.res, next); }); this.server.websockets.on('connection', conn => { if (conn.request.session) { conn.request.session.socketId = conn.id; } else { error("ERROR: You are trying to attach a websocket id to a session, but there is NO session"); } }); resolve(); } else if (config.setupWebsockets && typeof config.setupWebsockets === 'function' ) { config.setupWebsockets(this.server.websockets).then(resolve); } else { resolve(); } }); } createRouter () { this[debug]('createRouter'); return new Promise( (resolve, reject) => { this.router = new Router(this.project, this.server); this.router.buildRoutes().then(resolve).catch(reject); }); } exposeModels () { this[debug]('exposeModels'); let { config } = this.project; return new Promise( (resolve, reject) => { if (config.exposeModelsGlobally && config.orm === 'mongoose') { exposeModelsGlobally(this.router).then(resolve).catch(reject); } else if (config.exposeModelsGlobally) { resolve(); warn('You can only automatically expose model globally when specifying mongoose as your ORM'); warn('If you are using mongoose, please set `orm : "mongoose"` in your config.js file.'); warn('If you are not using mongoose, please set "exposeModelsGlobally : false" in your config.js file to supress this warning'); } else { resolve(); } }); } middleware () { this[debug]('middleware'); return new Promise( (resolve, reject) => { Promise.each(this.project.middleware, exec => exec(this.server)) .then(resolve) .catch(reject); }); } /** * If Waterline is being used, then the developer needs to initialize it. * This is wrapped in a try/catch because we don't expect that this method will be there (especially if the project is not using glad-cli). */ initializeWaterline () { this[debug]('initializeWaterline'); return new Promise( (resolve, reject) => { if (this.project.orm === 'waterline') { this[debug]('initializeWaterline: Using Waterline'); try { let hooks = require(`${this.project.projectPath}/hooks`); hooks.initializeWaterline(this.router).then(resolve).catch(reject); } catch (err) { error(err); reject("It seems like you have initialized this project to use waterline, but you are missing the onAfterModels hook in your hooks file."); } } else { this[debug]('initializeWaterline: Not using Waterline'); // no need to initialize for waterline. resolve(); } }); } drawSocketIo () { this[debug]('drawSocketIo'); return new Promise( resolve => { try { let socketRouter = require(`${this.project.projectPath}/sockets/router`); let socketPolicies = require(`${this.project.projectPath}/sockets/policies`); this.server.websockets.on('connection', conn => { let connectionDebug = _debug('glad'); connectionDebug('WebSocket:connection'); socketRouter.forEach(route => { connectionDebug('WebSocket:connection: Drawing Route for %s', route.event); conn.on(route.event, data => { let routeDebug = _debug('glad'); routeDebug('WebSocket:Route: Received %s', route.event); if (route.policy && socketPolicies[route.policy]) { routeDebug('WebSocket:Route: Applying policy for %s', route.event); socketPolicies[route.policy](conn, () => { routeDebug('WebSocket:Route: Policy accepted for %s', route.event); route.action.call(this.server.websockets, data, conn); }); } else if (route.policy) { error(`WS: The route for the ${route.event} event requires a policy, but the socket policy does not exist. Please ensure that it is defined in ${this.project.projectPath}/sockets/policies.js`); } else { routeDebug('WebSocket:Route: No policy for %s > Calling action', route.event); route.action.call(this.server.websockets, data, conn); } }); }); }); resolve(); } catch (err) { this[debug]('Not using websockets'); log("Not using websockets"); resolve(); } }); } draw () { this[debug]('draw'); return new Promise( resolve => { let key; for (key in this.router.routes) { if (this.router.routes.hasOwnProperty(key)) { let target = this.router.routes[key]; let method; for (method in target) { if (target.hasOwnProperty(method)) { target[method].forEach(cfg => { cfg.controller = this.router.controllers[key]; this.router.route(method, cfg); }); } } } } resolve(); }); } setViewEngine () { this[debug]('setViewEngine'); let { config } = this.project; this.server.app.set('view engine', config.defaultViewEngine || 'pug'); return Promise.resolve(); } }