coffee-script preparations

This commit is contained in:
Dominic Bosch 2013-11-19 14:53:36 +01:00
parent e3fb282dd3
commit 3ab28e31f1
19 changed files with 1494 additions and 2 deletions

View file

@ -12,6 +12,13 @@ README: webapi-eca
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:
git clone https://github.com/dominicbosch/webapi-eca.git

24
coffee/server.coffee Normal file
View 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
View file

@ -0,0 +1 @@
efew = require 'fs'

3
compile_coffee.sh Executable file
View 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/

View file

@ -7,7 +7,7 @@ require('groc').CLI(
"README.md",
"TODO.js",
"LICENSE.js",
"js/*",
"js-coffee/*",
"mod_actions/**/*.js",
"mod_events/**/*.js",
"-o./webpages/doc"

80
js-coffee/config.js Normal file
View 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
View 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
View 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
View 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
View 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
View 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);
};

View 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
View 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
View 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
View 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
View 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
View 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
View file

@ -0,0 +1,3 @@
#!/bin/bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
node $DIR/js-coffee/server

View file

@ -1,3 +1,3 @@
#!/bin/bash
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 &