module.exports = function(O){ ////////////////////////////////////////////////////////////////////////////// //// SETUP //// ////////////////////////////////////////////////////////////////////////////// process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; var Opts = O || new Optionall({ '__dirname': Path.resolve(module.filename + '/../..') , 'file_priority': [ 'package.json' , 'environment.json' , 'credentials.json' ] }); var S = new (Events.EventEmitter.bind({}))(); S.settings = Belt.extend({ 'log_level': 'debug' }, Opts); var log = Opts.log || new Winston.Logger(); if (!Opts.log) log.add(Winston.transports.Console, {'level': S.settings.log_level, 'colorize': true, 'timestamp': false}); S.log = log; //error handler S.on('error', function(err){ // Request({ // 'url': S.settings['2post'] // , 'method': 'post' // , 'form': { // 'event': 'server error' // , 'message': Belt.get(err, 'message') // } // }, Belt.noop); log.error(err); }); //////////////////////////////////////////////////////////////////////////////// ////SERVICES / DATA //// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// ////SETUP //// //////////////////////////////////////////////////////////////////////////////// /* setup redis */ S['setupRedis'] = function(options, callback){ var a = Belt.argulint(arguments) , self = this; a.o = _.defaults(a.o, { }); var ocb = _.once(a.cb); self['redis'] = Redis.createClient(a.o); self.redis.on('error', function(err){ return self.emit('error', err); }); self.redis.on('ready', function(){ self.log.info('Connected to Redis...'); return ocb(); }); }; /* setup session store */ S['setupSessions'] = function(options, callback){ var a = Belt.argulint(arguments) , self = this; a.o = _.defaults(a.o, { }); self['sessionsStore'] = new RedisSessions(a.o); self['sessions'] = self.sessionsStore; //alias a.cb(); return self; }; /* setup express server for incoming requests */ S['setupServer'] = function(options, callback){ var a = Belt.argulint(arguments) , self = this; a.o = _.defaults(a.o, { //session_secret 'cookie_secret': Crypto.randomBytes(512).toString('utf-8') , 'body_parser': { 'limit': '500mb' , 'parameterLimit': 10000 , 'extended': true } , 'sessions': { 'store': self.sessionsStore , 'secret': a.o.session_secret || Crypto.randomBytes(512).toString('utf-8') , 'cookie': {'maxAge': 60000000} , 'key': a.o.session_key , 'saveUninitialized': true , 'resave': true } , 'views': Path.join(self.settings.__dirname, '/lib/views') }); self['express'] = Express(); self.express.set('env', self.settings.environment); self.express.set('port', a.o.port); self.express.set('view engine', 'ejs'); self.express.set('views', a.o.views); /* middleware */ self['logger'] = self.settings.environment === 'production' ? Morgan('common', {'skip': function(req, res) { return res.statusCode < 400; }}) : Morgan('dev'); self.express.use(self.logger); self['bodyParserJSON'] = BodyParser.json(a.o.body_parser); self.express.use(self.bodyParserJSON); self['bodyParserURLEncoded'] = BodyParser.urlencoded(a.o.body_parser); self.express.use(self.bodyParserURLEncoded); self['cookieParser'] = CookieParser(a.o.cookie_secret); self.express.use(self.cookieParser); self['sessions'] = Sessions(a.o.sessions); self.express.use(self.sessions); self['errorHandler'] = ErrorHandler(); self.express.use(self.errorHandler); //self.express.use(ServeFavicon(Path.join(self.settings.__dirname, a.o.favicon))); self.express.use(Timeout('100m')); self.express.disable('x-powered-by'); self.express.set('trust proxy', true); self['httpServer'] = HTTP.createServer(self.express).listen(a.o.port, function(){ log.info('[HTTP] Express server started'); log.info(Belt.stringify({ 'environment': self.settings.environment.toUpperCase() , 'port': self.express.get('port') })); return a.cb(); }); return self; }; S['setupHelpers'] = function(options, callback){ var a = Belt.argulint(arguments) , self = this; a.o = _.defaults(a.o, { }); var gb = {}; Async.waterfall([ function(cb){ return CP.exec('mkdir -p "' + Path.join(self.settings.__dirname, '/lib/helpers') + '"', Belt.cw(cb)); } , function(cb){ self['helpers'] = _.chain(FS.readdirSync(Path.join(self.settings.__dirname, '/lib/helpers'))) .filter(function(f){ return f.match(/\.(js|json)$/i); }) .value(); if (!_.any(self.helpers)) return cb(); self.helpers = _.object( _.map(self.helpers, function(g){ return g.replace(/\.(js|json)$/i, ''); }) , _.map(self.helpers, function(g){ return require(Path.join(self.settings.__dirname, '/lib/helpers/', g)); }) ); return Async.eachSeries(_.keys(self.helpers), function(k, cb2){ log.info('Creating helper [%s]...', k); self.helpers[k] = new self.helpers[k](_.extend( {}, self.settings, {'log': S.log, 'name': k, 'instance': self}) ).once('ready', Belt.cw(cb2)); self.helpers[k].on('error', function(err){ return self.emit('error', err); }); }, Belt.cw(cb, 0)); } ], a.cb); return self; } S['setupControllers'] = function(options, callback){ var a = Belt.argulint(arguments) , self = this; a.o = _.defaults(a.o, { }); var gb = {}; Async.waterfall([ function(cb){ return CP.exec('mkdir -p "' + Path.join(self.settings.__dirname, '/lib/controllers') + '"', Belt.cw(cb)); } , function(cb){ self['controllers'] = _.chain(FS.readdirSync(Path.join(self.settings.__dirname, '/lib/controllers'))) .filter(function(f){ return f.match(/\.(js|json)$/i); }) .value(); if (!_.any(self.controllers)) return cb(); self.controllers = _.object( _.map(self.controllers, function(g){ return g.replace(/\.(js|json)$/i, ''); }) , _.map(self.controllers, function(g){ return require(Path.join(self.settings.__dirname, '/lib/controllers/', g)); }) ); return Async.eachSeries(_.keys(self.controllers), function(k, cb2){ log.info('Creating controller [%s]...', k); self.controllers[k] = new self.controllers[k](_.extend( {}, self.settings, {'log': S.log, 'name': k, 'instance': self}) ).once('ready', Belt.cw(cb2)); self.controllers[k].on('error', function(err){ return self.emit('error', err); }); }, Belt.cw(cb, 0)); } ], a.cb); return self; } Async.waterfall([ function(cb){ return S.setupSessions(S.settings.redis, Belt.cw(cb, 0)); } , function(cb){ return S.setupRedis(_.omit(S.settings.redis, ['prefix']), Belt.cw(cb, 0)); } , function(cb){ return S.setupServer(S.settings.express, Belt.cw(cb, 0)); } , function(cb){ S['status'] = {}; S.express.all('/', function(req, res){ return res.status(200).json(S.status); }); return cb(); } , function(cb){ return S.setupHelpers(S.settings, Belt.cw(cb, 0)); } , function(cb){ return S.setupControllers(S.settings, Belt.cw(cb, 0)); } ], function(err){ if (err) return S.emit(err); log.info('/////READY/////'); return S.emit('ready'); }); return S; };
module.exports = function(O){ var Opts = O || new Optionall({ '__dirname': Path.resolve(module.filename + '/../..') , 'file_priority': ['package.json', 'environment.json', 'config.json'] }); var S = new (Events.EventEmitter.bind({}))(); S.settings = Belt.extend({ 'log_level': 'debug' // server , 'rate_limit': 200 }, Opts); S.instance = S.settings.instance; var log = S.instance.log || new Winston.Logger(); if (!S.instance.log) log.add(Winston.transports.Console, {'level': S.settings.log_level, 'colorize': true, 'timestamp': false}); S['dbs'] = {}; //////////////////////////////////////////////////////////////////////////////// ////METHODS //// //////////////////////////////////////////////////////////////////////////////// S['dbConnect'] = function(options, callback){ var a = Belt.argulint(arguments) , self = this; a.o = _.defaults(a.o, { //db 'host': self.settings.mongodb.host , 'port': self.settings.mongodb.port }); var gb ={}; return Async.waterfall([ function(cb){ gb['db'] = self.dbs[a.o.db]; if (gb.db) return cb(); return Mongodb.MongoClient.connect('mongodb://' + a.o.host + ':' + a.o.port + '/' + a.o.db , Belt.cs(cb, self.dbs, a.o.db + '.conn', 1, 0)); } , function(cb){ if (gb.db) return cb(); gb['db'] = self.dbs[a.o.db]; gb.db['collections'] = {}; gb.db.conn.on('error', function(err){ Belt.get(gb, 'db.conn.close()'); Belt.delete(self.dbs, a.o.db); }); gb.db.conn.on('close', function(err){ Belt.get(gb, 'db.conn.close()'); Belt.delete(self.dbs, a.o.db); }); return cb(); } ], function(err){ if (!err && !gb.db) err = new Error('db not connected'); if (err) Belt.delete(self.dbs, a.o.db); return a.cb(err, gb.db); }); }; S['getCollection'] = function(options, callback){ var a = Belt.argulint(arguments) , self = this; a.o = _.defaults(a.o, { //db //collection }); var gb ={}; return Async.waterfall([ function(cb){ gb['conn'] = Belt.get(self.dbs, a.o.db + '.conn'); if (gb.conn) return cb(); return self.dbConnect(a.o, Belt.cs(cb, gb, 'conn', 1, 'conn', 0)); } , function(cb){ gb['collection'] = Belt.get(self.dbs[a.o.db], 'collections.' + a.o.collection); if (gb.collection) return cb(); return gb.conn.collection(a.o.collection, Belt.cs(cb, gb, 'collection', 1, 0)); } , function(cb){ if (!gb.collection) return cb(new Error('collection not found')); Belt.set(self.dbs[a.o.db], 'collections.' + a.o.collection, gb.collection); return cb(); } ], function(err){ if (err) Belt.delete(self.dbs, a.o.db + '.collections.' + a.o.collection); return a.cb(err, gb.collection); }); }; S['wrapper'] = function(options, callback){ var a = Belt.argulint(arguments) , self = this; a.o = _.defaults(a.o, { //db //collection //method //args }); var gb ={}; return Async.waterfall([ function(cb){ return self.getCollection(a.o, Belt.cs(cb, gb, 'collection', 1, 0)); } , function(cb){ if (!gb.collection[a.o.method]) return cb(new Error('method not found')); var args; if (a.o.method === 'count'){ args = Belt.objCast(_.pick(a.o.args, [ 'skip' , 'limit' , 'min' , 'max' ]), { 'skip': 'number' , 'limit': 'number' , 'min': 'number' , 'max': 'number' }, { 'skip_null': true }); return gb.collection.count( a.o.args.query || a.o.args.filter , Belt.cs(cb, gb, 'data', 1, 0)); } if (a.o.method === 'find'){ args = Belt.objCast(_.pick(a.o.args, [ 'skip' , 'limit' , 'min' , 'max' ]), { 'skip': 'number' , 'limit': 'number' , 'min': 'number' , 'max': 'number' }, { 'skip_null': true }); gb['cursor'] = gb.collection.find(a.o.args.query); _.each(args, function(v, k){ gb.cursor = gb.cursor[k](v); }); return gb.cursor.toArray(Belt.cs(cb, gb, 'data', 1, 0)); } if (a.o.method === 'findOne'){ args = Belt.objCast(_.omit(a.o.args, [ 'query' , 'filter' ]), { 'skip': 'number' , 'limit': 'number' , 'min': 'number' , 'max': 'number' }, { 'skip_null': true }); return gb.collection.findOne( a.o.args.query || a.o.args.filter , args , Belt.cs(cb, gb, 'data', 1, 0)); } if (a.o.method === 'findOneAndUpdate'){ args = Belt.objCast(_.omit(a.o.args, [ 'query' , 'filter' , 'update' ]), { 'skip': 'number' , 'limit': 'number' , 'min': 'number' , 'max': 'number' , 'upsert': 'boolean' }, { 'skip_null': true }); return gb.collection.findOneAndUpdate( a.o.args.query || a.o.args.filter , a.o.args.update , args , Belt.cs(cb, gb, 'data', 1, 0)); } if (a.o.method === 'findOneAndDelete'){ args = Belt.objCast(_.omit(a.o.args, [ 'query' , 'filter' ]), { 'skip': 'number' , 'limit': 'number' , 'min': 'number' , 'max': 'number' }, { 'skip_null': true }); return gb.collection.findOneAndDelete( a.o.args.query || a.o.args.filter , args , Belt.cs(cb, gb, 'data', 1, 0)); } if (a.o.method === 'findOneAndReplace'){ args = Belt.objCast(_.omit(a.o.args, [ 'query' , 'filter' , 'replacement' ]), { 'skip': 'number' , 'limit': 'number' , 'min': 'number' , 'max': 'number' }, { 'skip_null': true }); return gb.collection.findOneAndReplace( a.o.args.query || a.o.args.filter , a.o.args.replacement , args , Belt.cs(cb, gb, 'data', 1, 0)); } if (a.o.method === 'insertOne'){ args = Belt.objCast(_.omit(a.o.args, [ 'doc' ]), { }, { 'skip_null': true }); return gb.collection.insertOne( a.o.args.doc , args , Belt.cs(cb, gb, 'data', 1, 0)); } if (a.o.method === 'insertMany'){ args = Belt.objCast(_.omit(a.o.args, [ 'docs' ]), { }, { 'skip_null': true }); return gb.collection.insertMany( a.o.args.docs , args , Belt.cs(cb, gb, 'data', 1, 0)); } if (a.o.method === 'deleteOne'){ args = Belt.objCast(_.omit(a.o.args, [ 'query' , 'filter' ]), { }, { 'skip_null': true }); return gb.collection.deleteOne( a.o.args.query || a.o.args.filter , args , Belt.cs(cb, gb, 'data', 1, 0)); } if (a.o.method === 'deleteMany'){ args = Belt.objCast(_.omit(a.o.args, [ 'query' , 'filter' ]), { }, { 'skip_null': true }); return gb.collection.deleteMany( a.o.args.query || a.o.args.filter , args , Belt.cs(cb, gb, 'data', 1, 0)); } if (a.o.method === 'updateOne'){ args = Belt.objCast(_.omit(a.o.args, [ 'query' , 'filter' , 'update' ]), { 'skip': 'number' , 'limit': 'number' , 'min': 'number' , 'max': 'number' , 'upsert': 'boolean' }, { 'skip_null': true }); return gb.collection.updateOne( a.o.args.query || a.o.args.filter , a.o.args.update , args , Belt.cs(cb, gb, 'data', 1, 0)); } if (a.o.method === 'updateMany'){ args = Belt.objCast(_.omit(a.o.args, [ 'query' , 'filter' , 'update' ]), { 'skip': 'number' , 'limit': 'number' , 'min': 'number' , 'max': 'number' , 'upsert': 'boolean' }, { 'skip_null': true }); return gb.collection.updateMany( a.o.args.query || a.o.args.filter , a.o.args.update , args , Belt.cs(cb, gb, 'data', 1, 0)); } } ], function(err){ return a.cb(err, gb.data); }); }; S['rateLimitQueue'] = Async.priorityQueue(function(task, next){ return task(Belt.cw(next)); }, S.settings.rate_limit); //////////////////////////////////////////////////////////////////////////////// ////ROUTES //// //////////////////////////////////////////////////////////////////////////////// S.instance.express.all('/db/:db/collection/:collection/method/:method.json', function (req, res){ S.rateLimitQueue.push(function(next){ return S.wrapper({ 'db': req.params.db , 'collection': req.params.collection , 'method': req.params.method , 'args': _.extend({}, req.query || {}, req.body || {}) }, function(err, data){ res.status(200).json({ 'error': Belt.get(err, 'message') , 'data': data }); next(); }); }, new Date().valueOf()); }); //////////////////////////////////////////////////////////////////////////////// ////SETUP //// //////////////////////////////////////////////////////////////////////////////// setTimeout(function(){ return S.emit('ready'); }, 0); return S; };