diff --git a/README.md b/README.md index 7de45a2..40321e8 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,8 @@ Insert your settings, for example: { "http_port": 8125, "db_port": 6379, - "crypto_key": "[your key]" + "crypto_key": "[your key]", + "session_secret": "[your secret]" } Start the server: diff --git a/create_doc.js b/create_doc.js index 4410f99..f009fba 100644 --- a/create_doc.js +++ b/create_doc.js @@ -1,20 +1,19 @@ /* * # groc Documentation * Create the documentation to be displayed through the webserver. - */ -require('groc').CLI([ - "README.md", - "TODO.js", - "LICENSE.js", - "js/*", - "mod_actions/**/*.js", - "mod_events/**/*.js" - // , - // "rules/*.json" + */ +require('groc').CLI( + [ + "README.md", + "TODO.js", + "LICENSE.js", + "js/*", + "mod_actions/**/*.js", + "mod_events/**/*.js", + "-o./webpages/doc" ], - function(error) { - if (error) { - process.exit(1); - } + function(err) { + if (err) console.error(err); + else console.log('Done!'); } ); diff --git a/js/config.js b/js/config.js index 620d9ab..6a61f64 100644 --- a/js/config.js +++ b/js/config.js @@ -1,78 +1,79 @@ 'use strict'; -/** - * command standard config file loading and pass certain property back to the callback - * @param {String} prop - * @param {function} cb - */ -function fetchProp(prop, cb) { - if(typeof cb === 'function') { - exports.getConfig(null, function(err, data) { - if(!err) cb(null, data[prop]); - else cb(err); - }); +var path = require('path'), log, config; + +exports = module.exports = function(relPath) { + if(typeof relPath !== 'string') relPath = path.join('config', 'config.json'); + loadConfigFile(relPath); +}; + +exports.init = function(args, cb) { + args = args || {}; + if(args.log) log = args.log; + else log = args.log = require('./logging'); + + loadConfigFile(path.join('config', 'config.json')); + + if(typeof cb === 'function') cb(); +}; + + +function loadConfigFile(relPath) { + try { + config = JSON.parse(require('fs').readFileSync(path.resolve(__dirname, '..', relPath))); + if(config && config.http_port && config.db_port + && config.crypto_key && config.session_secret) { + log.print('CF', 'config file loaded successfully!'); + } else { + log.error('CF', new Error('Missing property in config file, requires:\n' + + ' - http_port\n' + + ' - db_port\n' + + ' - crypto_key\n' + + ' - session_secret')); + } + } catch (e) { + e.addInfo = 'no config ready'; + log.error('CF', e); } } /** - * - * @param {String[]} relPath - * @param {Object} cb + * Fetch a property from the configuration + * @param {String} prop */ -exports.getConfig = function(relPath, cb) { - var fs = require('fs'), path = require('path'), log = require('./logging'); - if(!relPath) relPath = path.join('config', 'config.json'); - fs.readFile( - path.resolve(__dirname, '..', relPath), - 'utf8', - function (err, data) { - if (err) { - err.addInfo = 'config file loading'; - if(typeof cb === 'function') cb(err); - else log.error('CF', err); - } else { - try { - var config = JSON.parse(data); - if(!config.http_port || !config.db_port || !config.crypto_key) { - var e = new Error('Missing property, requires:\n' - + ' - http_port\n' - + ' - db_port\n' - + ' - crypto_key'); - if(typeof cb === 'function') cb(e); - else log.error('CF', e); - } else { - if(typeof cb === 'function') cb(null, config); - else log.print('CF', 'config file loaded successfully but pointless since no callback defined...'); - } - } catch(e) { - e.addInfo = 'config file parsing'; - log.error('CF', e); - } - } - } - ); +function fetchProp(prop) { + if(config) return config[prop]; +} + +/** + * Get the HTTP port + */ +exports.getHttpPort = function() { + return fetchProp('http_port'); }; /** - * Command config file loading and retrieve the http port via the callback. - * @param {function} cb + * Get the DB port */ -exports.getHttpPort = function(cb) { - fetchProp('http_port', cb); +exports.getDBPort = function() { + return fetchProp('db_port'); }; /** - * Command config file loading and retrieve the DB port via the callback. - * @param {function} cb + * Get the crypto key */ -exports.getDBPort = function(cb) { - fetchProp('db_port', cb); +exports.getCryptoKey = function() { + return fetchProp('crypto_key'); }; /** - * Command config file loading and retrieve the crypto key via the callback. - * @param {function} cb + * Get the session secret */ -exports.getCryptoKey = function(cb) { - fetchProp('crypto_key', cb); +exports.getSessionSecret = function() { + return fetchProp('session_secret'); }; + +exports.die = function(cb) { + if(typeof cb === 'function') cb(); +}; + \ No newline at end of file diff --git a/js/db_interface.js b/js/db_interface.js index b660d6c..451d30b 100644 --- a/js/db_interface.js +++ b/js/db_interface.js @@ -11,32 +11,32 @@ // 'action_modules' and then stored in the db with the key 'action\_module\_' + ID // (e.g. action\_module\_probinder). 'use strict'; -var redis = require('redis'), - crypto = require('crypto'), - log = require('./logging'), - crypto_key, db; - -/* - * Initializes the DB connection. Requires a port where the DB listens to requests - * and a key that is used for encryptions. - * @param {int} db_port +var log, crypto_key, db, + redis = require('redis'), + crypto = require('crypto'); + +/** + * Initializes the DB connection. Requires a valid configuration file which contains + * a db port and a crypto key. + * @param args {Object} + * @param cb {function} */ -exports.init = function(cb){ - require('./config').getConfig(null, function(err, data) { - if(!err) { - crypto_key = data.crypto_key; - db = redis.createClient(data.db_port); - db.on("error", function (err) { - err.addInfo = 'message from DB'; - log.error('DB', err); - }); - if(typeof cb === 'function') cb(); - } else { - err.addInfo = 'fetching db_port and crypto_key'; - if(typeof cb === 'function') cb(err); - } +exports.init = function(args, cb) { + args = args || {}; + if(args.log) log = args.log; + else log = args.log = require('./logging'); + + var config = require('./config'); + config.init(args); + crypto_key = config.getCryptoKey(); + db = redis.createClient(config.getDBPort()); + db.on("error", function (err) { + err.addInfo = 'message from DB'; + log.error('DB', err); }); + + if(typeof cb === 'function') cb(); }; /** @@ -264,3 +264,19 @@ exports.getRules = function(callback) { getSetRecords('rules', exports.getRule, callback); }; +/** + * + * @param {function} cb + * @param {Object} objUser + */ +exports.storeUser = function(cb, objUser) { + if(objUser && objUser.id) { + db.sadd('users', objUser.id, replyHandler('storing user key ' + objUser.id)); + db.set('user:' + objUser.id, data, replyHandler('storing user properties ' + objUser.id)); + } +}; + +exports.die = function(cb) { + if(typeof cb === 'function') cb(); +}; + \ No newline at end of file diff --git a/js/engine.js b/js/engine.js index bbbda57..4003d35 100644 --- a/js/engine.js +++ b/js/engine.js @@ -1,9 +1,18 @@ 'use strict'; +var log, ml; +exports.init = function(args, cb) { + args = args || {}; + if(args.log) log = args.log; + else log = args.log = require('./logging'); + + ml = require('./module_loader').init(args); + + if(typeof cb === 'function') cb(); +}; + var path = require('path'), cp = require('child_process'), - ml = require('./module_loader'), - log = require('./logging'), poller, db, isRunning = true, qEvents = new (require('./queue')).Queue(); // export queue into redis @@ -17,7 +26,7 @@ var regex = /\$X\.[\w\.\[\]]*/g, // find properties of $X * @param {String} db_port the db port * @param {String} crypto_key the key to be used for encryption on the db, max legnth 256 */ -function init(db_link) { +exports.addDBLink = function(db_link) { db = db_link; loadActions(); poller = cp.fork(path.resolve(__dirname, 'eventpoller')); @@ -25,11 +34,11 @@ function init(db_link) { if(evt.event === 'ep_finished_loading') { eventsLoaded = true; tryToLoadRules(); - } else pushEvent(evt); + } else exports.pushEvent(evt); }); //start to poll the event queue pollQueue(); -} +}; function loadActions() { db.getActionModules(function(err, obj) { @@ -64,7 +73,7 @@ function loadActions() { function tryToLoadRules() { if(eventsLoaded && actionsLoaded) { db.getRules(function(err, obj) { - for(var el in obj) loadRule(JSON.parse(obj[el])); + for(var el in obj) exports.loadRule(JSON.parse(obj[el])); }); } } @@ -73,16 +82,16 @@ function tryToLoadRules() { * Insert an action module into the list of available interfaces. * @param {Object} objModule the action module object */ -function loadActionModule(name, objModule) { +exports.loadActionModule = function(name, objModule) { log.print('EN', 'Action module "' + name + '" loaded'); listActionModules[name] = objModule; -} +}; /** * Insert a rule into the eca rules repository * @param {Object} objRule the rule object */ -function loadRule(objRule) { +exports.loadRule = function(objRule) { //TODO validate rule log.print('EN', 'Loading Rule: ' + objRule.id); if(listRules[objRule.id]) log.print('EN', 'Replacing rule: ' + objRule.id); @@ -94,7 +103,7 @@ function loadRule(objRule) { } catch (err) { log.print('EN', 'Unable to inform poller about new active rule!'); } -} +}; function pollQueue() { if(isRunning) { @@ -110,9 +119,9 @@ function pollQueue() { * Stores correctly posted events in the queue * @param {Object} evt The event object */ -function pushEvent(evt) { +exports.pushEvent = function(evt) { qEvents.enqueue(evt); -} +}; /** * Handles correctly posted events @@ -225,29 +234,26 @@ function preprocessActionArguments(evt, act, res) { } } -function loadEventModule(args, answHandler) { +exports.loadEventModule = function(args, answHandler) { if(args && args.name) { answHandler.answerSuccess('Loading event module ' + args.name + '...'); poller.send('cmd|loadevent|'+args.name); } else if(args) answHandler.answerError(args.name + ' not found'); -} +}; -function loadEventModules(args, answHandler) { +exports.loadEventModules = function(args, answHandler) { answHandler.answerSuccess('Loading event moules...'); poller.send('cmd|loadevents'); -} +}; -function shutDown() { +exports.shutDown = function() { log.print('EN', 'Shutting down Poller and DB Link'); isRunning = false; if(poller) poller.send('cmd|shutdown'); if(db) db.shutDown(); -} +}; -exports.init = init; -exports.loadActionModule = loadActionModule; -exports.loadRule = loadRule; -exports.loadEventModule = loadEventModule; -exports.loadEventModules = loadEventModules; -exports.pushEvent = pushEvent; -exports.shutDown = shutDown; +exports.die = function(cb) { + if(typeof cb === 'function') cb(); +}; + \ No newline at end of file diff --git a/js/eventpoller.js b/js/eventpoller.js index 6ad4d43..b38829b 100644 --- a/js/eventpoller.js +++ b/js/eventpoller.js @@ -2,11 +2,19 @@ 'use strict'; +var log, db, ml; +function init() { + log = require('./logging'); + if(process.argv.length > 2) log(parseInt(process.argv[2]) || 0); + + ml = require('./module_loader').init({ log: log }), + db = require('./db_interface').init({ log: log }, doneInitDB); + + if(typeof cb === 'function') cb(); +}; + var fs = require('fs'), path = require('path'), - log = require('./logging'), - db = require('./db_interface'), - ml = require('./module_loader'), listMessageActions = {}, listAdminCommands = {}, listEventModules = {}, @@ -16,43 +24,41 @@ var fs = require('fs'), eId = 0; //TODO allow different polling intervals (a wrapper together with settimeout per to be polled could be an easy and solution) -function init() { - db.init(function(err) { - if(!err) { +function doneInitDB(err) { + if(!err) { //TODO eventpoller will not load event modules from filesystem, this will be done by // the moduel manager and the eventpoller receives messages about new/updated active rules - db.getEventModules(function(err, obj) { - if(err) log.error('EP', 'retrieving Event Modules from DB!'); - else { - if(!obj) { - log.print('EP', 'No Event Modules found in DB!'); - process.send({ event: 'ep_finished_loading' }); - } else { - var m, semaphore = 0; - for(var el in obj) { - semaphore++; - m = ml.requireFromString(obj[el], el); - db.getEventModuleAuth(el, function(mod) { - return function(err, obj) { - if(--semaphore === 0) process.send({ event: 'ep_finished_loading' }); - if(obj && mod.loadCredentials) mod.loadCredentials(JSON.parse(obj)); - }; - }(m)); - log.print('EP', 'Loading Event Module: ' + el); - listEventModules[el] = m; - } + db.getEventModules(function(err, obj) { + if(err) log.error('EP', 'retrieving Event Modules from DB!'); + else { + if(!obj) { + log.print('EP', 'No Event Modules found in DB!'); + process.send({ event: 'ep_finished_loading' }); + } else { + var m, semaphore = 0; + for(var el in obj) { + semaphore++; + m = ml.requireFromString(obj[el], el); + db.getEventModuleAuth(el, function(mod) { + return function(err, obj) { + if(--semaphore === 0) process.send({ event: 'ep_finished_loading' }); + if(obj && mod.loadCredentials) mod.loadCredentials(JSON.parse(obj)); + }; + }(m)); + log.print('EP', 'Loading Event Module: ' + el); + listEventModules[el] = m; } - initAdminCommands(); - initMessageActions(); } - }); - } else { - err.addInfo = 'eventpoller init failed'; - log.error('EP', err); - } - }); + initAdminCommands(); + initMessageActions(); + } + }); + } else { + err.addInfo = 'eventpoller init failed'; + log.error('EP', err); + } } function initMessageActions() { @@ -150,5 +156,10 @@ function pollLoop() { } } +exports.die = function(cb) { + if(typeof cb === 'function') cb(); +}; + + init(); pollLoop(); \ No newline at end of file diff --git a/js/http_listener.js b/js/http_listener.js index b7e5a72..065d48c 100644 --- a/js/http_listener.js +++ b/js/http_listener.js @@ -1,26 +1,69 @@ // # HTTP Listener // Isso 'use strict'; + +var log, config; +exports.init = function(args, cb) { + args = args || {}; + if(args.log) log = args.log; + else log = args.log = require('./logging'); + + config = require('./config').init(args); + + if(typeof cb === 'function') cb(); +}; + var path = require('path'), express = require('express'), - port = express(), - log = require('./logging'), + app = express(), + RedisStore = require('connect-redis')(express), qs = require('querystring'), adminHandler, eventHandler, server; -function init(http_port, funcAdminHandler, funcEvtHandler) { - if(!http_port || !funcEvtHandler) { +exports.addHandlers = function(funcAdminHandler, funcEvtHandler) { + if(!funcEvtHandler) { log.error('HL', 'ERROR: either port or eventHandler function not defined!'); return; } adminHandler = funcAdminHandler; eventHandler = funcEvtHandler; - port.use('/doc/', express.static(path.resolve(__dirname, '..', 'doc/'))); - port.get('/admin', onAdminCommand); - port.post('/pushEvents', onPushEvent); - server = port.listen(http_port); // inbound event channel - log.print('HL', 'Started listening for http requests on port ' + http_port); -} + +//FIXME this whole webserver requires clean approach together with session handling all over the engine. +//One entry point, from then collecting response contents and one exit point that sends it! + + app.use(express.cookieParser()); + app.use('/doc/', express.static(path.resolve(__dirname, '..', 'webpages', 'doc'))); + app.use('/mobile/', express.static(path.resolve(__dirname, '..', 'webpages', 'mobile'))); + app.use('/rulesforge/', express.static(path.resolve(__dirname, '..', 'webpages', 'rulesforge'))); + app.get('/admin', onAdminCommand); + app.post('/pushEvents', onPushEvent); + var db_port = config.getDBPort(), + sess_sec = config.getSessionSecret(), + http_port = config.getHttpPort(); + if(db_port) { + app.use(express.session({ + store: new RedisStore({ + host: 'localhost', + port: db_port, + db: 2 + // , + // pass: 'RedisPASS' + }), + // FIXME use a secret from config + secret: sess_sec + })); + log.print('HL', 'Added redis DB as session backbone'); + } else { + if(sess_sec) app.use(express.session({secret: sess_sec})); + else { + app.use(express.session({ secret: '#C[>;j`@".TXm2TA;A2Tg)' })); + log.print('HL', 'no session secret found?!'); + } + log.print('HL', 'no session backbone'); + } + if(http_port) server = app.listen(http_port); // inbound event channel + else log.error('HL', new Error('No HTTP port found!?')); +}; function answerHandler(r) { var response = r, hasBeenAnswered = false; @@ -89,8 +132,12 @@ function onPushEvent(request, response) { }); } -exports.init = init; exports.shutDown = function() { log.print('HL', 'Shutting down HTTP listener'); process.exit(); // This is a bit brute force... }; + +exports.die = function(cb) { + if(typeof cb === 'function') cb(); +}; + \ No newline at end of file diff --git a/js/logging.js b/js/logging.js index 180016b..b82c730 100644 --- a/js/logging.js +++ b/js/logging.js @@ -2,7 +2,32 @@ * Logging * ======= * Functions to handle logging and errors. + * + * Valid log methods are: + * + * - 0 standard I/O + * - 1 file + * - 2 silent */ +var logMethods = [ flushToConsole, flushToFile, null], + logMethod = 0, logFile; + +exports = module.exports = function(logMeth) { + if(logMeth) logMethod = parseInt(logMeth) || 0; +}; + +function flush(err, msg) { + if(typeof logMethods[logMethod] === 'function') logMethods[logMethod](err, msg); +} + +function flushToConsole(err, msg) { + if(err) console.error(msg); + else console.log(msg); +} + +function flushToFile(err, msg) { + fs.appendFile('./server.log', msg, function (err) {}); +} // @function print(module, msg) @@ -12,29 +37,48 @@ * @param {String} msg */ exports.print = function(module, msg) { - console.log((new Date()).toISOString() + ' | ' + module + ' | ' + msg); + flush(false, (new Date()).toISOString() + ' | ' + module + ' | ' + msg); }; -// @method error(module, msg) - -/* +/** * Prints a log to stderr. * @param {String} module * @param {Error} err */ -exports.error = function(module, err) { +function printError(module, err, isSevere) { var ts = (new Date()).toISOString() + ' | ', ai = ''; if(typeof err === 'string') { var e = new Error(); - if(module) console.error(ts + module + ' | ERROR AND BAD HANDLING: ' + err + '\n' + e.stack); - else console.error(ts + '!N/A! | ERROR, BAD HANDLING AND NO MODULE NAME: ' + err + '\n' + e.stack); + if(module) flush(true, ts + module + ' | ERROR AND BAD HANDLING: ' + err + '\n' + e.stack); + else flush(true, ts + '!N/A! | ERROR, BAD HANDLING AND NO MODULE NAME: ' + err + '\n' + e.stack); } else if(err) { if(err.addInfo) ai = ' (' + err.addInfo + ')'; if(!err.message) err.message = 'UNKNOWN REASON!\n' + err.stack; - if(module) console.error(ts + module + ' | ERROR'+ai+': ' + err.message + '\n' + err.stack); - else console.error(ts + '!N/A! | ERROR AND NO MODULE NAME'+ai+': ' + err.message + '\n' + err.stack); + if(module) { + var msg = ts + module + ' | ERROR'+ai+': ' + err.message; + if(isSevere) msg += '\n' + err.stack; + flush(true, msg); + } else flush(true, ts + '!N/A! | ERROR AND NO MODULE NAME'+ai+': ' + err.message + '\n' + err.stack); } else { var e = new Error('Unexpected error'); - console.error(e.message + ': \n' + e.stack); + flush(true, e.message + ': \n' + e.stack); } }; + +/** + * Prints a message to stderr. + * @param {String} module + * @param {Error} err + */ +exports.error = function(module, err) { + printError(module, err, false); +}; + +/** + * Prints a message with error stack to stderr + * @param {String} module + * @param {Error} err + */ +exports.severe = function(module, err) { + printError(module, err, true); +}; diff --git a/js/module_loader.js b/js/module_loader.js index ca6f3ac..bcc08a6 100644 --- a/js/module_loader.js +++ b/js/module_loader.js @@ -1,8 +1,17 @@ +'use strict'; + +var log; +exports.init = function(args, cb) { + args = args || {}; + if(args.log) log = args.log; + else log = args.log = require('./logging'); + if(typeof cb === 'function') cb(); +}; + var fs = require('fs'), - path = require('path'), - log = require('./logging'); + path = require('path'); -function requireFromString(src, name, dir) { +exports.requireFromString = function(src, name, dir) { if(!dir) dir = __dirname; //FIXME load modules only into a safe environment with given modules, no access to whole application var id = path.resolve(dir, name, name + '.js'); @@ -16,16 +25,16 @@ function requireFromString(src, name, dir) { // log.error('LM', ' during compilation of ' + name + ': ' + err); } return m.exports; -} +}; -function loadModule(directory, name, callback) { +exports.loadModule = function(directory, name, callback) { try { fs.readFile(path.resolve(__dirname, '..', directory, name, name + '.js'), 'utf8', function (err, data) { if (err) { log.error('LM', 'Loading module file!'); return; } - var mod = requireFromString(data, name, directory); + var mod = exports.requireFromString(data, name, directory); if(mod && fs.existsSync(path.resolve(__dirname, '..', directory, name, 'credentials.json'))) { fs.readFile(path.resolve(__dirname, '..', directory, name, 'credentials.json'), 'utf8', function (err, auth) { if (err) { @@ -44,9 +53,9 @@ function loadModule(directory, name, callback) { } catch(err) { log.error('LM', 'Failed loading module "' + name + '"'); } -} +}; -function loadModules(directory, callback) { +exports.loadModules = function(directory, callback) { fs.readdir(path.resolve(__dirname, '..', directory), function (err, list) { if (err) { log.error('LM', 'loading modules directory: ' + err); @@ -56,14 +65,14 @@ function loadModules(directory, callback) { list.forEach(function (file) { fs.stat(path.resolve(__dirname, '..', directory, file), function (err, stat) { if (stat && stat.isDirectory()) { - loadModule(directory, file, callback); + exports.loadModule(directory, file, callback); } }); }); }); -} - -exports.loadModule = loadModule; -exports.loadModules = loadModules; -exports.requireFromString = requireFromString; +}; +exports.die = function(cb) { + if(typeof cb === 'function') cb(); +}; + diff --git a/js/module_manager.js b/js/module_manager.js index d96feda..2b9cdc4 100644 --- a/js/module_manager.js +++ b/js/module_manager.js @@ -6,17 +6,29 @@ > Event and Action modules are loaded as strings and stored in the database, > then compiled into node modules and and rules */ + +'use strict'; + +var log, ml; +exports.init = function(args, cb) { + args = args || {}; + if(args.log) log = args.log; + else log = args.log = require('./logging'); + + ml = require('./module_loader').init(args); + + if(typeof cb === 'function') cb(); +}; + var fs = require('fs'), path = require('path'), - log = require('./logging'), - ml = require('./module_loader'), db = null, funcLoadAction, funcLoadRule; -function init(db_link, fLoadAction, fLoadRule) { +exports.addHandlers = function(db_link, fLoadAction, fLoadRule) { db = db_link; funcLoadAction = fLoadAction; funcLoadRule = fLoadRule; -} +}; /* # A First Level Header @@ -43,7 +55,7 @@ This is the function documentation @param {Object} [args] the optional arguments @param {String} [args.name] the optional name in the arguments */ -function loadRulesFile(args, answHandler) { +exports.loadRulesFile = function(args, answHandler) { if(!args) args = {}; if(!args.name) args.name = 'rules'; if(!funcLoadRule) log.error('ML', 'no rule loader function available'); @@ -67,7 +79,7 @@ function loadRulesFile(args, answHandler) { } }); } -} +}; /** * @@ -82,19 +94,19 @@ function loadActionCallback(name, data, mod, auth) { if(auth) db.storeActionModuleAuth(name, auth); } -function loadActionModule(args, answHandler) { +exports.loadActionModule = function (args, answHandler) { if(args && args.name) { answHandler.answerSuccess('Loading action module ' + args.name + '...'); ml.loadModule('mod_actions', args.name, loadActionCallback); } -} +}; -function loadActionModules(args, answHandler) { +exports.loadActionModules = function(args, answHandler) { answHandler.answerSuccess('Loading action modules...'); ml.loadModules('mod_actions', loadActionCallback); -} +}; -exports.init = init; -exports.loadRulesFile = loadRulesFile; -exports.loadActionModule = loadActionModule; -exports.loadActionModules = loadActionModules; \ No newline at end of file +exports.die = function(cb) { + if(typeof cb === 'function') cb(); +}; + \ No newline at end of file diff --git a/js/server.js b/js/server.js index 078f536..5b2e948 100644 --- a/js/server.js +++ b/js/server.js @@ -23,25 +23,86 @@ dog's back. > ## This is an H2 in a blockquote */ -var http_listener = require('./http_listener'), - db = require('./db_interface'), - engine = require('./engine'), - mm = require('./module_manager'), - log = require('./logging'), - fs = require('fs'), +//FIXME server should be started via command line arguments http_port and logging level to allow proper testing +var log = require('./logging'), http_port; +log.print('RS', 'STARTING SERVER'); +if(process.argv.length > 2) log(process.argv[2]); +else log.print('RS', 'No log method passed, using stdI/O'); +if(process.argv.length > 3) http_port = parseInt(process.argv[3]); +else log.print('RS', 'No HTTP port passed, using standard port from config file'); + +var fs = require('fs'), path = require('path'), procCmds = { 'die': function() { shutDown(); } - }, - objCmds = { - 'loadrules': mm.loadRulesFile, - 'loadaction': mm.loadActionModule, - 'loadactions': mm.loadActionModules, - 'loadevent': engine.loadEventModule, - 'loadevents': engine.loadEventModules, - 'shutdown': shutDown, - 'restart': null //TODO implement }; + +function handleModuleLoad(cb, msg) { + return function(err) { + if(!err) { + if(typeof cb === 'function') cb(); + log.print('RS', msg + ' initialized successfully'); + } else { + err.addInfo = msg + ' init failed'; + log.error('RS', err); + } + }; +} + +function loadHL() { + http_listener = require('./http_listener').init(log, + handleModuleLoad(loadEN, 'http listener') + ); +} + +function loadEN(cb) { + engine = require('./engine').init(log, + handleModuleLoad(loadMM, 'engine') + ); +} + +function loadMM(cb) { + mm = require('./module_manager').init(log, + handleModuleLoad(loadDB, 'module manager') + ); +} + +function loadDB(cb) { + db = require('./db_interface').init(log, + handleModuleLoad(doneInitDB, 'db interface init failed') + ); +} + +function doneInitDB(err) { + if(!err) { + objCmds = { + 'loadrules': mm.loadRulesFile, + 'loadaction': mm.loadActionModule, + 'loadactions': mm.loadActionModules, + 'loadevent': engine.loadEventModule, + 'loadevents': engine.loadEventModules, + 'shutdown': shutDown + }; + //FIXME engine requires db to be finished with init... + engine.addDBLink(db); + log.print('RS', 'Initialzing http listener'); + http_listener.addHandlers(handleAdminCommands, engine.pushEvent); + log.print('RS', 'Initialzing module manager'); + mm.addHandlers(db, engine.loadActionModule, engine.loadRule); + } + else { + err.addInfo = err.message; + err.message = 'Not Starting engine!'; + log.error(err); + } +} + +(function() { + loadHL(); + // engine = require('./engine').init(log), + // mm = require('./module_manager').init(log), + // db = require('./db_interface').init(log, doneInitDB), //TODO have a close lok at this special case +})(); function handleAdminCommands(args, answHandler) { if(args && args.cmd) { @@ -74,22 +135,21 @@ process.on('message', function(cmd) { }); -log.print('RS', 'STARTING SERVER'); log.print('RS', 'Initialzing DB'); //FIXME initialization of all modules should depend on one after the other // in a transaction style manner -db.init(function(err) { - if(!err) { - engine.init(db); - log.print('RS', 'Initialzing http listener'); - //FIXME http_port shouldn't be passed here we can load it inside the listener via the new config.js module - http_listener.init(null/*config.http_port*/, handleAdminCommands, engine.pushEvent); - log.print('RS', 'Initialzing module manager'); - mm.init(db, engine.loadActionModule, engine.loadRule); - } - else { - err.addInfo = err.message; - err.message = 'Not Starting engine!'; - log.error(err); - } -}); + +/* + * FIXME + * - new consequent error and callback handling starts to manifest in the code, + * still a lot of work required! + * - unit testing seems a bit more complex because of the dependencies, this + * has to be started before solving above point because it will give hints to + * better loose coupling + */ + +/* + * FIXME ALL MODULES NEED TO FOLLOW CONVENTION TO ALLOW PROPER MODULE HANDLING: + * - init(args, cb) + * - die() + */ diff --git a/js/users.js b/js/users.js new file mode 100644 index 0000000..718a3c0 --- /dev/null +++ b/js/users.js @@ -0,0 +1,87 @@ +'use strict'; + +var log; +exports.init = function(args, cb) { + args = args || {}; + if(args.log) log = args.log; + else log = args.log = require('./logging'); + if(typeof cb === 'function') cb(); +}; + +var objCmds = { + addUser: addUser, + getUser: getUser, + delUser: delUser, + addRule: addRule, + getRules: getRules, + delRule: delRule +}; + +exports.handleCommand = function(args, cb) { + if(!args.cmd) { + var e = new Error('No command defined!'); + if(typeof cb === 'function') cb(e); + else log.error('US', e); + } else { + objCmds[args.cmd](args, cb); + } +}; + +/** + * + * @param {Object} args + * @param {function} cb + */ +function addUser(args, cb) { + +} + +/** + * + * @param {Object} args + * @param {function} cb + */ +function getUser(args, cb) { + +} + +/** + * + * @param {Object} args + * @param {function} cb + */ +function delUser(args, cb) { + +} + +/** + * + * @param {Object} args + * @param {function} cb + */ +function addRule(args, cb) { + +} + +/** + * + * @param {Object} args + * @param {function} cb + */ +function getRule(args, cb) { + +} + +/** + * + * @param {Object} args + * @param {function} cb + */ +function delRule(args, cb) { + +} + +exports.die = function(cb) { + if(typeof cb === 'function') cb(); +}; + \ No newline at end of file diff --git a/package.json b/package.json index b47fff2..6e03f62 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "url" : "https://github.com/dominicbosch/webapi-eca.git" }, "dependencies": { + "connect-redis": "1.4.6", "express": "3.4.0", "groc": "0.6.1", "needle": "0.6.1", diff --git a/testing/mod_config.js b/testing/mod_config.js new file mode 100644 index 0000000..7d6ba5d --- /dev/null +++ b/testing/mod_config.js @@ -0,0 +1,270 @@ + +exports.testUnit_DB = function(test){ + test.ok(false, "needs implementation"); + test.done(); +}; + +// // # DB Interface +// // Handles the connection to the database and provides functionalities for +// // event/action modules, rules and the encrypted storing of authentication tokens. +// +// // ## General +// // General functionality as a wrapper for the module holds initialization, +// // encryption/decryption, the retrieval of modules and shut down. +// // Modules of the same group, e.g. action modules are registered in an unordered +// // set in the database, from where they can be retrieved again. For example a new +// // action module has its ID (e.g 'probinder') first registered in the set +// // 'action_modules' and then stored in the db with the key 'action\_module\_' + ID +// // (e.g. action\_module\_probinder). +// 'use strict'; +// var redis = require('redis'), + // crypto = require('crypto'), + // log = require('./logging'), + // crypto_key, db; +// +// +// // @function init() +// +// /* + // * Initializes the DB connection. Requires a port where the DB listens to requests + // * and a key that is used for encryptions. + // * @param {int} db_port + // */ +// exports.init = function(db_port, key, cbDone){ + // if(!db_port || !key) { + // log.error('DB', 'No DB port or cipher key defined!'); + // return null; + // } + // crypto_key = key; + // db = redis.createClient(db_port); + // db.on("error", function (err) { + // log.error('DB', ' Message from DB: ' + err); + // }); + // if(cbDone) cbDone(); +// }; +// +// /** + // * ### encrypt + // */ +// function encrypt(plainText) { + // if(!plainText) return null; + // try { + // var enciph = crypto.createCipher('aes-256-cbc', crypto_key); + // var et = enciph.update(plainText, 'utf8', 'base64') + enciph.final('base64'); + // log.print('DB', 'Encrypted credentials into: ' + et); + // return et; + // } catch (err) { + // log.error('DB', 'in encrypting: ' + err); + // return null; + // } +// } +// +// /** + // * ### decrypt + // */ +// function decrypt(crypticText) { + // if(!crypticText) return null; + // try { + // var deciph = crypto.createDecipher('aes-256-cbc', crypto_key); + // return deciph.update(crypticText, 'base64', 'utf8') + deciph.final('utf8'); + // } catch (err) { + // log.error('DB', 'in decrypting: ' + err); + // return null; + // } +// } +// +// /** + // * ### replyHandler + // * Abstraction answer handling for simple information replies from the DB. + // * @param {String} action the action to be displayed in the output string. + // */ +// function replyHandler(action) { + // return function(err, reply) { + // if(err) log.error('DB', ' during "' + action + '": ' + err); + // else log.print('DB', action + ': ' + reply); + // }; +// } +// +// /** + // * ### getSetRecords + // * The general structure for modules is that the key is stored in a set. + // * By fetching all set entries we can then fetch all modules, which is + // * automated in this function. + // * + // * @param {String} set the set name how it is stored in the DB + // * @param {function} funcSingle the function that fetches single entries from the DB + // * @param {function} callback the function to be called on success or error, receives + // * arguments (err, obj) + // */ +// function getSetRecords(set, funcSingle, callback) { + // db.smembers(set, function(err, reply) { + // if(err) log.error('DB', 'fetching ' + set + ': ' + err); + // else { + // if(reply.length === 0) { + // callback(null, null); + // } else { + // var semaphore = reply.length, objReplies = {}; + // setTimeout(function() { + // if(semaphore > 0) { + // callback('Timeout fetching ' + set, null); + // } + // }, 1000); + // for(var i = 0; i < reply.length; i++){ + // funcSingle(reply[i], function(prop) { + // return function(err, reply) { + // if(err) log.error('DB', ' fetching single element: ' + prop); + // else { + // objReplies[prop] = reply; + // if(--semaphore === 0) callback(null, objReplies); + // } + // }; + // }(reply[i])); + // } + // } + // } + // }); +// } +// +// // @method shutDown() +// +// // Shuts down the db link. +// exports.shutDown = function() { db.quit(); }; +// +// // ## Action Modules +// +// /** + // * ### storeActionModule + // * Store a string representation of an action module in the DB. + // * @param {String} id the unique identifier of the module + // * @param {String} data the string representation + // */ +// exports.storeActionModule = function(id, data) { + // db.sadd('action_modules', id, replyHandler('storing action module key ' + id)); + // db.set('action_module_' + id, data, replyHandler('storing action module ' + id)); +// }; +// +// /** + // * ### getActionModule(id, callback) + // * Query the DB for an action module. + // * @param {String} id the module id + // * @param {function} callback the callback to receive the answer (err, obj) + // */ +// exports.getActionModule = function(id, callback) { + // if(callback) db.get('action_module_' + id, callback); +// }; +// +// /** + // * ### getActionModules(callback) + // * Fetch all action modules. + // * @param {function} callback the callback to receive the answer (err, obj) + // */ +// exports.getActionModules = function(callback) { + // getSetRecords('action_modules', exports.getActionModule, callback); +// }; +// +// /** + // * storeActionModuleAuth(id, data) + // * Store a string representation of the authentication parameters for an action module. + // * @param {String} id the unique identifier of the module + // * @param {String} data the string representation + // */ +// exports.storeActionModuleAuth = function(id, data) { + // if(data) { + // db.sadd('action_modules_auth', id, replyHandler('storing action module auth key ' + id)); + // db.set('action_module_' + id +'_auth', encrypt(data), replyHandler('storing action module auth ' + id)); + // } +// }; +// +// /** + // * ### getActionModuleAuth(id, callback) + // * Query the DB for an action module authentication token. + // * @param {String} id the module id + // * @param {function} callback the callback to receive the answer (err, obj) + // */ +// exports.getActionModuleAuth = function(id, callback) { + // if(callback) db.get('action_module_' + id + '_auth', function(err, txt) { callback(err, decrypt(txt)); }); +// }; +// +// // ## Event Modules +// +// /** + // * ### storeEventModule(id, data) + // * Store a string representation of an event module in the DB. + // * @param {String} id the unique identifier of the module + // * @param {String} data the string representation + // */ +// exports.storeEventModule = function(id, data) { + // db.sadd('event_modules', id, replyHandler('storing event module key ' + id)); + // db.set('event_module_' + id, data, replyHandler('storing event module ' + id)); +// }; +// +// /** + // * ### getEventModule(id, callback) + // * Query the DB for an event module. + // * @param {String} id the module id + // * @param {function} callback the callback to receive the answer (err, obj) + // */ +// exports.getEventModule = function(id, callback) { + // if(callback) db.get('event_module_' + id, callback); +// }; +// +// /** + // * ### getEventModules(callback) + // * Fetch all event modules. + // * @param {function} callback the callback that receives the arguments (err, obj) + // */ +// exports.getEventModules = function(callback) { + // getSetRecords('event_modules', exports.getEventModule, callback); +// }; +// +// /** + // * ### storeEventModuleAuth(id, data) + // * Store a string representation of he authentication parameters for an event module. + // * @param {String} id the unique identifier of the module + // * @param {String} data the string representation + // */ +// exports.storeEventModuleAuth = function(id, data) { + // if(data) { + // db.sadd('event_modules_auth', id, replyHandler('storing event module auth key ' + id)); + // db.set('event_module_' + id +'_auth', encrypt(data), replyHandler('storing event module auth ' + id)); + // } +// }; +// +// // @method getEventModuleAuth(id, callback) +// +// // Query the DB for an event module authentication token. +// // @param {String} id the module id +// // @param {function} callback the callback to receive the answer (err, obj) +// exports.getEventModuleAuth = function(id, callback) { + // if(callback) db.get('event_module_' + id +'_auth', function(err, txt) { callback(err, decrypt(txt)); }); +// }; +// +// // ## Rules +// +// // @method storeRule(id, data) +// +// // Store a string representation of a rule in the DB. +// // @param {String} id the unique identifier of the rule +// // @param {String} data the string representation +// exports.storeRule = function(id, data) { + // db.sadd('rules', id, replyHandler('storing rule key ' + id)); + // db.set('rule_' + id, data, replyHandler('storing rule ' + id)); +// }; +// +// // @method getRule(id, callback) +// +// // Query the DB for a rule. +// // @param {String} id the rule id +// // @param {function} callback the callback to receive the answer (err, obj) +// exports.getRule = function(id, callback) { + // db.get('rule_' + id, callback); +// }; +// +// // @method getRules(callback) +// +// // Fetch all rules from the database. +// // @param {function} callback the callback to receive the answer (err, obj) +// exports.getRules = function(callback) { + // getSetRecords('rules', exports.getRule, callback); +// }; +// diff --git a/testing/mod_db_interface.js b/testing/mod_db_interface.js index 747bee6..7d6ba5d 100644 --- a/testing/mod_db_interface.js +++ b/testing/mod_db_interface.js @@ -1,6 +1,6 @@ exports.testUnit_DB = function(test){ - test.ok(true, "db"); + test.ok(false, "needs implementation"); test.done(); }; diff --git a/testing/mod_engine.js b/testing/mod_engine.js index 72c7fb4..d903943 100644 --- a/testing/mod_engine.js +++ b/testing/mod_engine.js @@ -24,7 +24,7 @@ exports.group = { }; exports.testUnit_Engine = function(test){ - test.ok(true, "engine"); + test.ok(false, "needs implementation"); test.done(); }; diff --git a/testing/mod_eventpoller.js b/testing/mod_eventpoller.js index 32a20e7..350dfa8 100644 --- a/testing/mod_eventpoller.js +++ b/testing/mod_eventpoller.js @@ -1,6 +1,6 @@ exports.testUnit_EventPoller = function(test){ - test.ok(true, "ep"); + test.ok(false, "needs implementation"); test.done(); }; diff --git a/testing/mod_http_listener.js b/testing/mod_http_listener.js index 614cbbd..11f48bf 100644 --- a/testing/mod_http_listener.js +++ b/testing/mod_http_listener.js @@ -1,6 +1,6 @@ exports.testUnit_HL = function(test){ - test.ok(true, "hl"); + test.ok(false, "needs implementation"); test.done(); }; diff --git a/testing/mod_logging.js b/testing/mod_logging.js index f444277..13919fc 100644 --- a/testing/mod_logging.js +++ b/testing/mod_logging.js @@ -1,6 +1,6 @@ exports.testUnit_LOG = function(test){ - test.ok(true, "log"); + test.ok(false, "needs implementation"); test.done(); }; diff --git a/testing/mod_module_loader.js b/testing/mod_module_loader.js index d7aa65d..2793d87 100644 --- a/testing/mod_module_loader.js +++ b/testing/mod_module_loader.js @@ -1,6 +1,6 @@ exports.testUnit_ML = function(test){ - test.ok(true, "ml"); + test.ok(false, "needs implementation"); test.done(); }; diff --git a/testing/mod_module_manager.js b/testing/mod_module_manager.js index 622ab7e..1dc5eb9 100644 --- a/testing/mod_module_manager.js +++ b/testing/mod_module_manager.js @@ -1,6 +1,6 @@ exports.testUnit_MM = function(test){ - test.ok(true, "mm"); + test.ok(false, "needs implementation"); test.done(); }; diff --git a/testing/mod_server.js b/testing/mod_server.js index 43bbb07..e03e13a 100644 --- a/testing/mod_server.js +++ b/testing/mod_server.js @@ -1,11 +1,12 @@ var path = require('path'); //FIXME handle EADDR in use! exports.setUp = function(cb) { - this.srv = require('child_process').fork(path.resolve(__dirname, '..', 'js', 'server')); + this.srv = require('child_process').fork(path.resolve(__dirname, '..', 'js', 'server'), ['2']); cb(); }; exports.testUnit_SRV = function(test){ + test.ok(false, "needs implementation"); setTimeout( function() { diff --git a/testing/mod_user.js b/testing/mod_user.js new file mode 100644 index 0000000..5123694 --- /dev/null +++ b/testing/mod_user.js @@ -0,0 +1,9 @@ + +exports.setUp = function() { + this.mod = require('./user'); +}; + +exports.addUser = function() { + test.ok(false, "needs implementation"); + +}; diff --git a/testing/unit_integration.js b/testing/unit_integration.js index 2beb5d5..c18b28d 100644 --- a/testing/unit_integration.js +++ b/testing/unit_integration.js @@ -1,5 +1,7 @@ exports.testUnitIntegration = function(test){ - test.ok(true, "unit integration"); + test.ok(false, "needs implementation"); test.done(); }; +//TODO malicious module has to be loaded only for testing, the error +// it causes need to be verified and in the end the module need to be deleted again \ No newline at end of file diff --git a/testing/whole_system.js b/testing/whole_system.js index 969d34e..175e420 100644 --- a/testing/whole_system.js +++ b/testing/whole_system.js @@ -1,5 +1,18 @@ +var path = require('path'); +//FIXME handle EADDR in use! +exports.setUp = function(cb) { + this.srv = require('child_process').fork(path.resolve(__dirname, '..', 'js', 'server'), ['2']); + cb(); +}; exports.testSystem = function(test){ - test.ok(true, "system"); + + test.ok(false, "needs implementation"); test.done(); }; + +exports.tearDown = function(cb) { + this.srv.send('die'); + this.srv = null; + cb(); +}; \ No newline at end of file diff --git a/webpages/mobile/index.html b/webpages/mobile/index.html new file mode 100644 index 0000000..a63b156 --- /dev/null +++ b/webpages/mobile/index.html @@ -0,0 +1,8 @@ + + + Mobile Page + + +

Mobile Page

+ + \ No newline at end of file diff --git a/webpages/rulesforge/index.html b/webpages/rulesforge/index.html new file mode 100644 index 0000000..5f50f37 --- /dev/null +++ b/webpages/rulesforge/index.html @@ -0,0 +1,8 @@ + + + Rules Forge + + +

Rules Forge

+ + \ No newline at end of file