UNPKG

sleek.js

Version:

Sleek.js is an MVC wrapper Framework implemented from Node.JS, built-in with base dependency on handlebars.js, express.js. Sleek.js architecture follows common format of MVC which makes it easy to handle and build better web apps with pluggable modules &

764 lines (711 loc) 30.4 kB
/* * System library * * @package Sleek.js * @version 1.0 * * The MIT License (MIT) * Copyright Cubet Techno Labs, Cochin (c) 2013 <info@cubettech.com> * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * @author Robin <robin@cubettech.com> * @Date 23-10-2013 */ var path = require('path'); var hbs = require('handlebars'); var fs = require('fs'); var async = require('async'); global.appPath = path.dirname(require.main.filename); global.HELPER ={}; //get defines __def = require(path.join(appPath,'application/config/defines.js')); require(path.join(appPath,'system/lib/handhelpers.js')); var _fns = require(path.join(appPath,'system/lib/functions.js')); //get a copy of request for plugins app.use(function(req,res,next){ res.sleekReq = req; next(); }); //load custom config libraries if(sleekConfig.configLibs){ for(var i in sleekConfig.configLibs){ try { require(path.join(appPath,'lib', sleekConfig.configLibs[i]+'.js')); } catch(err){ console.log(err); } } } //set loggings as in config if(sleekConfig.logToFile === true) { var access = fs.createWriteStream(path.join(appPath, sleekConfig.accesslog), { flags:'a' }); process.stdout.write = (function(write) { return function(string, encoding, fd) { access.write(string); }; })(process.stdout.write); var errorLog = fs.createWriteStream(path.join(appPath, sleekConfig.errorlog), { flags:'a' }); process.stderr.write = (function(write) { return function(string, encoding, fd) { errorLog.write(string); }; })(process.stdout.write); } global.system = { /** * get model object * pass model name. example; for userModel pass user * * @param model model name, ignoring trailing 'Model' * @return model object * @author Robin <robin@cubettech.com> * @Date 23-10-2013 */ getModel: function(model){ try { return require(path.join(appPath, 'application/models',model+'Model.js')); } catch (err) { this.log(err); } }, /** * get a library object * * @param lib library name * @return library object * @author Robin <robin@cubettech.com> * @Date 23-10-2013 */ getLibrary: function(lib){ try { return require(path.join(appPath ,'lib',lib+'.js')); } catch (err) { this.log(err); } }, /** * load a helpers object * * @param helper helper name * @return helper object * @author Robin <robin@cubettech.com> * @Date 23-10-2013 */ loadHelper: function(helper){ try { if(helper instanceof Object) { for(var h in helper) { _fns.extendJSON(global.HELPER, require(path.join(appPath ,'application/helpers',helper[h]+'.js'))); } } else { _fns.extendJSON(global.HELPER, require(path.join(appPath ,'application/helpers',helper+'.js'))); } } catch (err) { this.log(err); } }, /** * get a controller object * * @param controller controller name * @return controller object * @author Robin <robin@cubettech.com> * @Date 23-10-2013 */ getController: function(controller){ try { return require(path.join(appPath,'application/controllers',controller+'.js')); } catch (err) { this.log(err); } }, /** * Set a partial file to load in view * can load partial from view, using {{> partialname data}} * * @param partial file path from view folder * @param name Set a name for partial to load in view * * @author Robin <robin@cubettech.com> * @Date 23-10-2013 */ setPartial: function(partial, name){ try { if(!name){ name = partial; } var realPath = path.join(appPath,'application/views',sleekConfig.theme, partial+'.html'); if (! fs.existsSync(realPath)) { realPath = path.join(appPath,'application/views/default',partial+'.html'); } var template = fs.readFileSync(realPath, 'utf8'); var plugsDir = fs.readdirSync(path.join(appPath,'modules')); async.eachSeries(plugsDir, function (plug, _pcbk) { var stats = fs.statSync(path.join(appPath,'modules',plug)); if(stats.isDirectory() && plug.charAt(0) !== '.'){ var Ovr = require(path.join(appPath,'modules',plug, 'override.js')); async.eachSeries(Ovr.data, function (ovdta, _ovdbk) { if(ovdta.view === partial){ var _m = ovdta.mode; if (_m === 'prepend' || _m === 'append' || _m === 'replace') { if(ovdta.controller && ovdta.action){ var M = system.getPluginController(ovdta.controller,plug); var fn = M[ovdta.action]; fn(function(dt){ if(_m === 'prepend') { template = dt + template; } else if (_m === 'append') { template += dt; } else { template = dt; } _ovdbk(); }); } else { realPath = path.join(appPath,'modules',plug,'views',partial+'.html'); var dt = fs.readFileSync(realPath, 'utf8'); if(_m === 'prepend') { template = dt + template; } else if (_m === 'append') { template += dt; } else { template = dt; } _ovdbk(); } } else { _ovdbk(); } } else { _ovdbk(); } }, function(){ _pcbk(); }); } else { _pcbk(); } }, function(){ hbs.registerPartial(name, template); }); } catch (err) { this.log(err); } }, /** * Set a plugin partial file to load in view * can load partial from view, using {{> partialname data}} * * @param partial file path from view folder * @param name Set a name for partial to load in view * @param plugin (Optional) Plugin name * * @author Robin <robin@cubettech.com> * @Date 28-01-2014 */ setPluginPartial: function(partial, name , plugin){ try { //if calling from plugins controller if(!plugin){ var pt = _fns._getCallerFile(); plugin = pt.split(path.sep); if(plugin[plugin.indexOf('controllers')-2] === 'modules') { plugin = plugin[plugin.indexOf('controllers')-1]; } else { this.log('Please specify plugin name'); } } if(!name){ name = partial; } var realPath = path.join(appPath,'modules',plugin,'views',partial+'.html'); var template = fs.readFileSync(realPath, 'utf8'); hbs.registerPartial(name, template); } catch (err) { this.log(err); } }, /** * Load view * * @param res response object * @param view file path from view folder * @param passedData Set data to load in view * * @author Robin <robin@cubettech.com> * @Date 23-10-2013 */ loadView: function(res, view, passedData){ try { var pt = _fns._getCallerFile(); var plugin = pt.split('/'); var caller = plugin[plugin.indexOf('controllers')-1]; //get plugin overrides var plugsDir = fs.readdirSync(path.join(appPath,'modules')); async.eachSeries(plugsDir, function (plug, _pcbk) { var stats = fs.statSync(path.join(appPath,'modules',plug)); if(stats.isDirectory() && plug.charAt(0) !== '.' && fs.existsSync(path.join(appPath,'modules',plug,'override.js'))){ var Ovr = require(path.join(appPath,'modules',plug, 'override.js')); async.eachSeries(Ovr.data, function (_ovDt, _ovcbk) { if(_ovDt.view === view && caller === 'application'){ var prio = _ovDt.priority ? _ovDt.priority : 0; var _mode = _ovDt.mode; if(_mode === 'append' || _mode === 'prepend' || _mode === 'replace'){ passedData.locals = res.locals; if(!passedData.PLUGINS){ passedData.PLUGINS = []; } var M = system.getPluginController(_ovDt.controller,plug); var fn = M[_ovDt.action]; fn(res.sleekReq,res,passedData,function(dt){ passedData.PLUGINS.push({data:dt, mode:_mode, prio:prio}); _ovcbk(); }); } else { _ovcbk(); } } else { _ovcbk(); } }, function(){ _pcbk(); }); } else { _pcbk(); } }, function(){ var getpath = path.join(sleekConfig.theme,view+'.html'); var realPath = getpath; fs.exists(path.join(appPath,'application/views',getpath), function(exists){ if(! exists) { realPath = path.join('default',view+'.html'); } }); var assetFile = path.join(app.get('views'), path.dirname(realPath), 'assets.json'); var scripts = ''; fs.exists(assetFile, function(exists){ if(exists) { fs.readFile(assetFile, 'utf8', function (err, data) { if (err) { this.log(err); return; } data = JSON.parse(data); for(var count in data.css) { if (fs.existsSync(path.join(appPath,'public', sleekConfig.theme, 'css', data.css[count]+'.css'))) { scripts += '<link rel="stylesheet" href="'+path.join('/',sleekConfig.theme, 'css', data.css[count]+'.css')+'"/>\n'; } else if (fs.existsSync(path.join(appPath,'application/views', sleekConfig.theme, 'assets/css', data.css[count]+'.css'))) { scripts += '<link rel="stylesheet" href="'+path.join('assets/themes', sleekConfig.theme, 'css', data.css[count])+'.css"/>\n'; } else if (fs.existsSync(path.join(appPath,'application/views/default/assets/css', data.css[count]+'.css'))) { scripts += '<link rel="stylesheet" href="'+path.join('assets/themes/default/css', data.css[count])+'.css"/>\n'; } else { scripts += '<link rel="stylesheet" href="'+path.join('/default', 'css', data.css[count]+'.css')+'"/>\n'; } } for(var count in data.js) { if (fs.existsSync(path.join(appPath,'public', sleekConfig.theme, 'js', data.js[count]+'.js'))) { scripts += '<script type="text/javascript" src="'+path.join('/',sleekConfig.theme, 'js', data.js[count]+'.js')+'" ></script>\n'; } else if (fs.existsSync(path.join(appPath,'application/views', sleekConfig.theme, 'assets/js', data.js[count]+'.js'))) { scripts += '<script type="text/javascript" src="'+path.join('assets/themes', sleekConfig.theme, 'js', data.js[count])+'.js" ></script>\n'; } else if (fs.existsSync(path.join(appPath,'application/views/default/assets/js', data.js[count]+'.js'))) { scripts += '<script type="text/javascript" src="'+path.join('/assets/themes/default/js', data.js[count])+'.js" ></script>\n'; } else { scripts += '<script type="text/javascript" src="'+path.join('/default', 'js', data.js[count])+'.js" ></script>\n'; } } hbs.registerPartial('getSleekScripts', scripts); _fns._render(res,view,passedData); }); } else { hbs.registerPartial('getSleekScripts', scripts); _fns._render(res,view,passedData); } }); }); } catch (err) { this.log(err); } }, /** * Write log * * @param str string to log error * @param status Optional log status * * @author Robin <robin@cubettech.com> * @Date 23-10-2013 */ log: function(str, status){ if(!status) { status = 'Error'; } if(str) { if(str.stack) str = str.stack; var log = '\n' + status + ' :: ' + new Date() + ' :\n'; log += str +'\n'; if(sleekConfig.logToFile === true) { fs.appendFile(path.join(appPath, sleekConfig.systemlog), log, function (err) {}); } else { console.log(log); } } }, /** * Get compiled handlebars template with data * * @param name file path from view folder * @param data Data to compile with template * @param raw if true returns raw template * * @author Robin <robin@cubettech.com> * @Date 13-11-2013 */ getCompiledView: function(name, data, raw){ try { if(!name){ throw 'Please supply template name to compile' } var realPath = path.join(appPath,'application/views',sleekConfig.theme, name+'.html'); if (! fs.existsSync(realPath)) { realPath = path.join(appPath,'application/views/default',name+'.html'); } var templateFile = fs.readFileSync(realPath, 'utf8'); if(raw) { return templateFile; } else { var template = hbs.compile(templateFile); var compiled; if(data){ compiled = template(data); } else { compiled = template(); } return compiled; } } catch (err) { this.log(err); } }, /** * Get controller from a plugin * * @param controller controller name * @param plugin plugin name * * @author Robin <robin@cubettech.com> * @Date 22-01-2014 */ getPluginController: function(controller, plugin){ try { return require(path.join(appPath,'modules', plugin, 'controllers', controller+'.js')); } catch (err) { this.log(err); } }, /** * get model object from a plugin * pass model name. example; for userModel pass user * * @param model model name, ignoring trailing 'Model' * @param plugin plugin name * @return model object * @author Robin <robin@cubettech.com> * @Date 22-01-2014 */ getPluginModel: function(model,plugin){ try { //if calling from plugins controller if(!plugin){ var pt = _fns._getCallerFile(); plugin = pt.split('/'); if(plugin[plugin.indexOf('controllers')-2] === 'modules') { plugin = plugin[plugin.indexOf('controllers')-1]; } else { this.log('Please specify plugin name'); } } return require(path.join(appPath,'modules', plugin, 'models',model+'Model.js')); } catch (err) { this.log(err); } }, /** * Load Plugin view * * @param res response object * @param view file path from view folder * @param passedData Set data to load in view * @param plugin name (optional, if calling from plugin) * * @author Robin <robin@cubettech.com> * @Date 22-01-2014 */ loadPluginView: function(res, view, passedData,plugin){ try { //if calling from plugins controller if(!plugin){ var pt = _fns._getCallerFile(); plugin = pt.split('/'); if(plugin[plugin.indexOf('controllers')-2] === 'modules') { plugin = plugin[plugin.indexOf('controllers')-1]; } else { this.log('Please specify plugin name'); } } var realPath = path.join(appPath,'modules',plugin,'views',view+'.html'); fs.exists(path.join(appPath,'modules',plugin,'views',view+'.html'), function(exists){ if(! exists) { realPath = path.join('default',view+'.html'); } }); var assetFile = path.join(path.dirname(realPath), 'assets.json'); var scripts = ''; fs.exists(assetFile, function(exists){ if(exists) { fs.readFile(assetFile, 'utf8', function (err, data) { if (err) { this.log(err); return; } data = JSON.parse(data); for(var count in data.css) { if (fs.existsSync(path.join(appPath,'modules', plugin, 'assets', 'css', data.css[count]+'.css'))) { scripts += '<link rel="stylesheet" href="'+path.join('/assets','modules', plugin, 'css', data.css[count]+'.css')+'"/>\n'; } else if(fs.existsSync(path.join(appPath,'public', sleekConfig.theme, 'css', data.css[count]+'.css'))) { scripts += '<link rel="stylesheet" href="'+path.join('/', sleekConfig.theme, 'css', data.css[count]+'.css')+'"/>\n'; } else { scripts += '<link rel="stylesheet" href="'+path.join('/default', 'css', data.css[count]+'.css')+'"/>\n'; } } for(var count in data.js) { if(fs.existsSync(path.join(appPath,'modules', plugin, 'assets', 'js', data.js[count]+'.js'))){ scripts += '<script type="text/javascript" src="'+path.join('/assets','modules', plugin, 'js', data.js[count]+'.js')+'" ></script>\n'; } else if (fs.existsSync(path.join(appPath,'public', sleekConfig.theme, 'js', data.js[count]+'.js'))) { scripts += '<script type="text/javascript" src="'+path.join('/',sleekConfig.theme, 'js', data.js[count]+'.js')+'" ></script>\n'; } else { scripts += '<script type="text/javascript" src="'+path.join('/default', 'js', data.js[count]+'.js')+'" ></script>\n'; } } hbs.registerPartial('getSleekScripts', scripts); res.render(realPath, passedData); }); } else { hbs.registerPartial('getSleekScripts', scripts); res.render(realPath, passedData); } }); } catch (err) { this.log(err); } }, /** * Get compiled handlebars template with data from plugin * * @param name file path from view folder * @param data Data to compile with template * @param plugin name (optional, if calling from plugin) * @param raw if true, returns raw template file without compiling * * @author Robin <robin@cubettech.com> * @Date 22-01-2014 */ getCompiledPluginView: function(name, data, plugin, raw){ try { //if calling from plugins controller if(!plugin){ var pt = _fns._getCallerFile(); plugin = pt.split('/'); if(plugin[plugin.indexOf('controllers')-2] === 'modules') { plugin = plugin[plugin.indexOf('controllers')-1]; } else { this.log('Please specify plugin name'); } } if(!name){ throw 'Please supply template name to compile'; } var realPath = path.join(appPath,'modules',plugin,'views',name+'.html'); if (! fs.existsSync(realPath)) { realPath = path.join(appPath,'application/views/default',name+'.html'); } var templateFile = fs.readFileSync(realPath, 'utf8'); if(raw){ return templateFile; } else { var template = hbs.compile(templateFile); var compiled; if(data){ compiled = template(data); } else { compiled = template(); } return compiled; } } catch (err) { this.log(err); } }, /** * Get define values * @param key define key * * @author Robin <robin@cubettech.com> * @Date 18-06-2014 **/ defines: function(key,value){ if(value) { __def[key] = value; } else { return __def[key] ? __def[key] : ''; } } }; module.exports = function(app){ try { //file server for plugins & themes app.get('/assets/:mod?/:plugin?/:type?/:file*?', function(req, res){ var filepath = req.params.file + (req.params[0] || ''); if(!req.params.plugin || !req.params.type || !req.params.file || !req.params.mod) { res.end(); return false; } if(req.params.mod === 'modules'){ res.sendfile(path.join(appPath,'modules',req.params.plugin,'assets',req.params.type,filepath)); } else if (req.params.mod === 'themes') { res.sendfile(path.join(appPath,'application/views',req.params.plugin,'assets',req.params.type,filepath)); } }); //set commons var R = require(path.join(appPath, 'application/config/routes.js')); var Helper = require(path.join(appPath, 'application/helpers/routes.js')); var commonfns = []; if(R.commonRouteFunctions) { for(var i in R.commonRouteFunctions){ var ct = R.commonRouteFunctions[i]; if(Helper[ct] instanceof Function) { var sc = Helper[ct] ? Helper[ct] : function(req,res,next){ next(); }; commonfns.push(sc); } } } else { commonfns = function(req,res,next){ next(); }; } //routes var rts = []; for(var c in R.routes) { var rt = R.routes[c]; rts[c] = system.getController(rt.controller); var act = rt.action; var rout = rt.route; for(var r in rt.params) { rout += '/' + rt.params[r] + '([A-Za-z0-9_]+)?'; } var fn = Helper[rt.fn] ? Helper[rt.fn] : function(req,res,next){ next(); }; if(!rt.type) { rt.type = 'GET'; } var meth = rt.type.toLowerCase(); app[meth](rout, commonfns, fn, rts[c][act]); } //plugin routes var plugsDir = fs.readdirSync(path.join(appPath,'modules')); var prts = []; for(var p in plugsDir) { var stats = fs.statSync(path.join(appPath,'modules',plugsDir[p])); if(stats.isDirectory() && plugsDir[p].charAt(0) !== '.' && fs.existsSync(path.join(appPath,'modules',plugsDir[p],'routes.js'))){ var P = require(path.join(appPath,'modules',plugsDir[p], 'routes.js')); for(var c in P.routes) { var rt = P.routes[c]; prts[c] = system.getPluginController(rt.controller,plugsDir[p]); var act = rt.action; var rout = rt.route; for(var r in rt.params) { rout += '/' + rt.params[r] + '([A-Za-z0-9_]+)?'; } var fn = Helper[rt.fn] ? Helper[rt.fn] : function(req,res,next){ next(); }; if(!rt.type) { rt.type = 'GET'; } var meth = rt.type.toLowerCase(); app[meth](rout,commonfns, fn, prts[c][act]); } } } //handle unknown requests app.all('*/([A-Za-z0-9_]+)', function(req, res){ var realPath = path.join(appPath,'application/views',sleekConfig.theme, 'error.html'); if (! fs.existsSync(realPath)) { realPath = path.join(__dirname,'error.html'); } var templateFile = fs.readFileSync(realPath, 'utf8'); var template = hbs.compile(templateFile); var compiled = template({title:'404! Page Not Found'}); res.send(compiled); }); //handle 500 app.use(function(err, req, res, next){ res.status(err.status || 500); var type= req.accepts('html', 'json', 'text'); system.log(err.stack); if(type === 'html') { var realPath = path.join(appPath,'application/views',sleekConfig.theme, 'error.html'); var builtinview = false; if (! fs.existsSync(realPath)) { builtinview = true; realPath = path.join(__dirname,'error.html'); } var templateFile = fs.readFileSync(realPath, 'utf8'); var template = hbs.compile(templateFile); var er = app.get('env') === 'development' ? err : 'Sorry! Something went wrong. Please try later'; var ers = app.get('env') === 'development' ? err.stack : ''; var compiled = template({ title:'Application Error', error: er, stack:ers }); if((app.get('env') !== 'development') && builtinview) { compiled += '<style type="text/css">pre{text-align: center;}</style>'; } res.send(compiled); } else if (type === 'json') { var error = { message: err.message, stack: err.stack }; for (var prop in err) error[prop] = err[prop]; var json = JSON.stringify({ error: error }); res.setHeader('Content-Type', 'application/json'); res.end(json); // plain text } else { res.setHeader('Content-Type', 'text/plain'); res.end(err.stack || String(err)); } }); } catch (e){ system.log(e); } };