jsharmony
Version:
Rapid Application Development (RAD) Platform for Node.js Database Application Development
425 lines (387 loc) • 16.4 kB
JavaScript
/*
Copyright 2017 apHarmony
This file is part of jsHarmony.
jsHarmony is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
jsHarmony is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this package. If not, see <http://www.gnu.org/licenses/>.
*/
var _ = require('lodash');
var fs = require('fs');
var path = require('path');
var async = require('async');
var DB = require('jsharmony-db');
var Helper = require('./lib/Helper.js');
var HelperFS = require('./lib/HelperFS.js');
var AppSrv = require('./AppSrv.js');
var jsHarmonyConfig = require('./jsHarmonyConfig.js');
var jsHarmonyExtensions = require('./jsHarmonyExtensions.js');
var jsHarmonySite = require('./jsHarmonySite.js');
var jsHarmonyServer = require('./jsHarmonyServer.js');
var jsHarmonyModule = require('./jsHarmonyModule.js');
var jsHarmonyLocale = require('./jsHarmonyLocale.js');
var jsHarmonyMailer = require('./lib/Mailer.js');
var Logger = require('./lib/Logger.js');
var XValidate = require('jsharmony-validate');
require('./lib/ext-validation.js')(XValidate);
function jsHarmony(config) {
this.Config = new jsHarmonyConfig(config);
this.Modules = {};
this.Extensions = new jsHarmonyExtensions();
this.Views = {};
this.DBConfig = {};
this.DB = {};
this.Sites = {
'main': new jsHarmonySite.Placeholder()
};
this.Servers = {};
this.Log = new Logger(this);
this.Mailer = undefined;
this.Locale = new jsHarmonyLocale('en');
this.SystemErrors = [];
this.EJS = {};
this.Stylus = {
/*
'stylusName': {
source: this.Config.moduledir+'....styl', //Stylus Source File
path: '/....css', //URL for router
//roles: { siteid: { "*":"B" } }, //(If roles are not defined, enable access by any role for all sites)
public: true //Enable public unauthenticated access
//css: 'cached css' //Cached / evaluated css
}
*/
};
this.Models = {}; //Do not access this directly - use getModel, hasModel
this.CustomControls = {};
this.CustomControlQueries = {};
this.XValidate = XValidate;
this.CustomFormatters = {};
this.Popups = {};
this.Cache = {};
this.FontCache = {};
this.AppSrv = null;
this.codegen = null;
this.map = {};
this.uimap = {};
this.isInitialized = false;
this.isConfigLoaded = false;
this.onLoadingConfig = null; //function(configFiles){ /* Re-order / modify configFiles array */ }
this.Statistics = {
StartTime: Date.now(),
Counts: {
InitErrors: 0,
InitWarnings: 0,
InitDeprecated: 0
}
};
//Add jsHarmony Module
this.Modules['jsharmony'] = new jsHarmonyModule.jsHarmonySystemModule(this);
}
//Add module (before Init/Run)
jsHarmony.prototype.AddModule = function(module){
if(!module.name) module.name = module.typename;
var moduleName = module.name;
module.jsh = this;
if(moduleName in this.Modules) throw new Error('Module '+moduleName+' already exists in jsh.Modules');
this.Modules[moduleName] = module;
//Initialize / Merge Module Config
if(this.Config.modules[moduleName]) module.Config.Merge(this.Config.modules[moduleName], this, module.name);
this.Config.modules[moduleName] = module.Config;
//Run onModuleAdded event
module.onModuleAdded(this);
};
jsHarmony.prototype.GetModule = function(moduleName){
return this.Modules[moduleName];
};
//Load models and initialize the configuration
jsHarmony.prototype.Init = function(init_cb){
var _this = this;
if(_this.isInitialized){
if(init_cb) init_cb();
return;
}
_this.LogInit_PERFORMANCE('Starting '+(Date.now()-_this.Statistics.StartTime));
//Load Configuration Files
_this.Config.LoadJSConfigFolder(_this);
_this.Config.LoadJSONConfigFolder(_this);
var defaultDBDriver = null;
//Initialize Configuration
async.waterfall([
function(cb){ _this.Config.Init(cb); },
function(cb){
_this.LogInit_PERFORMANCE('Loading Config '+(Date.now()-_this.Statistics.StartTime));
//Add Application Module
if(!_this.Modules['application']){
_this.AddModule(new jsHarmonyModule.ApplicationModule(_this));
}
//Set Module Namespace, so that relative paths can be transformed to absolute
_this.SetModuleNamespace();
//Load Configuration Files from modules
var modeldirs = _this.getModelDirs();
for (var i = 0; i < modeldirs.length; i++) {
_this.Config.LoadJSONConfigFolder(_this, path.normalize(modeldirs[i].path), _this.Modules[modeldirs[i].module]);
}
//Create Required Folders
_this.requireFolder(_this.Config.datadir,'Data folder');
HelperFS.createFolderIfNotExistsSync(_this.Config.localmodeldir);
async.waterfall([
async.apply(HelperFS.createFolderIfNotExists, _this.Config.logdir),
async.apply(HelperFS.createFolderIfNotExists, _this.Config.datadir + 'temp'),
async.apply(HelperFS.createFolderIfNotExists, _this.Config.datadir + 'temp/public'),
async.apply(HelperFS.createFolderIfNotExists, _this.Config.datadir + 'temp/cmsfiles'),
async.apply(HelperFS.createFolderIfNotExists, _this.Config.datadir + 'temp/report'),
], function (err, rslt) { if (err) _this.Log.error(err); });
return cb();
},
function(cb){
_this.LogInit_PERFORMANCE('Initializing Config '+(Date.now()-_this.Statistics.StartTime));
//Initialize Module Configs
async.eachSeries(_this.Modules, function(module, module_cb){
module.Config.Init(module_cb, _this);
}, cb);
},
function(cb){
//Apply database-specific configurations
var modeldirs = _this.getModelDirs();
for(var dbid in _this.DBConfig){
var dbconfig = _this.DBConfig[dbid];
if(dbconfig && dbconfig._driver && dbconfig._driver.name){
var driverName = dbconfig._driver.name;
if(driverName in _this.Config.forDB){
var driverConfigs = _this.Config.forDB[driverName];
_.each(driverConfigs, function(driverConfig){
if(!driverConfig.sourceModuleName) _this.Config.Merge(driverConfig, _this);
});
for (var i = 0; i < modeldirs.length; i++) {
_.each(driverConfigs, function(driverConfig){
if(driverConfig.sourceModuleName == modeldirs[i].module) _this.Config.Merge(driverConfig, _this);
});
}
}
}
}
return cb();
},
function(cb){
Helper.triggerAsync(_this.Config.onConfigLoaded, cb, _this);
},
function(cb){
_this.LogInit_PERFORMANCE('Transforming '+(Date.now()-_this.Statistics.StartTime));
//Load Views
_this.LoadViews();
//Validate Module Transforms
_.each(_this.Modules, function(module){
module.transform.Validate();
});
return cb();
},
function(cb){
_this.LogInit_PERFORMANCE('Loading Locales '+(Date.now()-_this.Statistics.StartTime));
//Preload translations for current locale
async.each(_this.Modules, function(module, module_cb){
module.translator.loadLanguages([_this.Locale.id], module_cb);
}, cb);
},
function(cb){
_this.LogInit_PERFORMANCE('Loading DB '+(Date.now()-_this.Statistics.StartTime));
//Load Database Drivers
if(!_this.DBConfig['default']) { _this.DBConfig['default'] = { _driver: new DB.noDriver() }; }
async.eachOfSeries(_this.DBConfig, function(db, dbid, db_cb){
_this.LogInit_PERFORMANCE('Initializing DB '+dbid+' '+(Date.now()-_this.Statistics.StartTime));
_this.InitDB(dbid, db_cb);
}, function(){
defaultDBDriver = _this.DBConfig['default']._driver.name;
return cb();
});
},
function(cb){
_this.LogInit_PERFORMANCE('DB Driver Loaded Event '+(Date.now()-_this.Statistics.StartTime));
Helper.triggerAsync(_this.Config.onDBDriverLoaded, cb, _this);
},
function(cb){
_this.LogInit_PERFORMANCE('Loading Schemas '+(Date.now()-_this.Statistics.StartTime));
if(!_this.Config.silentStart) _this.Log.console('Loading models...');
_this.LoadDBSchemas(cb);
},
function(cb){
_this.LogInit_PERFORMANCE('Loading SQL Objects '+(Date.now()-_this.Statistics.StartTime));
_this.ParseSQLObjectInheritance();
_this.ParseSQLObjects();
return cb();
},
function(cb){
//Configure Mailer
if(!_this.Mailer && _this.Config.mailer_settings) _this.Mailer = jsHarmonyMailer(_this.Config.mailer_settings, _this.Log.info);
return cb();
},
function(cb){
if(!_this.Config.loadModels){ return cb(); }
_this.isConfigLoaded = true;
_this.LogInit_PERFORMANCE('Loading Models '+(Date.now()-_this.Statistics.StartTime));
var modeldirs = _this.getModelDirs();
_this.Cache['application.js'] = '';
_this.Cache['application.css'] = '';
if(_this.Config.theme && _this.Config.themes[_this.Config.theme]){
_.each(_this.Config.themes[_this.Config.theme], function(css_path){
if(css_path){
for (var i = 0; i < modeldirs.length; i++) {
var modeldir = modeldirs[i];
var module_css_path = path.join(modeldir.path, '../themes', css_path);
if (fs.existsSync(module_css_path)) _this.Cache['application.css'] += '\r\n' + fs.readFileSync(module_css_path, 'utf8');
}
}
});
}
for (var i = 0; i < modeldirs.length; i++) {
var modeldir = modeldirs[i];
var prefix = modeldir.namespace||'';
if (fs.existsSync(modeldir.path)) _this.LoadModels(modeldir.path, modeldir, prefix, defaultDBDriver, modeldir.module);
if (fs.existsSync(modeldir.path + 'js/')) _this.Cache['application.js'] += '\r\n' + _this.MergeFolder(modeldir.path + 'js/', modeldir.module);
if (fs.existsSync(modeldir.path + 'public_css/')) _this.Cache['application.css'] += '\r\n' + _this.MergeFolder(modeldir.path + 'public_css/', modeldir.module);
}
_this.LogInit_PERFORMANCE('Parsing Models '+(Date.now()-_this.Statistics.StartTime));
_this.ParseMacros();
_this.ParseDeprecated();
_this.ParseModelInheritance();
_this.ParseEntities();
_this.ParsePopups();
return cb();
},
function(cb){
_this.LogInit_PERFORMANCE('Validating Config '+(Date.now()-_this.Statistics.StartTime));
//Validate Configuration
_this.Config.Validate(_this,'jsHarmony');
for(var moduleName in _this.Modules){
if(_this.Modules[moduleName].Config===_this.Config) continue;
_this.Modules[moduleName].Config.Validate(_this,'module '+moduleName);
}
//Load Field Mapping
_this.map = _this.Config.field_mapping;
_this.uimap = _this.Config.ui_field_mapping;
for(var dbid in _this.DB){
_this.AddGlobalSQLParams(_this.DB[dbid].SQLExt.Funcs, _this.map, 'jsh.map.');
}
//Load AppSrv
_this.AppSrv = new _this.AppSrvClass(_this);
cb();
},
function(cb){
_this.LogInit_PERFORMANCE('Initializing Modules '+(Date.now()-_this.Statistics.StartTime));
//Initialize Modules
async.eachSeries(_this.Modules, function(module, module_cb){
module.Init(module_cb);
}, cb);
},
function(cb){
//Initialize Extensions
_this.InitExtensions(cb);
},
function(cb){
_this.LogInit_PERFORMANCE('Initializing Sites '+(Date.now()-_this.Statistics.StartTime));
for(var siteid in _this.Sites){
if(!_this.Sites[siteid].initialized){
_this.Sites[siteid] = new jsHarmonySite(_this, siteid, _this.Sites[siteid]);
if(siteid=='main') _this.Config.server.add_default_routes = true;
}
}
_this.isInitialized = true;
var loadTime = (Date.now()-_this.Statistics.StartTime);
_this.Log.clearLastErrorFile();
if(!_this.Config.silentStart){
_this.Log.console('::jsHarmony Server ready:: '+(loadTime/1000).toFixed(2)+'s');
var statsmsg = [];
if(_this.Statistics.Counts.InitErrors) statsmsg.push('Errors: '+_this.Statistics.Counts.InitErrors);
if(_this.Statistics.Counts.InitWarnings) statsmsg.push('Warnings: '+_this.Statistics.Counts.InitWarnings);
if(_this.Statistics.Counts.InitDeprecated) statsmsg.push('Deprecated: '+_this.Statistics.Counts.InitDeprecated);
if(statsmsg.length) _this.Log.console(' '+statsmsg.join(', '));
}
return cb();
}
], init_cb);
};
//Initialize jsHarmony Express Server
jsHarmony.prototype.CreateServer = function(serverConfig, cb){
var rslt = new jsHarmonyServer(serverConfig||this.Config.server, this);
rslt.Init(function(){
if(cb) return cb(rslt);
});
};
//Initialize jsHarmony and start the server
jsHarmony.prototype.Run = function(onComplete){
var _this = this;
_this.Init(function(){
//Run each module
async.each(_this.Modules, function(module, module_cb){
if(module.Run) return module.Run(module_cb);
else return module_cb();
}, function(){
//If no module started a server, run the default server
if(_.isEmpty(_this.Servers)){
//Add default listener & server
_this.CreateServer(_this.Config.server, function(server){
_this.Servers['default'] = server;
_this.Servers['default'].Run(onComplete);
});
}
else{
if(onComplete) onComplete();
return;
}
});
});
};
//Initialize Module Namespaces / Schemas
jsHarmony.prototype.SetModuleNamespace = function(moduleName){
var _this = this;
//Set namespace of root modules
if(!moduleName){
for(var rootModuleName in _this.Modules){
var module = _this.Modules[rootModuleName];
if(module.parent) continue;
_this.SetModuleNamespace(module.name);
}
return;
}
//Parse rest of modules in tree
_this.Modules[moduleName].SetModuleNamespace();
for(var childModuleName in _this.Modules){
var childModule = _this.Modules[childModuleName];
if(childModule.parent !== moduleName) continue;
_this.SetModuleNamespace(childModuleName);
}
};
//Set the Job Processor
jsHarmony.prototype.SetJobProc = function(jobproc){
this.AppSrv.JobProc = jobproc;
};
//Require a folder in order to start jsHarmony
jsHarmony.prototype.requireFolder = function(fpath,desc){
var _this = this;
if(!fs.existsSync(fpath)){
if(!desc) desc = 'Path';
_this.Log.console('FATAL ERROR: '+desc+' '+fpath+' not found.');
_this.Log.console('Please create this folder or change the config to use a different path.');
process.exit(8);
}
};
jsHarmony.prototype.getPrototype = function(){ return jsHarmony; };
jsHarmony.prototype.Auth = require('./lib/Auth.js');
jsHarmony.prototype.AppSrvClass = AppSrv;
jsHarmony.Auth = jsHarmony.prototype.Auth;
jsHarmony.lib = {};
jsHarmony.lib.Helper = Helper;
jsHarmony.lib.HelperFS = HelperFS;
jsHarmony.typename = 'jsHarmony';
jsHarmony.prototype = _.extend(jsHarmony.prototype, require('./jsHarmony.Render.js'));
jsHarmony.prototype = _.extend(jsHarmony.prototype, require('./jsHarmony.Helper.js'));
jsHarmony.prototype = _.extend(jsHarmony.prototype, require('./jsHarmony.LoadModels.js'));
jsHarmony.prototype = _.extend(jsHarmony.prototype, require('./jsHarmony.LoadSQL.js'));
jsHarmony.prototype = _.extend(jsHarmony.prototype, require('./jsHarmony.LoadViews.js'));
jsHarmony.prototype = _.extend(jsHarmony.prototype, require('./jsHarmony.LoadTasks.js'));
module.exports = jsHarmony;