mirror of
https://github.com/Hopiu/webapi-eca.git
synced 2026-04-23 16:14:53 +00:00
- 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
This commit is contained in:
parent
d01b40d5a2
commit
b743a880e4
7 changed files with 284 additions and 196 deletions
38
README.md
38
README.md
|
|
@ -54,29 +54,29 @@ Create the doc *(to be accessed via the webserver, e.g.: localhost:8125/doc/)*:
|
|||
|
||||
_
|
||||
|
||||
## TODO
|
||||
|
||||
|
||||
* [ ] Redis queue
|
||||
* [ ] user handling (personal credentials)
|
||||
* [ ] security in terms of users (private key, login)
|
||||
* [ ] vm for modules, only give few libraries (no fs!)
|
||||
* [ ] rules generator (provide webpage that is used to create rules dependent on the existing modues)
|
||||
* [ ] geo location module, test on smartphone.
|
||||
TODO
|
||||
----
|
||||
|
||||
* Redis queue
|
||||
* user handling (personal credentials)
|
||||
* security in terms of users (private key, login)
|
||||
* vm for modules, only give few libraries (no fs!)
|
||||
* rules generator (provide webpage that is used to create rules dependent on the existing modues)
|
||||
* geo location module, test on smartphone.
|
||||
|
||||
_
|
||||
|
||||
## TODO per module
|
||||
|
||||
TODO per module
|
||||
---------------
|
||||
|
||||
Testing | clean documentation | Clean error handling (Especially in loading of modules and their credentials):
|
||||
|
||||
* [ ] DB Interface
|
||||
* [ ] Engine
|
||||
* [ ] Event Poller
|
||||
* [ ] HTTP Listener
|
||||
* [ ] Logging
|
||||
* [ ] Module Loader
|
||||
* [ ] Module Manager
|
||||
* [ ] Server
|
||||
* DB Interface
|
||||
* Engine
|
||||
* Event Poller
|
||||
* HTTP Listener
|
||||
* Logging
|
||||
* Module Loader
|
||||
* Module Manager
|
||||
* Server
|
||||
|
||||
|
|
|
|||
78
js/config.js
Normal file
78
js/config.js
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
'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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {String[]} relPath
|
||||
* @param {Object} cb
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Command config file loading and retrieve the http port via the callback.
|
||||
* @param {function} cb
|
||||
*/
|
||||
exports.getHttpPort = function(cb) {
|
||||
fetchProp('http_port', cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Command config file loading and retrieve the DB port via the callback.
|
||||
* @param {function} cb
|
||||
*/
|
||||
exports.getDBPort = function(cb) {
|
||||
fetchProp('db_port', cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Command config file loading and retrieve the crypto key via the callback.
|
||||
* @param {function} cb
|
||||
*/
|
||||
exports.getCryptoKey = function(cb) {
|
||||
fetchProp('crypto_key', cb);
|
||||
};
|
||||
|
|
@ -17,24 +17,26 @@ var redis = require('redis'),
|
|||
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);
|
||||
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);
|
||||
}
|
||||
});
|
||||
if(cbDone) cbDone();
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ 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, db_port, crypto_key) {
|
||||
function init(db_link) {
|
||||
db = db_link;
|
||||
loadActions();
|
||||
poller = cp.fork(path.resolve(__dirname, 'eventpoller'), [db_port, crypto_key]);
|
||||
poller = cp.fork(path.resolve(__dirname, 'eventpoller'));
|
||||
poller.on('message', function(evt) {
|
||||
if(evt.event === 'ep_finished_loading') {
|
||||
eventsLoaded = true;
|
||||
|
|
|
|||
|
|
@ -2,145 +2,153 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
if(process.argv.length < 3) {
|
||||
log.error('EP', 'No DB port defined! Not starting poller...');
|
||||
} else {
|
||||
(function() {
|
||||
var fs = require('fs'),
|
||||
path = require('path'),
|
||||
log = require('./logging'),
|
||||
db = require('./db_interface'),
|
||||
ml = require('./module_loader'),
|
||||
listMessageActions = {},
|
||||
listAdminCommands = {},
|
||||
listEventModules = {},
|
||||
listPoll = {}, //TODO this will change in the future because it could have
|
||||
//several parameterized (user-specific) instances of each event module
|
||||
isRunning = true,
|
||||
eId = 0;
|
||||
//TODO allow different polling intervals (a wrapper together with settimeout per to be polled could be an easy and solution)
|
||||
|
||||
db.init(process.argv[2], process.argv[3]);
|
||||
|
||||
//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;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
listMessageActions['event'] = function(args) {
|
||||
var prop = args[1], arrModule = prop.split('->');
|
||||
// var arrModule = obj.module.split('->');
|
||||
if(arrModule.length > 1){
|
||||
var module = listEventModules[arrModule[0]];
|
||||
for(var i = 1; i < arrModule.length; i++) {
|
||||
if(module) module = module[arrModule[i]];
|
||||
}
|
||||
if(module) {
|
||||
log.print('EP', 'Found active event module "' + prop + '", adding it to polling list');
|
||||
//FIXME change this to [module][prop] = module; because like this identical properties get overwritten
|
||||
// also add some on a per user basis information because this should go into a user context for the users
|
||||
// that sat up this rule!
|
||||
listPoll[prop] = module;
|
||||
} else {
|
||||
log.print('EP', 'No property "' + prop + '" found');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
listAdminCommands['loadevent'] = function(args) {
|
||||
ml.loadModule('mod_events', args[2], loadEventCallback);
|
||||
};
|
||||
|
||||
listAdminCommands['loadevents'] = function(args) {
|
||||
ml.loadModules('mod_events', loadEventCallback);
|
||||
};
|
||||
|
||||
listAdminCommands['shutdown'] = function(args) {
|
||||
log.print('EP', 'Shutting down DB Link');
|
||||
isRunning = false;
|
||||
db.shutDown();
|
||||
};
|
||||
|
||||
//TODO this goes into module_manager, this will receive notification about
|
||||
// new loaded/stored event modules and fetch them from the db
|
||||
listMessageActions['cmd'] = function(args) {
|
||||
var func = listAdminCommands[args[1]];
|
||||
if(typeof(func) === 'function') func(args);
|
||||
};
|
||||
|
||||
process.on('message', function(strProps) {
|
||||
var arrProps = strProps.split('|');
|
||||
if(arrProps.length < 2) log.error('EP', 'too few parameter in message!');
|
||||
else {
|
||||
var func = listMessageActions[arrProps[0]];
|
||||
if(func) func(arrProps);
|
||||
}
|
||||
});
|
||||
function loadEventCallback(name, data, mod, auth) {
|
||||
db.storeEventModule(name, data); // store module in db
|
||||
if(auth) db.storeEventModuleAuth(name, auth);
|
||||
listEventModules[name] = mod; // store compiled module for polling
|
||||
}
|
||||
|
||||
function checkRemotes() {
|
||||
for(var prop in listPoll) {
|
||||
try {
|
||||
listPoll[prop](
|
||||
/*
|
||||
* define and immediately call anonymous function with param prop.
|
||||
* This places the value of prop into the context of the callback
|
||||
* and thus doesn't change when the for loop keeps iterating over listPoll
|
||||
*/
|
||||
(function(p) {
|
||||
return function(err, obj) {
|
||||
if(err) {
|
||||
err.additionalInfo = 'module: ' + p;
|
||||
log.error('EP', err);
|
||||
} else {
|
||||
process.send({
|
||||
event: p,
|
||||
eventid: 'polled_' + eId++,
|
||||
data: obj
|
||||
});
|
||||
}
|
||||
};
|
||||
})(prop)
|
||||
);
|
||||
} catch (e) {
|
||||
log.error('EP', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pollLoop() {
|
||||
if(isRunning) {
|
||||
checkRemotes();
|
||||
setTimeout(pollLoop, 10000);
|
||||
}
|
||||
}
|
||||
var fs = require('fs'),
|
||||
path = require('path'),
|
||||
log = require('./logging'),
|
||||
db = require('./db_interface'),
|
||||
ml = require('./module_loader'),
|
||||
listMessageActions = {},
|
||||
listAdminCommands = {},
|
||||
listEventModules = {},
|
||||
listPoll = {}, //TODO this will change in the future because it could have
|
||||
//several parameterized (user-specific) instances of each event module
|
||||
isRunning = true,
|
||||
eId = 0;
|
||||
//TODO allow different polling intervals (a wrapper together with settimeout per to be polled could be an easy and solution)
|
||||
|
||||
pollLoop();
|
||||
})();
|
||||
}
|
||||
function init() {
|
||||
db.init(function(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;
|
||||
}
|
||||
}
|
||||
initAdminCommands();
|
||||
initMessageActions();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
err.addInfo = 'eventpoller init failed';
|
||||
log.error('EP', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initMessageActions() {
|
||||
listMessageActions['event'] = function(args) {
|
||||
var prop = args[1], arrModule = prop.split('->');
|
||||
// var arrModule = obj.module.split('->');
|
||||
if(arrModule.length > 1){
|
||||
var module = listEventModules[arrModule[0]];
|
||||
for(var i = 1; i < arrModule.length; i++) {
|
||||
if(module) module = module[arrModule[i]];
|
||||
}
|
||||
if(module) {
|
||||
log.print('EP', 'Found active event module "' + prop + '", adding it to polling list');
|
||||
//FIXME change this to [module][prop] = module; because like this identical properties get overwritten
|
||||
// also add some on a per user basis information because this should go into a user context for the users
|
||||
// that sat up this rule!
|
||||
listPoll[prop] = module;
|
||||
} else {
|
||||
log.print('EP', 'No property "' + prop + '" found');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//TODO this goes into module_manager, this will receive notification about
|
||||
// new loaded/stored event modules and fetch them from the db
|
||||
listMessageActions['cmd'] = function(args) {
|
||||
var func = listAdminCommands[args[1]];
|
||||
if(typeof(func) === 'function') func(args);
|
||||
};
|
||||
|
||||
process.on('message', function(strProps) {
|
||||
var arrProps = strProps.split('|');
|
||||
if(arrProps.length < 2) log.error('EP', 'too few parameter in message!');
|
||||
else {
|
||||
var func = listMessageActions[arrProps[0]];
|
||||
if(func) func(arrProps);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initAdminCommands() {
|
||||
listAdminCommands['loadevent'] = function(args) {
|
||||
ml.loadModule('mod_events', args[2], loadEventCallback);
|
||||
};
|
||||
listAdminCommands['loadevents'] = function(args) {
|
||||
ml.loadModules('mod_events', loadEventCallback);
|
||||
};
|
||||
listAdminCommands['shutdown'] = function(args) {
|
||||
log.print('EP', 'Shutting down DB Link');
|
||||
isRunning = false;
|
||||
db.shutDown();
|
||||
};
|
||||
}
|
||||
|
||||
function loadEventCallback(name, data, mod, auth) {
|
||||
db.storeEventModule(name, data); // store module in db
|
||||
if(auth) db.storeEventModuleAuth(name, auth);
|
||||
listEventModules[name] = mod; // store compiled module for polling
|
||||
}
|
||||
|
||||
function checkRemotes() {
|
||||
for(var prop in listPoll) {
|
||||
try {
|
||||
listPoll[prop](
|
||||
/*
|
||||
* define and immediately call anonymous function with param prop.
|
||||
* This places the value of prop into the context of the callback
|
||||
* and thus doesn't change when the for loop keeps iterating over listPoll
|
||||
*/
|
||||
(function(p) {
|
||||
return function(err, obj) {
|
||||
if(err) {
|
||||
err.additionalInfo = 'module: ' + p;
|
||||
log.error('EP', err);
|
||||
} else {
|
||||
process.send({
|
||||
event: p,
|
||||
eventid: 'polled_' + eId++,
|
||||
data: obj
|
||||
});
|
||||
}
|
||||
};
|
||||
})(prop)
|
||||
);
|
||||
} catch (e) {
|
||||
log.error('EP', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pollLoop() {
|
||||
if(isRunning) {
|
||||
checkRemotes();
|
||||
setTimeout(pollLoop, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
pollLoop();
|
||||
|
|
@ -31,7 +31,7 @@ exports.error = function(module, err) {
|
|||
} 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);
|
||||
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);
|
||||
} else {
|
||||
var e = new Error('Unexpected error');
|
||||
|
|
|
|||
42
js/server.js
42
js/server.js
|
|
@ -67,29 +67,29 @@ function shutDown(args, answHandler) {
|
|||
if(http_listener) http_listener.shutDown();
|
||||
}
|
||||
|
||||
fs.readFile(path.resolve(__dirname, '..', 'config', 'config.json'), 'utf8', function (err, data) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
log.error('RS', 'Loading config file');
|
||||
return;
|
||||
}
|
||||
var config = JSON.parse(data);
|
||||
if(!config.http_port || !config.db_port || !config.crypto_key) {
|
||||
log.error('RS', new Error('you forgot to define either http_port, db_port, crypto_key, or even all of them!'));
|
||||
} else {
|
||||
log.print('RS', 'Initialzing DB');
|
||||
db.init(config.db_port, config.crypto_key, function() {
|
||||
engine.init(db, config.db_port, config.crypto_key);
|
||||
});
|
||||
log.print('RS', 'Initialzing http listener');
|
||||
http_listener.init(config.http_port, handleAdminCommands, engine.pushEvent);
|
||||
log.print('RS', 'Initialzing module manager');
|
||||
mm.init(db, engine.loadActionModule, engine.loadRule);
|
||||
}
|
||||
});
|
||||
|
||||
// Send message
|
||||
process.on('message', function(cmd) {
|
||||
if(typeof procCmds[cmd] === 'function') procCmds[cmd]();
|
||||
else console.error('err with command');
|
||||
});
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue