UNPKG

portal

Version:
208 lines (205 loc) 8.66 kB
'use strict'; const fiber = require('carbonfibers'), fs = fiber.fs, ejs = fiber.ejs, errors = require('./errors'), dns = require('dns'), files = require('./files'), ses = require('./ses'), controllers = require('./controllers'), PathName = controllers.PathName, ServerError = errors.ServerError, ClientError = errors.ClientError; module.exports = function (server) { server.db.packages = server.db.get('packages'); server.db.packages.index('_created'); server.db.packages.index('domain'); server.db.packages.index('hostnames'); { const keys = server.redis.keys('portal,packagedata,*').wait(); if (keys.length) { /* useful */ console.log('Uncached ' + (server.redis.del(keys).wait()|0) + ' Packages.'); } } const packages = Object.create(null); Package.prototype.__proto__ = server; function Package(options) { if (!(this instanceof Package)) { return new Package(options); } Object.keys(options).forEach(function (key) { this[key] = options[key]; }.bind(this)); if (typeof this.name !== 'string') { throw new Error('Package.name not a string: ' + options); } /*useful*/ console.log('Loading Package: ' + this.name); this.paths = this.paths || {}; this.paths.views = this.paths.views || this.dirname; this.paths.less = this.paths.less || this.dirname; this.paths.javascript = this.paths.javascript || this.dirname; this.paths.controllers = this.paths.controllers || this.dirname; this.paths.files = this.paths.files || this.dirname; this.paths.emails = this.paths.emails || this.dirname; if (this.database) { if (this.database.redis) { this.redis = fiber.redis(this.database.redis.port, this.database.redis.host); } if (this.database.mongo) { this.mongo = fiber.mongo(this.database.mongo.host + '/' + this.database.mongo.database); } if (this.database.sql) { this.sql = fiber.sql(this.database.sql.connection); } } if (this.aws) { this.s3 = fiber.s3(this.aws); this.ses = ses.call(this); } this.controllers = Object.create(null); this.views = Object.create(null); if (fiber.fs.exists(this.paths.controllers).wait()) { controllers(this); } if(fs.exists(this.paths.views).wait()) { fs.readdirc(this.paths.views).wait() .forEach(function (filename) { if(/\.ejs$/.test(filename) && !/\.email\.ejs$/.test(filename)) { try { this.views[filename.substring(this.paths.views.length).replace(/\.ejs$/, '')] = ejs.compile('' + fs.readFile(filename).wait(), { compileDebug: this.DEBUG, open:'<%%', close: '%%>' }); } catch (e) { /*useful*/ console.warn('! View Failed to compile: ', filename); throw e; } //*useful*/ console.log('View: ', filename.substring(this.paths.views.length)); } }.bind(this)); } if (fiber.fs.exists(this.paths.javascript).wait()) { this.javascript = fiber.fs.readdirc(this.paths.javascript).wait() .filter(function (filename) { return /\.js$/.test(filename); }).sort(); } if (fiber.fs.exists(this.paths.less).wait()) { this.less = fiber.fs.readdirc(this.paths.less).wait() .filter(function (filename) { return /\.less$/.test(filename); }).sort(); } if (fiber.fs.exists(this.paths.files).wait()) { this.files = files.call(this, { path: this.paths.files, bucket: this.filesBucketName }); } console.log(' Loaded ' + this.name + ', ' + Object.keys(this.files || {}).length + ' Files, ' + Object.keys(this.views || {}).length + ' Views, ' + Object.keys(this.emails || {}).length + ' Emails, ' + Object.keys(this.controllers || {}).length + ' Controllers\n'); } Package.prototype.controll = function (request, response) { const url = PathName(decodeURIComponent(request.url)), controller = this.controllers[url.replace(/\/[^\/]+$/, '/')], methodName = url.replace(/^.*\//, ''), lastIndex = url.lastIndexOf('/'); if (!controller) { throw new ClientError('error:notfound:404'); } const method = controller.api[methodName]; if (!method) { throw new ClientError('error:notfound:404'); } const controllerInstance = Object.create(controller); controllerInstance.package = this; controllerInstance.session = request.session; if (this.legacySessions) { controllerInstance.session.agent = request.headers['user-agent']; controllerInstance.session.ip = request.connection.remoteAddress; controllerInstance.host = request.headers.host || ''; controllerInstance.session.permissions = (this.defaultPermissions || []).reduce(function (obj, permission) { obj[permission] = 32503701600000; return obj; }, {}); if (controllerInstance.session.user) { Object.keys(controllerInstance.session.user._permissions || {}).forEach(function(permission) { controllerInstance.session.permissions[permission] = controllerInstance.session.user._permissions[permission]; }); } } controllerInstance.originalArguments = request.body; const result = controllerInstance.models.output.call(controllerInstance, method.apply(controllerInstance, controllerInstance.models.input.call(controllerInstance, request.body))); if (!Array.isArray(result)) { throw new Error('Controller Does Not Return An Array of args: ' + this.name + controller.namespace + methodName); } response.setHeader('Content-Type', 'application/json'); if (controllerInstance.xSessionKey) { response.setHeader('X-Session', controllerInstance.xSessionKey); } response.end(JSON.stringify([('success' + url.substring(lastIndex) + url.substring(0, lastIndex)).replace(/\//g, ':')].concat(result))); }; fs.readdirc(server.dirname + '/' + server.packages).wait() .filter(function (filename) { return /package\.config\.\w+$/.test(filename); }) .forEach(function (filename) { const options = require(filename); options.server = server; options.name = options.name || filename.replace(/.+\/(.+)\/[^\/]+$/, function (_, name) { return name; }); packages[options.name] = Package(options); }.bind(server)); function lookup(request, response, hostname, destroyOnFail) { hostname = hostname || request.hostname; const key = 'portal,packagedata,' + hostname, saveKey = 'portal,packagedata,' + request.hostname, xSession = request.headers['x-session']; let packageData; request.session = {}; //fake session: // request.session = { // user: { // name: 'Janitor Jimmy Jay Jimbob Jr.' // } // }; if (xSession) { const results = this.redis.mget(key, xSession).wait(), sessionBody = results[1]; packageData = JSON.parse(results[0]); if (sessionBody) { this.redis.expire(xSession, 604800); response.setHeader('X-Session', xSession); request.session = JSON.parse(sessionBody); } } else { packageData = JSON.parse(this.redis.get(key).wait() || 'null'); } if (this.DEBUG) { packageData = null; } if (packageData && hostname === request.hostname) { this.redis.expire(key, 2592000); } else { packageData = (this.db.packages.find({ domain: { $in: ([hostname].concat(hostname.split(/\./g).reverse().reduce(function (array, sub, index, subs) { if (array.length !== subs.length - 1) { array.unshift((index === 0 ? '.' : array[0]).replace(/^\./, '.' + sub + '.').replace(/\.$/, '')); } return array; }, []))) } }).wait().sort(function (a, b) { return b.domain.length - a.domain.length; })[0]); if (packageData) { packageData.hostname = request.hostname; this.redis.setex(saveKey, 2592000, JSON.stringify(packageData)); let hostnames = (packageData.hostnames||[]).reduce(function (hostnames, hostname) { hostnames[hostname] = true; return hostnames; }, {}); if (!hostnames[request.hostname]) { hostnames[request.hostname] = true; this.db.packages.update({ _id: packageData._id }, { $set: { hostnames: Object.keys(hostnames), } }); } } } if (!packageData) { if (destroyOnFail && destroyOnFail > 5) { request.socket.destroy(); throw new ServerError('No Domain Package Data Found.'); } else { let cnamehost; try { cnamehost = (fiber.wait(dns.resolve.bind(dns), hostname, 'CNAME')||[])[0]; } catch (error) { request.socket.destroy(); throw new ServerError('Failed to resolve CNAME for package.'); } return lookup.call(this, request, response, cnamehost, (destroyOnFail|0) + 1); } } packageData.__proto__ = packages[packageData.package]; return packageData; }; return lookup; }