var colors = require('colors'); // for logging
var express = require('express');
var _ = require('lodash');
var fs = require('fs');
var http = require('http');
var https = require('https');
var bodyParser = require('body-parser');
var multer = require('multer');
var routeManager = require('./routeManager');
/**
* A SnoozeJS Module
* @constructor
* @param {string} nm - The name of the module
* @param {array} modules - Array of module names to inject
*/
var Module = function(nm, modules) {
/**
* @access private
* @ignore
* SnoozeJS NPM Module
*/
var snooze = require('./snooze');
/**
* @access private
* @ignore
* The return object of the constructor.
*/
var ret = {};
/**
* @access private
* @ignore
* A reference to the module
*/
var self = null;
/**
* @access private
* @ignore
* Express App
*/
var _app = null;
/**
* @access private
* @ignore
* The name of the module
*/
var _name = null;
/**
* @access private
* @ignore
* The port to start the server on
*/
var _port = null;
/**
* @access private
* @ignore
* HTTP Post Limit
*/
var _postLimit = '50mb';
/**
* @access private
* @ignore
* Enable/Disable Logging
*/
var _logging = true;
/**
* @access private
* @ignore
* True if wakeup() has been called
*/
var _isAwake = false;
/**
* @access private
* @ignore
* snooze.json config
*/
var _config = snooze.getConfig();
/**
* @access private
* @ignore
* snooze.json SSL Config
*/
var _ssl = null;
/**
* @access private
* @ignore
* Is true of SSL is configured
*/
var _isHTTPs = false;
/**
* @access private
* @ignore
* Options to start express on
*/
var expressOptions = {};
/**
* @access private
* @ignore
* Run functions defined by the run() method
*/
var _runs = [];
/**
* @access private
* @ignore
* Injected modules
*/
var _modules = [];
/**
* @access private
* @ignore
* Server routes
*/
var _routes = [];
/**
* @access private
* @ignore
* Lib paths to import files from
*/
var _libs = [];
/**
* @access private
* @ignore
* An Entity Manager assigned to this module
*/
var EntityManager = require('./entityManager')(ret);
/**
* @access private
* @ignore
* A Mock Entity Manager assigned to this module
*/
var MockEntityManager = require('./entityManager')(ret);
/**
* @access private
* @ignore
* A Route Manager assigned to this module
*/
var _routeManager = routeManager(ret);
if(modules !== undefined) {
var _modules = modules;
}
_name = nm;
/**
* Creates the $module service
*/
var init = function() {
self = this;
if(_name === null || _name === undefined || _name.length < 1) {
snooze.fatal(new snooze.exceptions.ModuleNameNotDefinedException());
}
_createModuleService();
};
/**
* Sets process env vars from the snooze.json env config
*/
var initEnv = function() {
var mode = _config.mode;
var env = _config.modes[mode].env;
for(var key in env) {
var val = env[key];
process.env[key] = val;
}
};
/**
* Loads key/cert files from paths defined in snooze.json
*/
var initSSL = function() {
var mode = _config.mode;
var _ssl = _config.modes[mode].ssl;
if(_ssl !== undefined) {
_isHTTPs = true;
expressOptions.key = fs.readFileSync(process.cwd() + '/' + _ssl.key);
expressOptions.cert = fs.readFileSync(process.cwd() + '/' + _ssl.cert);
}
};
/**
* Requires files found in lib paths
* @param {string} bp - Set the base path to search from (defaults to cwd)
*/
var requireLibs = function(bp) {
var basePath = process.cwd();
if(bp !== undefined) {
basePath = bp;
}
var loadLibs = [];
_.each(_libs, function(lib) {
loadLibs.push(lib);
});
if(_isAwake === true) {
_.each(_config.libs, function(lib) {
loadLibs.push(lib);
});
}
for(var i = 0; i < loadLibs.length; i++) {
var path = basePath + '/' + loadLibs[i];
log(('opening lib path ' + path).yellow);
require('fs').readdirSync(path).forEach(function(file) {
log(('+ ' + file).yellow);
require(path + '/' + file);
});
}
};
/**
* Sets allow origin headers if allowOrigin is set in the snooze.json config
*/
var initOrigin = function() {
if(_config.allowOrigin === true) {
_app.use(function(req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'POST, GET, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Credentials', false);
res.setHeader('Access-Control-Max-Age', '86400');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept, Authorization');
next();
});
}
};
/**
* Sets express middlewares to use
*/
var initMiddleWare = function() {
_app.use(bodyParser.urlencoded({size: _postLimit, extended: true}));
_app.use(bodyParser.json({size: _postLimit}));
_app.use(multer({dest: './.tmp'}));
};
/**
* Merges startingConfig (if defined in wakeup()) into the runtime config
*/
var mergeStartingConfig = function(startingConfig) {
for(var key in startingConfig) {
_config[key] = startingConfig[key];
}
};
/**
* Merges modeConfig (like production or dev) into the runtime config
*/
var mergeModeConfig = function(mode) {
for(var key in _config.modes[mode]) {
_config[key] = _config.modes[mode][key];
}
};
/**
* Sets the port to 8000 if port has not already been defined and
* isn't defined in the config.
*/
var initPort = function() {
if(_port === null) {
_port = _config.port;
}
if(_port === undefined) {
_port = 8000;
}
};
/**
* Starts the express server with the express options.
*/
var startExpress = function() {
if(_isHTTPs === true) {
https.createServer(expressOptions, _app).listen(_port);
} else {
http.createServer(_app).listen(_port);
}
};
// Start
/**
* Starts the SnoozeJS server
* @param {object} startingConfig - Additional config (see snooze.json) that
* will merge into the snooze.json at runtime
*/
var wakeup = function(startingConfig) {
_isAwake = true;
mergeStartingConfig(startingConfig);
mergeModeConfig(_config.mode);
initEnv();
initSSL();
initPort();
_app = express(expressOptions);
initMiddleWare();
initOrigin();
importModules();
requireLibs();
EntityManager.compile();
_routeManager.compileRoutes(_routes);
_routeManager.bindRoutes();
startExpress();
doRuns();
log('Is it morning already?'.red);
log(('snooze started on port ' + _port).green);
};
// Accessors
/**
* Gets an injectable. When getting injectables,
* MockEntityManager will be checked first. With the exception to DAOs.
*
* @param {string} parameter - The name of the injectable to get
* @param {object} obj - Optionally an object that is requesting the injectable.
* This will be added to the objects recorded injectables list.
*/
var getInjectable = function(parameter, obj) {
if(parameter.substr(-3) === 'DTO')
{
// Mock
inj = MockEntityManager.getDTO(parameter);
if(inj !== undefined) {
if(obj !== undefined) {
obj.__addDTO(parameter);
}
} else {
// Actual
inj = EntityManager.getDTO(parameter);
if(inj !== undefined) {
if(obj !== undefined) {
obj.__addDTO(parameter);
}
} else {
snooze.fatal(new snooze.exceptions.InjectableNotFoundException('DTO', parameter, obj.getName()));
}
}
} else if(parameter.substr(-3) === 'DAO') {
// There is no such thing as a mock DAO
inj = EntityManager.getDAO(parameter);
if(inj !== undefined) {
if(obj !== undefined) {
obj.__addDAO(parameter);
}
} else {
snooze.fatal(new snooze.exceptions.InjectableNotFoundException('DAO', parameter, obj.getName()));
}
} else {
// Mock
var inj = MockEntityManager.getService(parameter);
if(inj !== undefined) {
if(obj !== undefined) {
obj.__addSrv(parameter);
}
} else {
// Actual
var inj = EntityManager.getService(parameter);
if(inj !== undefined) {
if(obj !== undefined) {
obj.__addSrv(parameter);
}
} else {
var name = 'undefined';
if(obj !== undefined) {
name = obj.getName();
}
snooze.fatal(new snooze.exceptions.InjectableNotFoundException('Service', parameter, name));
}
}
}
if(inj.$get !== undefined) {
return inj.$get();
}
return inj;
};
/**
* Gets the name of the Module
* @return {string} The name of the Module
*/
var getName = function() {
return _name;
};
/**
* Gets the config of the Module
* @return {module} The config for Module
*/
var getConfig = function() {
return _config;
};
/**
* Returns true of in testing mode
* @return {boolean} True if in testing mode
*/
var inTestMode = function() {
if(_config.unitTesting && _config.unitTesting.mode) {
if(_config.mode === _config.unitTesting.mode) {
return true;
}
}
return false;
};
/**
* Gets the SnoozeJS Express
* @return {object} ExpressJS App
*/
var getExpress = function() {
return _app;
};
/**
* Gets the SnoozeJS routes from the routeManager
* @return {array} Array of routes
*/
var getRoutes = function(type) {
return _routeManager.getRoutes(type);
};
/**
* Gets the SnoozeJS RouteManager
* @return {object} RouteManager
*/
var getRouteManager = function() {
return _routeManager;
};
/**
* Returns true of wakeup() has been called
* @return {boolean}
*/
var isAwake = function() {
return _isAwake;
};
/**
* Gets the SnoozeJS NPM Module
* @return {object} SnoozeJS NPM Module
*/
var getSnooze = function() {
return snooze;
};
// Modifiers
/**
* Creates the $module service
* @access private
* @ignore
*/
var _createModuleService = function() {
EntityManager.service('$module', function() {
return {
$get: function() {
return self;
}
};
});
};
/**
* Includes the snooze-baselib services
* @access private
* @ignore
*/
var _importBaseServices = function() {
var files = fs.readdirSync(__dirname + '/services');
for(var i = 0; i < files.length; i++) {
require(__dirname + '/services/' + files[i]);
}
_modules.unshift('snooze-baselib');
};
/**
* Sets the lib directories to load
* @param {array} loadLibs - Array of directories
* @return {object} Module
*/
var libs = function(loadLibs) {
for(var i = 0; i < loadLibs.length; i++) {
_libs.push(loadLibs[i]);
}
return self;
}
/**
* Records a route in the Module. These will be compiled by
* the RouteManager when wakeup() is called.
*
* @param {string} method - HTTP Method (GET, POST, PUT, DELETE)
* @param {string} path - HTTP Path (ex: /users)
* @param {object} options - Route options
* @return {object} Module
*/
var route = function(method, path, options) {
if(_routeManager.routeExists(method, path)) {
snooze.fatal(new snooze.exceptions.DuplicateRouteException(method, path));
} else {
_routes.push({
method: method,
path: path,
options: options
});
}
return self;
};
/**
* Alias to setPort
*/
var port = function(port) {
return setPort(port);
};
/**
* Sets the server port
*
* @param {int} port - The port to use
* @return {object} Module
*/
var setPort = function(prt) {
_port = prt;
return self;
};
/**
* Sets the ssl config
*
* @param {object} ssl - SSL Config
* @return {object} Module
*/
var ssl = function(ssl) {
_ssl = ssl;
return self;
};
/**
* Sets the post limit of http requests
*
* @param {string} postLimit - Post limit string (default: '50mb')
* @return {object} Module
*/
var setPostLimit = function(postLimit) {
_postLimit = postLimit;
return self;
};
/**
* Adds inject modules
* @ignore
* @param {array} modules - Array of module names
* @return {object} Module
*/
var addModules = function(modules) {
for(var i = 0; i < modules.length; i++) {
_modules.push(modules[i]);
}
};
/**
* For each injected module imports the controllers, services,
* validators, dtos, and daos
*
* @ignore
* @return {object} Module
*/
var importModules = function() {
for(var i = 0; i < _modules.length; i++) {
var _mod = snooze.module(_modules[i]);
log(('Importing ' + _mod.getName()).blue);
var controllers = _mod.getControllers();
var services = _mod.getServices();
var validators = _mod.getValidators();
var dtos = _mod.getDTOs();
var daos = _mod.getDAOs();
for(var k = 0; k < controllers.length; k++) {
var controller = controllers[k];
if(EntityManager.controllerExists(controller.getName()) === false) {
log(('+ ' + controller.getName()).blue);
EntityManager.addController(controller);
}
}
for(var k = 0; k < services.length; k++) {
var service = services[k];
if(EntityManager.serviceExists(service.getName()) === false) {
log(('+ ' + service.getName()).blue);
EntityManager.addService(service);
}
}
for(var k = 0; k < validators.length; k++) {
var validator = validators[k];
if(EntityManager.validatorExists(validator.getName()) === false) {
log(('+ ' + validator.getName()).blue);
EntityManager.addValidator(validator);
}
}
for(var k = 0; k < dtos.length; k++) {
var dto = dtos[k];
if(EntityManager.dtoExists(dto.getName()) === false) {
log(('+ ' + dto.getName()).blue);
EntityManager.addDTO(dto);
}
}
for(var k = 0; k < daos.length; k++) {
var dao = daos[k];
if(EntityManager.daoExists(dao.getName()) === false) {
log(('+ ' + dao.getName()).blue);
EntityManager.addDAO(dao);
}
}
}
};
/**
* Disables logging
*/
var disableLogging = function() {
_logging = false;
};
/**
* Enables logging (enabled by default)
*/
var enableLogging = function() {
_logging = true;
};
/**
* Runs each of the run functions defined
* @ignore
*/
var doRuns = function() {
for(var i = 0; i < _runs.length; i++) {
var _run = _runs[i];
EntityManager.run(_run);
}
};
// Helpers
/**
* Gets the parameter from a function
*
* @param {function} func - A function
* @return {array} Array of parameters
*/
var getParams = function(func) {
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var ARGUMENT_NAMES = /([^\s,]+)/g;
var fnStr = func.toString().replace(STRIP_COMMENTS, '');
var result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(ARGUMENT_NAMES);
if(result === null) {
result = [];
}
return result
};
/**
* Logs a message. Logs will not be printed when logging is disabled.
*
* @param {string} msg - The message to log
*/
var log = function() {
if(_config.silent === false) {
var log = Function.prototype.bind.call(console.log, console);
if(_logging === true) {
log.apply(console, arguments);
}
}
};
/**
* Logs a warning. Logs will not be printed when logging is disabled.
*
* @param {string} msg - The warning to log
*/
var warn = function() {
arguments[0] = (arguments[0]+'').red;
if(_config.silent === false || _config.silent === 'log') {
var log = Function.prototype.bind.call(console.log, console);
if(_logging === true) {
log.apply(console, arguments);
}
}
};
/**
* Kills the node process
*
* @param {int} code - What code to exit the process with
*/
var exit = function(code) {
process.exit(code);
};
/**
* Creates a run function to run after wakeup() has been called.
*
* @param {function} fn - An injection function to run.
* @return {object} Module - The SnoozeJS Module
*/
var run = function(fn) {
_runs.push(fn);
};
var addToRet = {
getName: getName,
getExpress: getExpress,
route: route,
port: port,
setPort: setPort,
ssl: ssl,
wakeup: wakeup,
addModules: addModules,
libs: libs,
log: log,
warn: warn,
disableLogging: disableLogging,
enableLogging: enableLogging,
exit: exit,
init: init,
importBaseServices: _importBaseServices,
isAwake: isAwake,
getRoutes: getRoutes,
getRouteManager: getRouteManager,
getInjectable: getInjectable,
getParams: getParams,
getConfig: getConfig,
inTestMode: inTestMode,
run: run,
requireLibs: requireLibs,
EntityManager: EntityManager,
getController: EntityManager.getController,
getValidator: EntityManager.getValidator,
getService: EntityManager.getService,
getDTO: EntityManager.getDTO,
getDAO: EntityManager.getDAO,
controller: EntityManager.controller,
validator: EntityManager.validator,
service: EntityManager.service,
dto: EntityManager.dto,
dao: EntityManager.dao,
getControllers: EntityManager.getControllers,
getServices: EntityManager.getServices,
getValidators: EntityManager.getValidators,
getDTOs: EntityManager.getDTOs,
getDAOs: EntityManager.getDAOs,
unit: EntityManager.unit,
getUnitTests: EntityManager.getUnits,
defineDTOFromJSON: EntityManager.defineDTOFromJSON,
MockEntityManager: MockEntityManager,
getMockController: MockEntityManager.getController,
getMockValidator: MockEntityManager.getValidator,
getMockService: MockEntityManager.getService,
getMockDTO: MockEntityManager.getDTO,
getMockDAO: MockEntityManager.getDAO,
mockController: MockEntityManager.controller,
mockValidator: MockEntityManager.validator,
mockService: MockEntityManager.service,
mockDto: MockEntityManager.dto,
mockDao: MockEntityManager.dao,
getMockControllers: MockEntityManager.getControllers,
getMockServices: MockEntityManager.getServices,
getMockValidators: MockEntityManager.getValidators,
getMockDTOs: MockEntityManager.getDTOs,
getMockDAOs: MockEntityManager.getDTOs
};
for(var key in addToRet) {
ret[key] = addToRet[key];
}
return ret;
}
module.exports = Module;