mirror of
https://github.com/Hopiu/webapi-eca.git
synced 2026-05-23 14:25:54 +00:00
coffee-script preparations
This commit is contained in:
parent
e3fb282dd3
commit
3ab28e31f1
19 changed files with 1494 additions and 2 deletions
|
|
@ -12,6 +12,13 @@ README: webapi-eca
|
||||||
|
|
||||||
Getting started
|
Getting started
|
||||||
---------------
|
---------------
|
||||||
|
Prerequisites:
|
||||||
|
|
||||||
|
- node.js & npm (find it [here](http://nodejs.org/))
|
||||||
|
- *(optional) coffee, if you want to compile from coffee sources:*
|
||||||
|
|
||||||
|
sudo npm -g install coffee-script
|
||||||
|
|
||||||
Clone project:
|
Clone project:
|
||||||
|
|
||||||
git clone https://github.com/dominicbosch/webapi-eca.git
|
git clone https://github.com/dominicbosch/webapi-eca.git
|
||||||
|
|
|
||||||
24
coffee/server.coffee
Normal file
24
coffee/server.coffee
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
|
||||||
|
###
|
||||||
|
Rules Server
|
||||||
|
============
|
||||||
|
This is the main module that is used to run the whole server:
|
||||||
|
|
||||||
|
node server [log_type http_port]
|
||||||
|
|
||||||
|
Valid `log_type`'s are:
|
||||||
|
|
||||||
|
- `0`: standard I/O output (default)
|
||||||
|
- `1`: log file (server.log)
|
||||||
|
- `2`: silent
|
||||||
|
|
||||||
|
`http_port` can be set to use another port, than defined in the
|
||||||
|
[config](config.html) file, to listen to, e.g. used by the test suite.
|
||||||
|
|
||||||
|
###
|
||||||
|
root = exports ? this
|
||||||
|
root.foo = -> 'Hello World'
|
||||||
|
###
|
||||||
|
My comments will show up here
|
||||||
|
|
||||||
|
###
|
||||||
1
coffee/test.coffee
Normal file
1
coffee/test.coffee
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
efew = require 'fs'
|
||||||
3
compile_coffee.sh
Executable file
3
compile_coffee.sh
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
echo "Started listening on file changes to compile them!..."
|
||||||
|
coffee -wc -o $DIR/js-coffee $DIR/coffee/
|
||||||
|
|
@ -7,7 +7,7 @@ require('groc').CLI(
|
||||||
"README.md",
|
"README.md",
|
||||||
"TODO.js",
|
"TODO.js",
|
||||||
"LICENSE.js",
|
"LICENSE.js",
|
||||||
"js/*",
|
"js-coffee/*",
|
||||||
"mod_actions/**/*.js",
|
"mod_actions/**/*.js",
|
||||||
"mod_events/**/*.js",
|
"mod_events/**/*.js",
|
||||||
"-o./webpages/doc"
|
"-o./webpages/doc"
|
||||||
|
|
|
||||||
80
js-coffee/config.js
Normal file
80
js-coffee/config.js
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var path = require('path'),
|
||||||
|
log = require('./logging'),
|
||||||
|
config;
|
||||||
|
|
||||||
|
exports = module.exports = function(args) {
|
||||||
|
args = args || {};
|
||||||
|
log(args);
|
||||||
|
if(typeof args.relPath === 'string') loadConfigFile(args.relPath);
|
||||||
|
//TODO check all modules whether they can be loaded without calling the module.exports with args
|
||||||
|
return module.exports;
|
||||||
|
};
|
||||||
|
|
||||||
|
loadConfigFile(path.join('config', 'config.json'));
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Answer true if the config file is ready, else false
|
||||||
|
*/
|
||||||
|
exports.isReady = function() {
|
||||||
|
if(config) return true;
|
||||||
|
else return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch a property from the configuration
|
||||||
|
* @param {String} prop
|
||||||
|
*/
|
||||||
|
function fetchProp(prop) {
|
||||||
|
if(config) return config[prop];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the HTTP port
|
||||||
|
*/
|
||||||
|
exports.getHttpPort = function() {
|
||||||
|
return fetchProp('http_port');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the DB port
|
||||||
|
*/
|
||||||
|
exports.getDBPort = function() {
|
||||||
|
return fetchProp('db_port');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the crypto key
|
||||||
|
*/
|
||||||
|
exports.getCryptoKey = function() {
|
||||||
|
return fetchProp('crypto_key');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the session secret
|
||||||
|
*/
|
||||||
|
exports.getSessionSecret = function() {
|
||||||
|
return fetchProp('session_secret');
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
322
js-coffee/db_interface.js
Normal file
322
js-coffee/db_interface.js
Normal file
|
|
@ -0,0 +1,322 @@
|
||||||
|
/**
|
||||||
|
* # 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;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the DB connection. Requires a valid configuration file which contains
|
||||||
|
* a db port and a crypto key.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
exports = module.exports = function(args) {
|
||||||
|
args = args || {};
|
||||||
|
log(args);
|
||||||
|
|
||||||
|
var config = require('./config')(args);
|
||||||
|
crypto_key = config.getCryptoKey();
|
||||||
|
db = redis.createClient(config.getDBPort(), 'localhost', { connect_timeout: 2000 });
|
||||||
|
db.on("error", function (err) {
|
||||||
|
err.addInfo = 'message from DB';
|
||||||
|
log.error('DB', err);
|
||||||
|
});
|
||||||
|
return module.exports;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.isConnected = function(cb) {
|
||||||
|
if(db.connected) cb(null);
|
||||||
|
else setTimeout(function() {
|
||||||
|
if(db.connected) {
|
||||||
|
log.print('DB', 'Successfully connected to DB!');
|
||||||
|
cb(null);
|
||||||
|
} else {
|
||||||
|
var e = new Error('Connection to DB failed!');
|
||||||
|
log.error('DB', e);
|
||||||
|
cb(e);
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ### encrypt
|
||||||
|
* this is used to decrypt
|
||||||
|
* @param {String} plainText
|
||||||
|
*/
|
||||||
|
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, id) {
|
||||||
|
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 "' + id + '": ' + 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} cb the function to be called on success or error, receives
|
||||||
|
* arguments (err, obj)
|
||||||
|
*/
|
||||||
|
function getSetRecords(set, funcSingle, cb) {
|
||||||
|
if(db) db.smembers(set, function(err, reply) {
|
||||||
|
if(err) log.error('DB', 'fetching ' + set + ': ' + err);
|
||||||
|
else {
|
||||||
|
if(reply.length === 0) {
|
||||||
|
cb(null, null);
|
||||||
|
} else {
|
||||||
|
var semaphore = reply.length, objReplies = {};
|
||||||
|
setTimeout(function() {
|
||||||
|
if(semaphore > 0) {
|
||||||
|
cb('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) cb(null, objReplies);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}(reply[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// @method shutDown()
|
||||||
|
|
||||||
|
// Shuts down the db link.
|
||||||
|
exports.shutDown = function() { if(db) 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) {
|
||||||
|
if(db) {
|
||||||
|
db.sadd('action_modules', id, replyHandler('storing action module key ' + id));
|
||||||
|
db.set('action_module_' + id, data, replyHandler('storing action module ' + id));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ### getActionModule(id, cb)
|
||||||
|
* Query the DB for an action module.
|
||||||
|
* @param {String} id the module id
|
||||||
|
* @param {function} cb the cb to receive the answer (err, obj)
|
||||||
|
*/
|
||||||
|
exports.getActionModule = function(id, cb) {
|
||||||
|
if(cb && db) db.get('action_module_' + id, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ### getActionModules(cb)
|
||||||
|
* Fetch all action modules.
|
||||||
|
* @param {function} cb the cb to receive the answer (err, obj)
|
||||||
|
*/
|
||||||
|
exports.getActionModules = function(cb) {
|
||||||
|
getSetRecords('action_modules', exports.getActionModule, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
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, cb)
|
||||||
|
* Query the DB for an action module authentication token.
|
||||||
|
* @param {String} id the module id
|
||||||
|
* @param {function} cb the cb to receive the answer (err, obj)
|
||||||
|
*/
|
||||||
|
exports.getActionModuleAuth = function(id, cb) {
|
||||||
|
if(cb && db) db.get('action_module_' + id + '_auth', function(id) {
|
||||||
|
return function(err, txt) { cb(err, decrypt(txt, 'action_module_' + id + '_auth')); };
|
||||||
|
}(id));
|
||||||
|
};
|
||||||
|
|
||||||
|
// ## 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) {
|
||||||
|
if(db) {
|
||||||
|
db.sadd('event_modules', id, replyHandler('storing event module key ' + id));
|
||||||
|
db.set('event_module_' + id, data, replyHandler('storing event module ' + id));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ### getEventModule(id, cb)
|
||||||
|
* Query the DB for an event module.
|
||||||
|
* @param {String} id the module id
|
||||||
|
* @param {function} cb the cb to receive the answer (err, obj)
|
||||||
|
*/
|
||||||
|
exports.getEventModule = function(id, cb) {
|
||||||
|
if(cb && db) db.get('event_module_' + id, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ### getEventModules(cb)
|
||||||
|
* Fetch all event modules.
|
||||||
|
* @param {function} cb the cb that receives the arguments (err, obj)
|
||||||
|
*/
|
||||||
|
exports.getEventModules = function(cb) {
|
||||||
|
getSetRecords('event_modules', exports.getEventModule, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ### 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) {
|
||||||
|
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, cb)
|
||||||
|
|
||||||
|
// Query the DB for an event module authentication token.
|
||||||
|
// @param {String} id the module id
|
||||||
|
// @param {function} cb the cb to receive the answer (err, obj)
|
||||||
|
exports.getEventModuleAuth = function(id, cb) {
|
||||||
|
if(cb) db.get('event_module_' + id +'_auth', function(id) {
|
||||||
|
return function(err, txt) { cb(err, decrypt(txt, 'event_module_' + id + '_auth')); };
|
||||||
|
}(id));
|
||||||
|
};
|
||||||
|
|
||||||
|
// ## 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) {
|
||||||
|
if(db) {
|
||||||
|
db.sadd('rules', id, replyHandler('storing rule key ' + id));
|
||||||
|
db.set('rule_' + id, data, replyHandler('storing rule ' + id));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// @method getRule(id, cb)
|
||||||
|
|
||||||
|
// Query the DB for a rule.
|
||||||
|
// @param {String} id the rule id
|
||||||
|
// @param {function} cb the cb to receive the answer (err, obj)
|
||||||
|
exports.getRule = function(id, cb) {
|
||||||
|
if(db) db.get('rule_' + id, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
// @method getRules(cb)
|
||||||
|
|
||||||
|
// Fetch all rules from the database.
|
||||||
|
// @param {function} cb
|
||||||
|
exports.getRules = function(cb) {
|
||||||
|
getSetRecords('rules', exports.getRule, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Object} objUser
|
||||||
|
* @param {function} cb
|
||||||
|
*/
|
||||||
|
exports.storeUser = function(objUser, cb) {
|
||||||
|
if(db && objUser && objUser.username && objUser.password) {
|
||||||
|
db.sadd('users', objUser.username, replyHandler('storing user key ' + objUser.username));
|
||||||
|
objUser.password = encrypt(objUser.password);
|
||||||
|
db.set('user:' + objUser.username, objUser, replyHandler('storing user properties ' + objUser.username));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the credentials and on success returns the user object.
|
||||||
|
* @param {Object} objUser
|
||||||
|
* @param {function} cb
|
||||||
|
*/
|
||||||
|
exports.loginUser = function(username, password, cb) {
|
||||||
|
if(typeof cb !== 'function') return;
|
||||||
|
if(db) db.get('user:' + username, function(p) {
|
||||||
|
return function(err, obj) {
|
||||||
|
if(err) cb(err);
|
||||||
|
else if(encrypt(obj.password) === p) cb(null, obj);
|
||||||
|
else cb(new Error('Wrong credentials!'));
|
||||||
|
};
|
||||||
|
}(password));
|
||||||
|
else cb(new Error('No database link available!'));
|
||||||
|
};
|
||||||
231
js-coffee/engine.js
Normal file
231
js-coffee/engine.js
Normal file
|
|
@ -0,0 +1,231 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var path = require('path'),
|
||||||
|
cp = require('child_process'),
|
||||||
|
log = require('./logging'),
|
||||||
|
qEvents = new (require('./queue')).Queue(), //TODO export queue into redis
|
||||||
|
regex = /\$X\.[\w\.\[\]]*/g, // find properties of $X
|
||||||
|
listRules = {},
|
||||||
|
listActionModules = {},
|
||||||
|
isRunning = true,
|
||||||
|
ml, poller, db;
|
||||||
|
|
||||||
|
exports = module.exports = function(args) {
|
||||||
|
args = args || {};
|
||||||
|
log(args);
|
||||||
|
ml = require('./module_loader')(args);
|
||||||
|
poller = cp.fork(path.resolve(__dirname, 'eventpoller'), [log.getLogType()]);
|
||||||
|
poller.on('message', function(evt) {
|
||||||
|
exports.pushEvent(evt);
|
||||||
|
});
|
||||||
|
//start to poll the event queue
|
||||||
|
pollQueue();
|
||||||
|
return module.exports;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize the rules engine which initializes the module loader.
|
||||||
|
* @param {Object} db_link the link to the db, see [db\_interface](db_interface.html)
|
||||||
|
* @param {String} db_port the db port
|
||||||
|
* @param {String} crypto_key the key to be used for encryption on the db, max legnth 256
|
||||||
|
*/
|
||||||
|
exports.addDBLinkAndLoadActionsAndRules = function(db_link) {
|
||||||
|
db = db_link;
|
||||||
|
if(ml && db) db.getActionModules(function(err, obj) {
|
||||||
|
if(err) log.error('EN', 'retrieving Action Modules from DB!');
|
||||||
|
else {
|
||||||
|
if(!obj) {
|
||||||
|
log.print('EN', 'No Action Modules found in DB!');
|
||||||
|
loadRulesFromDB();
|
||||||
|
} else {
|
||||||
|
var m;
|
||||||
|
for(var el in obj) {
|
||||||
|
log.print('EN', 'Loading Action Module from DB: ' + el);
|
||||||
|
try{
|
||||||
|
m = ml.requireFromString(obj[el], el);
|
||||||
|
db.getActionModuleAuth(el, function(mod) {
|
||||||
|
return function(err, obj) {
|
||||||
|
if(obj && mod.loadCredentials) mod.loadCredentials(JSON.parse(obj));
|
||||||
|
};
|
||||||
|
}(m));
|
||||||
|
listActionModules[el] = m;
|
||||||
|
} catch(e) {
|
||||||
|
e.addInfo = 'error in action module "' + el + '"';
|
||||||
|
log.error('EN', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadRulesFromDB();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
else log.severe('EN', new Error('Module Loader or DB not defined!'));
|
||||||
|
};
|
||||||
|
|
||||||
|
function loadRulesFromDB() {
|
||||||
|
if(db) db.getRules(function(err, obj) {
|
||||||
|
for(var el in obj) exports.addRule(JSON.parse(obj[el]));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert an action module into the list of available interfaces.
|
||||||
|
* @param {Object} objModule the action module object
|
||||||
|
*/
|
||||||
|
exports.loadActionModule = function(name, objModule) {
|
||||||
|
log.print('EN', 'Action module "' + name + '" loaded');
|
||||||
|
listActionModules[name] = objModule;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a rule into the working memory
|
||||||
|
* @param {Object} objRule the rule object
|
||||||
|
*/
|
||||||
|
exports.addRule = function(objRule) {
|
||||||
|
//TODO validate rule
|
||||||
|
log.print('EN', 'Loading Rule: ' + objRule.id);
|
||||||
|
if(listRules[objRule.id]) log.print('EN', 'Replacing rule: ' + objRule.id);
|
||||||
|
listRules[objRule.id] = objRule;
|
||||||
|
|
||||||
|
// Notify poller about eventual candidate
|
||||||
|
try {
|
||||||
|
poller.send('event|'+objRule.event);
|
||||||
|
} catch (err) {
|
||||||
|
log.print('EN', 'Unable to inform poller about new active rule!');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function pollQueue() {
|
||||||
|
if(isRunning) {
|
||||||
|
var evt = qEvents.dequeue();
|
||||||
|
if(evt) {
|
||||||
|
processEvent(evt);
|
||||||
|
}
|
||||||
|
setTimeout(pollQueue, 50); //TODO adapt to load
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores correctly posted events in the queue
|
||||||
|
* @param {Object} evt The event object
|
||||||
|
*/
|
||||||
|
exports.pushEvent = function(evt) {
|
||||||
|
qEvents.enqueue(evt);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles correctly posted events
|
||||||
|
* @param {Object} evt The event object
|
||||||
|
*/
|
||||||
|
function processEvent(evt) {
|
||||||
|
log.print('EN', 'processing event: ' + evt.event + '(' + evt.eventid + ')');
|
||||||
|
var actions = checkEvent(evt);
|
||||||
|
for(var i = 0; i < actions.length; i++) {
|
||||||
|
invokeAction(evt, actions[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check an event against the rules repository and return the actions
|
||||||
|
* if the conditons are met.
|
||||||
|
* @param {Object} evt the event to check
|
||||||
|
*/
|
||||||
|
function checkEvent(evt) {
|
||||||
|
var actions = [];
|
||||||
|
for(var rn in listRules) {
|
||||||
|
//TODO this needs to get depth safe, not only data but eventually also
|
||||||
|
// on one level above (eventid and other meta)
|
||||||
|
if(listRules[rn].event === evt.event && validConditions(evt.payload, listRules[rn])) {
|
||||||
|
log.print('EN', 'Rule "' + rn + '" fired');
|
||||||
|
actions = actions.concat(listRules[rn].actions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether all conditions of the rule are met by the event.
|
||||||
|
* @param {Object} evt the event to check
|
||||||
|
* @param {Object} rule the rule with its conditions
|
||||||
|
*/
|
||||||
|
function validConditions(evt, rule) {
|
||||||
|
for(var property in rule.condition){
|
||||||
|
if(!evt[property] || evt[property] != rule.condition[property]) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke an action according to its type.
|
||||||
|
* @param {Object} evt The event that invoked the action
|
||||||
|
* @param {Object} action The action to be invoked
|
||||||
|
*/
|
||||||
|
function invokeAction(evt, action) {
|
||||||
|
var actionargs = {},
|
||||||
|
arrModule = action.module.split('->');
|
||||||
|
if(arrModule.length < 2) {
|
||||||
|
log.error('EN', 'Invalid rule detected!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var srvc = listActionModules[arrModule[0]];
|
||||||
|
if(srvc && srvc[arrModule[1]]) {
|
||||||
|
//FIXME preprocessing not only on data
|
||||||
|
preprocessActionArguments(evt.payload, action.arguments, actionargs);
|
||||||
|
try {
|
||||||
|
if(srvc[arrModule[1]]) srvc[arrModule[1]](actionargs);
|
||||||
|
} catch(err) {
|
||||||
|
log.error('EN', 'during action execution: ' + err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else log.print('EN', 'No api interface found for: ' + action.module);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action properties may contain event properties which need to be resolved beforehand.
|
||||||
|
* @param {Object} evt The event whose property values can be used in the rules action
|
||||||
|
* @param {Object} act The rules action arguments
|
||||||
|
* @param {Object} res The object to be used to enter the new properties
|
||||||
|
*/
|
||||||
|
function preprocessActionArguments(evt, act, res) {
|
||||||
|
for(var prop in act) {
|
||||||
|
/*
|
||||||
|
* If the property is an object itself we go into recursion
|
||||||
|
*/
|
||||||
|
if(typeof act[prop] === 'object') {
|
||||||
|
res[prop] = {};
|
||||||
|
preprocessActionArguments(evt, act[prop], res[prop]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var txt = act[prop];
|
||||||
|
var arr = txt.match(regex);
|
||||||
|
/*
|
||||||
|
* If rules action property holds event properties we resolve them and
|
||||||
|
* replace the original action property
|
||||||
|
*/
|
||||||
|
// console.log(evt);
|
||||||
|
if(arr) {
|
||||||
|
for(var i = 0; i < arr.length; i++) {
|
||||||
|
/*
|
||||||
|
* The first three characters are '$X.', followed by the property
|
||||||
|
*/
|
||||||
|
var actionProp = arr[i].substring(3).toLowerCase();
|
||||||
|
// console.log(actionProp);
|
||||||
|
for(var eprop in evt) {
|
||||||
|
// our rules language doesn't care about upper or lower case
|
||||||
|
if(eprop.toLowerCase() === actionProp) {
|
||||||
|
txt = txt.replace(arr[i], evt[eprop]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
txt = txt.replace(arr[i], '[property not available]');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res[prop] = txt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.shutDown = function() {
|
||||||
|
log.print('EN', 'Shutting down Poller and DB Link');
|
||||||
|
isRunning = false;
|
||||||
|
if(poller) poller.send('cmd|shutdown');
|
||||||
|
if(db) db.shutDown();
|
||||||
|
};
|
||||||
153
js-coffee/eventpoller.js
Normal file
153
js-coffee/eventpoller.js
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
// # Event Poller
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var fs = require('fs'),
|
||||||
|
path = require('path'),
|
||||||
|
log = require('./logging'),
|
||||||
|
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,
|
||||||
|
db, ml;
|
||||||
|
|
||||||
|
//TODO allow different polling intervals (a wrapper together with settimeout per to be polled could be an easy and solution)
|
||||||
|
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
if(process.argv.length > 2) log({ logType: parseInt(process.argv[2]) || 0 });
|
||||||
|
var args = { logType: log.getLogType() };
|
||||||
|
ml = require('./module_loader')(args);
|
||||||
|
db = require('./db_interface')(args);
|
||||||
|
initAdminCommands();
|
||||||
|
initMessageActions();
|
||||||
|
pollLoop();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function loadEventModule(el, cb) {
|
||||||
|
if(db && ml) db.getEventModule(el, function(err, obj) {
|
||||||
|
if(err || !obj) {
|
||||||
|
if(typeof cb === 'function') cb(new Error('Retrieving Event Module ' + el + ' from DB: ' + err));
|
||||||
|
else log.error('EP', 'Retrieving Event Module ' + el + ' from DB!');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// log.print('EP', 'Loading Event Module: ' + el);
|
||||||
|
try {
|
||||||
|
var m = ml.requireFromString(obj, el);
|
||||||
|
db.getEventModuleAuth(el, function(mod) {
|
||||||
|
return function(err, objA) {
|
||||||
|
//TODO authentication needs to be done differently
|
||||||
|
if(objA && mod.loadCredentials) mod.loadCredentials(JSON.parse(objA));
|
||||||
|
};
|
||||||
|
}(m));
|
||||||
|
listEventModules[el] = m;
|
||||||
|
if(typeof cb === 'function') cb(null, m);
|
||||||
|
} catch(e) {
|
||||||
|
if(typeof cb === 'function') cb(e);
|
||||||
|
else log.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchPollFunctionFromModule(mod, func) {
|
||||||
|
for(var i = 1; i < func.length; i++) {
|
||||||
|
if(mod) mod = mod[func[i]];
|
||||||
|
}
|
||||||
|
if(mod) {
|
||||||
|
log.print('EP', 'Found active event module "' + func.join('->') + '", 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[func.join('->')] = mod;
|
||||||
|
} else {
|
||||||
|
log.print('EP', 'No property "' + func.join('->') + '" found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initMessageActions() {
|
||||||
|
listMessageActions['event'] = function(args) {
|
||||||
|
var prop = args[1], arrModule = prop.split('->');
|
||||||
|
if(arrModule.length > 1){
|
||||||
|
if(listEventModules[arrModule[0]]) {
|
||||||
|
fetchPollFunctionFromModule(listEventModules[arrModule[0]], arrModule);
|
||||||
|
} else {
|
||||||
|
log.print('EP', 'Event Module ' + arrModule[0] + ' needs to be loaded, doing it now...');
|
||||||
|
loadEventModule(arrModule[0], function(err, obj) {
|
||||||
|
if(err || !obj) log.error('EP', 'Event Module "' + arrModule[0] + '" not found: ' + err);
|
||||||
|
else {
|
||||||
|
log.print('EP', 'Event Module ' + arrModule[0] + ' found and loaded');
|
||||||
|
fetchPollFunctionFromModule(obj, arrModule);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//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['shutdown'] = function(args) {
|
||||||
|
log.print('EP', 'Shutting down DB Link');
|
||||||
|
isRunning = false;
|
||||||
|
if(db) db.shutDown();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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++,
|
||||||
|
payload: obj
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})(prop)
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
log.error('EP', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pollLoop() {
|
||||||
|
if(isRunning) {
|
||||||
|
checkRemotes();
|
||||||
|
setTimeout(pollLoop, 10000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
116
js-coffee/http_listener.js
Normal file
116
js-coffee/http_listener.js
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
// HTTP Listener
|
||||||
|
// =============
|
||||||
|
//
|
||||||
|
// Handles the HTTP requests to the server at the port specified by the [config](config.html) file.
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var path = require('path'),
|
||||||
|
express = require('express'),
|
||||||
|
app = express(),
|
||||||
|
RedisStore = require('connect-redis')(express),
|
||||||
|
qs = require('querystring'),
|
||||||
|
log = require('./logging'),
|
||||||
|
sess_sec = '#C[>;j`@".TXm2TA;A2Tg)',
|
||||||
|
db_port, http_port, server,
|
||||||
|
eventHandler, userHandler;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The module needs to be called as a function to initialize it.
|
||||||
|
* After that it fetches the http\_port, db\_port & sess\_sec properties
|
||||||
|
* from the configuration file.
|
||||||
|
*/
|
||||||
|
exports = module.exports = function(args) {
|
||||||
|
args = args || {};
|
||||||
|
log(args);
|
||||||
|
var config = require('./config')(args);
|
||||||
|
userHandler = require('./user_handler')(args);
|
||||||
|
db_port = config.getDBPort(),
|
||||||
|
sess_sec = config.getSessionSecret(),
|
||||||
|
http_port = config.getHttpPort();
|
||||||
|
return module.exports;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.addHandlers = function(funcAdminHandler, funcEvtHandler) {
|
||||||
|
if(!funcAdminHandler || !funcEvtHandler) {
|
||||||
|
log.error('HL', 'ERROR: either adminHandler or eventHandler function not defined!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
userHandler.addHandler(funcAdminHandler);
|
||||||
|
eventHandler = funcEvtHandler;
|
||||||
|
// Add cookie support for session handling.
|
||||||
|
app.use(express.cookieParser());
|
||||||
|
app.use(express.session({secret: sess_sec}));
|
||||||
|
log.print('HL', 'no session backbone');
|
||||||
|
|
||||||
|
// ^ TODO figure out why redis backbone doesn't work. eventually the db pass has to be set in the DB?
|
||||||
|
// } session information seems to be stored in DB but not retrieved correctly
|
||||||
|
// } if(db_port) {
|
||||||
|
// } app.use(express.session({
|
||||||
|
// } store: new RedisStore({
|
||||||
|
// } host: 'localhost',
|
||||||
|
// } port: db_port,
|
||||||
|
// } db: 2
|
||||||
|
// } ,
|
||||||
|
// } pass: null
|
||||||
|
// } }),
|
||||||
|
// } secret: sess_sec
|
||||||
|
// } }));
|
||||||
|
// } log.print('HL', 'Added redis DB as session backbone');
|
||||||
|
// } } else {
|
||||||
|
// } app.use(express.session({secret: sess_sec}));
|
||||||
|
// } log.print('HL', 'no session backbone');
|
||||||
|
// } }
|
||||||
|
|
||||||
|
// Redirect the requests to the appropriate handler.
|
||||||
|
app.use('/', express.static(path.resolve(__dirname, '..', 'webpages')));
|
||||||
|
// app.use('/doc/', express.static(path.resolve(__dirname, '..', 'webpages', 'doc')));
|
||||||
|
// app.get('/mobile', userHandler.handleRequest);
|
||||||
|
app.get('/rulesforge', userHandler.handleRequest);
|
||||||
|
// app.use('/mobile', express.static(path.resolve(__dirname, '..', 'webpages', 'mobile')));
|
||||||
|
// } app.use('/rulesforge/', express.static(path.resolve(__dirname, '..', 'webpages', 'rulesforge')));
|
||||||
|
app.get('/admin', userHandler.handleRequest);
|
||||||
|
app.post('/login', userHandler.handleLogin);
|
||||||
|
app.post('/push_event', onPushEvent);
|
||||||
|
try {
|
||||||
|
if(http_port) server = app.listen(http_port); // inbound event channel
|
||||||
|
else log.error('HL', new Error('No HTTP port found!? Nothing to listen on!...'));
|
||||||
|
} catch(e) {
|
||||||
|
e.addInfo = 'port unavailable';
|
||||||
|
log.error(e);
|
||||||
|
funcAdminHandler({cmd: 'shutdown'});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a post request reaches the server, this function handles it and treats the request as a possible event.
|
||||||
|
*/
|
||||||
|
function onPushEvent(req, resp) {
|
||||||
|
var body = '';
|
||||||
|
req.on('data', function (data) { body += data; });
|
||||||
|
req.on('end', function () {
|
||||||
|
var obj = qs.parse(body);
|
||||||
|
/* If required event properties are present we process the event */
|
||||||
|
if(obj && obj.event && obj.eventid){
|
||||||
|
resp.writeHead(200, { "Content-Type": "text/plain" });
|
||||||
|
resp.write('Thank you for the event (' + obj.event + '[' + obj.eventid + '])!');
|
||||||
|
eventHandler(obj);
|
||||||
|
} else {
|
||||||
|
resp.writeHead(400, { "Content-Type": "text/plain" });
|
||||||
|
resp.write('Your event was missing important parameters!');
|
||||||
|
}
|
||||||
|
resp.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.loadUsers = function() {
|
||||||
|
var users = JSON.parse(require('fs').readFileSync(path.resolve(__dirname, '..', relPath)));
|
||||||
|
for(var name in users) {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.shutDown = function() {
|
||||||
|
log.print('HL', 'Shutting down HTTP listener');
|
||||||
|
process.exit(); // This is a bit brute force...
|
||||||
|
};
|
||||||
92
js-coffee/logging.js
Normal file
92
js-coffee/logging.js
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
* Logging
|
||||||
|
* =======
|
||||||
|
* Functions to handle logging and errors.
|
||||||
|
*
|
||||||
|
* Valid log types are:
|
||||||
|
*
|
||||||
|
* - 0 standard I/O
|
||||||
|
* - 1 file
|
||||||
|
* - 2 silent
|
||||||
|
*/
|
||||||
|
var fs = require('fs'),
|
||||||
|
logTypes = [ flushToConsole, flushToFile, null],
|
||||||
|
logFile = require('path').resolve(__dirname, '..', 'server.log'),
|
||||||
|
logType = 0;
|
||||||
|
|
||||||
|
exports = module.exports = function(args) {
|
||||||
|
args = args || {};
|
||||||
|
if(args.logType) logType = parseInt(args.logType) || 0;
|
||||||
|
if(logType == 1) fs.truncateSync(logFile, 0);
|
||||||
|
if(logType > logTypes.length - 1) logType = 0;
|
||||||
|
return module.exports;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.getLogType = function() { return logType; };
|
||||||
|
|
||||||
|
function flush(err, msg) {
|
||||||
|
if(typeof logTypes[logType] === 'function') logTypes[logType](err, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
function flushToConsole(err, msg) {
|
||||||
|
if(err) console.error(msg);
|
||||||
|
else console.log(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
function flushToFile(err, msg) {
|
||||||
|
fs.appendFile(logFile, msg + '\n', function (err) {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// @function print(module, msg)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Prints a log to stdout.
|
||||||
|
* @param {String} module
|
||||||
|
* @param {String} msg
|
||||||
|
*/
|
||||||
|
exports.print = function(module, msg) {
|
||||||
|
flush(false, (new Date()).toISOString() + ' | ' + module + ' | ' + msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints a log to stderr.
|
||||||
|
* @param {String} module
|
||||||
|
* @param {Error} err
|
||||||
|
*/
|
||||||
|
function printError(module, err, isSevere) {
|
||||||
|
var ts = (new Date()).toISOString() + ' | ', ai = '';
|
||||||
|
if(!err) err = new Error('Unexpected error');
|
||||||
|
if(typeof err === 'string') err = new Error(err);
|
||||||
|
// 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) {
|
||||||
|
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');
|
||||||
|
// 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);
|
||||||
|
};
|
||||||
81
js-coffee/module_loader.js
Normal file
81
js-coffee/module_loader.js
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var fs = require('fs'),
|
||||||
|
path = require('path'),
|
||||||
|
log = require('./logging');
|
||||||
|
|
||||||
|
exports = module.exports = function(args) {
|
||||||
|
args = args || {};
|
||||||
|
log(args);
|
||||||
|
return module.exports;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.requireFromString = function(src, name, dir) {
|
||||||
|
if(!dir) dir = __dirname;
|
||||||
|
var id = path.resolve(dir, name, name + '.vm');
|
||||||
|
//FIXME load modules only into a safe environment with given modules, no access to whole application,
|
||||||
|
var vm = require('vm'),
|
||||||
|
sandbox = {
|
||||||
|
log: log,
|
||||||
|
needle: require('needle')
|
||||||
|
};
|
||||||
|
|
||||||
|
var mod = vm.runInNewContext(src, sandbox, 'myfile.vm');
|
||||||
|
console.log(mod);
|
||||||
|
var m = new module.constructor(id, module);
|
||||||
|
m.paths = module.paths;
|
||||||
|
try {
|
||||||
|
m._compile(src);
|
||||||
|
} catch(err) {
|
||||||
|
err.addInfo = 'during compilation of module ' + name;
|
||||||
|
log.error('LM', err);
|
||||||
|
// log.error('LM', ' during compilation of ' + name + ': ' + err);
|
||||||
|
}
|
||||||
|
return m.exports;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 = 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) {
|
||||||
|
log.error('LM', 'Loading credentials file for "' + name + '"!');
|
||||||
|
callback(name, data, mod, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(mod.loadCredentials) mod.loadCredentials(JSON.parse(auth));
|
||||||
|
callback(name, data, mod, auth);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Hand back the name, the string contents and the compiled module
|
||||||
|
callback(name, data, mod, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch(err) {
|
||||||
|
log.error('LM', 'Failed loading module "' + name + '"');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.loadModules = function(directory, callback) {
|
||||||
|
fs.readdir(path.resolve(__dirname, '..', directory), function (err, list) {
|
||||||
|
if (err) {
|
||||||
|
log.error('LM', 'loading modules directory: ' + err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.print('LM', 'Loading ' + list.length + ' modules from "' + directory + '"');
|
||||||
|
list.forEach(function (file) {
|
||||||
|
fs.stat(path.resolve(__dirname, '..', directory, file), function (err, stat) {
|
||||||
|
if (stat && stat.isDirectory()) {
|
||||||
|
exports.loadModule(directory, file, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
119
js-coffee/module_manager.js
Normal file
119
js-coffee/module_manager.js
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
/*
|
||||||
|
# Module Manager
|
||||||
|
> The module manager takes care of the module and rules loading in the initialization
|
||||||
|
> phase and on user request.
|
||||||
|
|
||||||
|
> Event and Action modules are loaded as strings and stored in the database,
|
||||||
|
> then compiled into node modules and rules
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var fs = require('fs'),
|
||||||
|
path = require('path'),
|
||||||
|
log = require('./logging'),
|
||||||
|
ml, db, funcLoadAction, funcLoadRule;
|
||||||
|
|
||||||
|
exports = module.exports = function(args) {
|
||||||
|
args = args || {};
|
||||||
|
log(args);
|
||||||
|
ml = require('./module_loader')(args);
|
||||||
|
return module.exports;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.addHandlers = function(db_link, fLoadAction, fLoadRule) {
|
||||||
|
db = db_link;
|
||||||
|
funcLoadAction = fLoadAction;
|
||||||
|
funcLoadRule = fLoadRule;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Load Rules from fs
|
||||||
|
* ------------------
|
||||||
|
*/
|
||||||
|
exports.loadRulesFromFS = function(args, answHandler) {
|
||||||
|
if(!args) args = {};
|
||||||
|
if(!args.name) args.name = 'rules';
|
||||||
|
if(!funcLoadRule) log.error('ML', 'no rule loader function available');
|
||||||
|
else {
|
||||||
|
fs.readFile(path.resolve(__dirname, '..', 'rules', args.name + '.json'), 'utf8', function (err, data) {
|
||||||
|
if (err) {
|
||||||
|
log.error('ML', 'Loading rules file: ' + args.name + '.json');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var arr = JSON.parse(data), txt = '';
|
||||||
|
log.print('ML', 'Loading ' + arr.length + ' rules:');
|
||||||
|
for(var i = 0; i < arr.length; i++) {
|
||||||
|
txt += arr[i].id + ', ';
|
||||||
|
db.storeRule(arr[i].id, JSON.stringify(arr[i]));
|
||||||
|
funcLoadRule(arr[i]);
|
||||||
|
}
|
||||||
|
answHandler.answerSuccess('Yep, loaded rules: ' + txt);
|
||||||
|
} catch (e) {
|
||||||
|
log.error('ML', 'rules file was corrupt! (' + args.name + '.json)');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Load Action Modules from fs
|
||||||
|
* ---------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Object} name
|
||||||
|
* @param {Object} data
|
||||||
|
* @param {Object} mod
|
||||||
|
* @param {String} [auth] The string representation of the auth json
|
||||||
|
*/
|
||||||
|
function loadActionCallback(name, data, mod, auth) {
|
||||||
|
db.storeActionModule(name, data); // store module in db
|
||||||
|
funcLoadAction(name, mod); // hand back compiled module
|
||||||
|
if(auth) db.storeActionModuleAuth(name, auth);
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.loadActionModuleFromFS = function (args, answHandler) {
|
||||||
|
if(ml) {
|
||||||
|
if(args && args.name) {
|
||||||
|
answHandler.answerSuccess('Loading action module ' + args.name + '...');
|
||||||
|
ml.loadModule('mod_actions', args.name, loadActionCallback);
|
||||||
|
} else log.error('MM', 'Action Module name not provided!');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.loadActionModulesFromFS = function(args, answHandler) {
|
||||||
|
if(ml) {
|
||||||
|
answHandler.answerSuccess('Loading action modules...');
|
||||||
|
ml.loadModules('mod_actions', loadActionCallback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Load Event Modules from fs
|
||||||
|
* --------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
function loadEventCallback(name, data, mod, auth) {
|
||||||
|
if(db) {
|
||||||
|
db.storeEventModule(name, data); // store module in db
|
||||||
|
if(auth) db.storeEventModuleAuth(name, auth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.loadEventModuleFromFS = function(args, answHandler) {
|
||||||
|
if(ml) {
|
||||||
|
if(args && args.name) {
|
||||||
|
answHandler.answerSuccess('Loading event module ' + args.name + '...');
|
||||||
|
ml.loadModule('mod_events', args.name, loadEventCallback);
|
||||||
|
} else log.error('MM', 'Event Module name not provided!');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.loadEventModulesFromFS = function(args, answHandler) {
|
||||||
|
answHandler.answerSuccess('Loading event moules...');
|
||||||
|
ml.loadModules('mod_actions', loadEventCallback);
|
||||||
|
};
|
||||||
|
|
||||||
57
js-coffee/queue.js
Normal file
57
js-coffee/queue.js
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
// *(will be replaced by a Redis DB queue)*
|
||||||
|
|
||||||
|
// Queue.js
|
||||||
|
// ========
|
||||||
|
//
|
||||||
|
// *A function to represent a queue*
|
||||||
|
|
||||||
|
// *Created by Stephen Morley - http://code.stephenmorley.org/ - and released under
|
||||||
|
// the terms of the CC0 1.0 Universal legal code:*
|
||||||
|
|
||||||
|
// *http://creativecommons.org/publicdomain/zero/1.0/legalcode*
|
||||||
|
|
||||||
|
// *items are added to the end of the queue and removed from the front.*
|
||||||
|
|
||||||
|
exports.Queue = function(){
|
||||||
|
// initialise the queue and offset
|
||||||
|
var queue = [];
|
||||||
|
var offset = 0;
|
||||||
|
this.getLength = function(){
|
||||||
|
// return the length of the queue
|
||||||
|
return (queue.length - offset);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Returns true if the queue is empty, and false otherwise.
|
||||||
|
*/
|
||||||
|
this.isEmpty = function(){ return (queue.length == 0); };
|
||||||
|
|
||||||
|
/* Enqueues the specified item. The parameter is:
|
||||||
|
* item - the item to enqueue
|
||||||
|
*/
|
||||||
|
this.enqueue = function(item){ queue.push(item); };
|
||||||
|
|
||||||
|
/* Dequeues an item and returns it. If the queue is empty then undefined is
|
||||||
|
* returned.
|
||||||
|
*/
|
||||||
|
this.dequeue = function(){
|
||||||
|
// if the queue is empty, return undefined
|
||||||
|
if (queue.length == 0) return undefined;
|
||||||
|
// store the item at the front of the queue
|
||||||
|
var item = queue[offset];
|
||||||
|
// increment the offset and remove the free space if necessary
|
||||||
|
if (++ offset * 2 >= queue.length){
|
||||||
|
queue = queue.slice(offset);
|
||||||
|
offset = 0;
|
||||||
|
}
|
||||||
|
// return the dequeued item
|
||||||
|
return item;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Returns the item at the front of the queue (without dequeuing it). If the
|
||||||
|
* queue is empty then undefined is returned.
|
||||||
|
*/
|
||||||
|
this.peek = function(){
|
||||||
|
// return the item at the front of the queue
|
||||||
|
return (queue.length > 0 ? queue[offset] : undefined);
|
||||||
|
};
|
||||||
|
};
|
||||||
34
js-coffee/server.js
Normal file
34
js-coffee/server.js
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Generated by CoffeeScript 1.6.3
|
||||||
|
/*
|
||||||
|
Rules Server
|
||||||
|
============
|
||||||
|
This is the main module that is used to run the whole server:
|
||||||
|
|
||||||
|
node server [log_type http_port]
|
||||||
|
|
||||||
|
Valid `log_type`'s are:
|
||||||
|
|
||||||
|
- `0`: standard I/O output (default)
|
||||||
|
- `1`: log file (server.log)
|
||||||
|
- `2`: silent
|
||||||
|
|
||||||
|
`http_port` can be set to use another port, than defined in the
|
||||||
|
[config](config.html) file, to listen to, e.g. used by the test suite.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
var root;
|
||||||
|
|
||||||
|
root = typeof exports !== "undefined" && exports !== null ? exports : this;
|
||||||
|
|
||||||
|
root.foo = function() {
|
||||||
|
return 'Hello World';
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
My comments will show up here
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
}).call(this);
|
||||||
88
js-coffee/user_handler.js
Normal file
88
js-coffee/user_handler.js
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
var path = require('path'),
|
||||||
|
qs = require('querystring'),
|
||||||
|
log = require('./logging'),
|
||||||
|
db = require('./db_interface'),
|
||||||
|
adminHandler;
|
||||||
|
|
||||||
|
exports = module.exports = function(args) {
|
||||||
|
args = args || {};
|
||||||
|
log(args);
|
||||||
|
db(args);
|
||||||
|
var users = JSON.parse(require('fs').readFileSync(path.resolve(__dirname, '..', 'config', 'users.json')));
|
||||||
|
for(var name in users) {
|
||||||
|
db.storeUser(users[name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return module.exports;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.addHandler = function(adminHandl) {
|
||||||
|
adminHandler = adminHandl;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.handleRequest = function(req, resp) {
|
||||||
|
req.on('end', function () {
|
||||||
|
resp.end();
|
||||||
|
});
|
||||||
|
if(req.session && req.session.user) {
|
||||||
|
resp.send('You\'re logged in');
|
||||||
|
} else resp.sendfile(path.resolve(__dirname, '..', 'webpages', 'handlers', 'login.html'));
|
||||||
|
// resp.end();
|
||||||
|
log.print('UH', 'last: '+ req.session.lastPage);
|
||||||
|
req.session.lastPage = req.originalUrl;
|
||||||
|
log.print('UH', 'last: '+ req.session.lastPage);
|
||||||
|
log.print('UH', 'retrieved req: '+ req.originalUrl);
|
||||||
|
// console.log(req);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.handleLogin = function(req, resp) {
|
||||||
|
var body = '';
|
||||||
|
req.on('data', function (data) { body += data; });
|
||||||
|
req.on('end', function () {
|
||||||
|
if(!req.session || !req.session.user) {
|
||||||
|
var obj = qs.parse(body);
|
||||||
|
db.loginUser(obj.username, obj.password, function(err, obj) {
|
||||||
|
if(!err) req.session.user = obj;
|
||||||
|
if(req.session.user) {
|
||||||
|
resp.write('Welcome ' + req.session.user.name + '!');
|
||||||
|
} else {
|
||||||
|
resp.writeHead(401, { "Content-Type": "text/plain" });
|
||||||
|
resp.write('Login failed!');
|
||||||
|
}
|
||||||
|
resp.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function answerHandler(r) {
|
||||||
|
var response = r, hasBeenAnswered = false;
|
||||||
|
function postAnswer(msg) {
|
||||||
|
if(!hasBeenAnswered) {
|
||||||
|
response.write(msg);
|
||||||
|
response.end();
|
||||||
|
hasBeenAnswered = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
answerSuccess: function(msg) {
|
||||||
|
if(!hasBeenAnswered) response.writeHead(200, { "Content-Type": "text/plain" });
|
||||||
|
postAnswer(msg);
|
||||||
|
},
|
||||||
|
answerError: function(msg) {
|
||||||
|
if(!hasBeenAnswered) response.writeHead(400, { "Content-Type": "text/plain" });
|
||||||
|
postAnswer(msg);
|
||||||
|
},
|
||||||
|
isAnswered: function() { return hasBeenAnswered; }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function onAdminCommand(request, response) {
|
||||||
|
var q = request.query;
|
||||||
|
log.print('HL', 'Received admin request: ' + request.originalUrl);
|
||||||
|
if(q.cmd) {
|
||||||
|
adminHandler(q, answerHandler(response));
|
||||||
|
// answerSuccess(response, 'Thank you, we try our best!');
|
||||||
|
} else answerError(response, 'I\'m not sure about what you want from me...');
|
||||||
|
}
|
||||||
|
|
||||||
81
js-coffee/users.js
Normal file
81
js-coffee/users.js
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var log = require('./logging'),
|
||||||
|
objCmds = {
|
||||||
|
addUser: addUser,
|
||||||
|
getUser: getUser,
|
||||||
|
delUser: delUser,
|
||||||
|
addRule: addRule,
|
||||||
|
getRules: getRules,
|
||||||
|
delRule: delRule
|
||||||
|
};
|
||||||
|
|
||||||
|
exports = module.exports = function(args) {
|
||||||
|
args = args || {};
|
||||||
|
log(args);
|
||||||
|
return module.exports;
|
||||||
|
};
|
||||||
|
|
||||||
|
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) {
|
||||||
|
|
||||||
|
}
|
||||||
3
run_coffee_server.sh
Executable file
3
run_coffee_server.sh
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/bash
|
||||||
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
node $DIR/js-coffee/server
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
node $DIR/js/server > $DIR/server.log 2>&1 &
|
node $DIR/js/server > $DIR/js/server.log 2>&1 &
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue