diff --git a/coffee/components-manager.coffee b/coffee/components-manager.coffee new file mode 100644 index 0000000..d03a490 --- /dev/null +++ b/coffee/components-manager.coffee @@ -0,0 +1,323 @@ +### + +Components Manager +================== +> The components manager takes care of the dynamic JS modules and the rules. +> Event Poller and Action Invoker modules are loaded as strings and stored in the database, +> then compiled into node modules and rules and used in the engine and event poller. + +### + +# **Loads Modules:** + +# - [Persistence](persistence.html) +db = require './persistence' +# - [Dynamic Modules](dynamic-modules.html) +dynmod = require './dynamic-modules' + +# - Node.js Modules: [fs](http://nodejs.org/api/fs.html), +# [vm](http://nodejs.org/api/vm.html) and +# [path](http://nodejs.org/api/path.html), +# [events](http://nodejs.org/api/events.html) +fs = require 'fs' +vm = require 'vm' +path = require 'path' +events = require 'events' + +### +Module call +----------- +Initializes the HTTP listener and its request handler. + +@param {Object} args +### +exports = module.exports = ( args ) => + @log = args.logger + @ee = new events.EventEmitter() + db args + dynmod args + module.exports + +exports.addListener = ( evt, eh ) => + @ee.addListener evt, eh + #TODO as soon as an event handler is added it needs to receive the + #full list of existing and activated rules + + +# cb ( obj ) where obj should contain at least the HTTP response code and a message +exports.processRequest = ( user, obj, cb ) => + if commandFunctions[obj.command] + answ = commandFunctions[obj.command] user, obj, cb + else + cb + code: 404 + message: 'Strange request!' + +commandFunctions = + forge_event_poller: ( user, obj, cb ) => + answ = + code: 200 + + db.getEventPoller obj.id, ( err, mod ) => + if mod + answ.code = 409 + answ.message = 'Event Poller module name already existing: ' + obj.id + + else + src = obj.data + cm = dynmod.compileString src, obj.id, {}, obj.lang + answ = cm.answ + if answ.code is 200 + events = [] + events.push name for name, id of cm.module + @log.info "CM | Storing new eventpoller with events #{ events }" + answ.message = + "Event Poller module successfully stored! Found following event(s): #{ events }" + db.storeEventPoller obj.id, user.username, + code: obj.data + lang: obj.lang + params: obj.params + events: events + if obj.public is 'true' + db.publishEventPoller obj.id + cb answ + + get_event_pollers: ( user, obj, cb ) -> + db.getAvailableEventPollerIds user.username, ( err, obj ) -> + oRes = {} + sem = obj.length + fGetEvents = ( id ) -> + db.getEventPoller id, ( err, obj ) -> + oRes[id] = obj.events + if --sem is 0 + cb + code: 200 + message: oRes + fGetEvents id for id in obj + + get_event_poller_params: ( user, obj, cb ) -> + db.getEventPollerRequiredParams obj.id, ( err, obj ) -> + cb + code: 200 + message: obj + + get_action_invokers: ( user, obj, cb ) -> + db.getAvailableActionInvokerIds user.username, ( err, obj ) -> + oRes = {} + sem = obj.length + fGetActions = ( id ) -> + db.getActionInvoker id, ( err, obj ) -> + oRes[id] = obj.actions + if --sem is 0 + cb + code: 200 + message: oRes + fGetActions id for id in obj + + get_action_invoker_params: ( user, obj, cb ) -> + db.getActionInvokerRequiredParams obj.id, ( err, obj ) -> + cb + code: 200 + message: obj + + forge_action_invoker: ( user, obj, cb ) => + answ = + code: 200 + + db.getActionInvoker obj.id, ( err, mod ) => + if mod + answ.code = 409 + answ.message = 'Action Invoker module name already existing: ' + obj.id + + else + src = obj.data + cm = dynmod.compileString src, obj.id, {}, obj.lang + answ = cm.answ + if answ.code is 200 + actions = [] + actions.push name for name, id of cm.module + @log.info "CM | Storing new eventpoller with actions #{ actions }" + answ.message = + "Action Invoker module successfully stored! Found following action(s): #{ actions }" + db.storeActionInvoker obj.id, user.username, + code: obj.data + lang: obj.lang + params: obj.params + actions: actions + if obj.public is 'true' + db.publishActionInvoker obj.id + cb answ + + get_rules: ( user, obj, cb ) -> + console.log 'CM | Implement get_rules' + + forge_rule: ( user, obj, cb ) => + obj.event = JSON.parse obj.event + console.log obj + db.getRule obj.id, ( err, objRule ) => + if objRule isnt null + answ = + code: 409 + message: 'Rule name already existing!' + else + answ = + code: 200 + message: 'Rule stored and activated!' + rule = + id: obj.id + event: "#{ obj.event.module } -> #{ obj.event.function }" + conditions: JSON.parse obj.conditions + actions: JSON.parse obj.actions + console.log rule + modules = JSON.parse obj.event.action_params + console.log 'store rule' + db.storeRule rule.id, JSON.stringify rule + console.log 'link rule' + db.linkRule rule.id, user.username + console.log 'activate rule' + db.activateRule rule.id, user.username + console.log 'store event params' + db.storeEventUserParams obj.event.module, user.username, obj.event_params + console.log 'store action params' + db.storeActionUserParams id, user.username, params for id, params of modules + #TODO implement ID approach, check for existing + + @ee.emit 'newRule', rule + cb answ + +# 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.info('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); +# } +# }); +# }); +# }); +# }; + + +# exports.storeEventModule = function (objUser, obj, answHandler) { +# try { +# // TODO in the future we might want to link the modules close to the user +# // and allow for e.g. private modules +# // we need a child process to run this code and kill it after invocation +# var m = exports.requireFromString(obj.data, obj.id); +# obj.methods = Object.keys(m); +# answHandler.answerSuccess('Thank you for the event module!'); +# db.storeEventModule(obj.id, obj); +# } catch (err) { +# answHandler.answerError(err.message); +# console.error(err); +# } +# }; + +# exports.getAllEventModules = function ( objUser, obj, answHandler ) { +# db.getEventModules(function(err, obj) { +# if(err) answHandler.answerError('Failed fetching event modules: ' + err.message); +# else answHandler.answerSuccess(obj); +# }); +# }; + +# exports.storeActionModule = function (objUser, obj, answHandler) { +# var m = exports.requireFromString(obj.data, obj.id); +# obj.methods = Object.keys(m); +# answHandler.answerSuccess('Thank you for the action module!'); +# db.storeActionModule(obj.id, obj); +# }; + +# exports.getAllActionModules = function ( objUser, obj, answHandler ) { +# db.getActionModules(function(err, obj) { +# if(err) answHandler.answerError('Failed fetching action modules: ' + err.message); +# else answHandler.answerSuccess(obj); +# }); +# }; + +# exports.storeRule = function (objUser, obj, answHandler) { +# //TODO fix, twice same logic +# var cbEventModule = function (lstParams) { +# return function(err, data) { +# if(err) { +# err.addInfo = 'fetching event module'; +# log.error('MM', err); +# } +# if(!err && data) { +# if(data.params) { +# lstParams.eventmodules[data.id] = data.params; +# } +# } +# if(--semaphore === 0) answHandler.answerSuccess(lstParams); +# }; +# }; +# var cbActionModule = function (lstParams) { +# return function(err, data) { +# if(err) { +# err.addInfo = 'fetching action module'; +# log.error('MM', err); +# } +# if(!err && data) { +# if(data.params) { +# lstParams.actionmodules[data.id] = data.params; +# } +# } +# if(--semaphore === 0) answHandler.answerSuccess(lstParams); +# }; +# }; + +# var semaphore = 1; +# var lst = { +# eventmodules: {}, +# actionmodules: {} +# }; +# try { +# var objRule = JSON.parse(obj.data); +# for(var i = 0; i < objRule.actions.length; i++) { +# semaphore++; +# db.getActionModule(objRule.actions[i].module.split('->')[0], cbActionModule(lst)); +# } +# db.getEventModule(objRule.event.split('->')[0], cbEventModule(lst)); +# db.storeRule(objRule.id, objUser.username, obj.data); +# ee.emit('newRule', objRule); +# // for( var i = 0; i < eventHandlers.length; i++ ) { +# // eventHandlers[i]( objRule ); +# // } +# } catch(err) { +# answHandler.answerError(err.message); +# log.error('MM', err); +# } + +# }; diff --git a/coffee/dynamic-modules.coffee b/coffee/dynamic-modules.coffee new file mode 100644 index 0000000..c60afe6 --- /dev/null +++ b/coffee/dynamic-modules.coffee @@ -0,0 +1,71 @@ +### + +Dynamic Modules +=============== +> Compiles CoffeeScript modules and loads JS modules in a VM, together +> with only a few allowed node.js modules. +### + +# - Node.js Modules: [vm](http://nodejs.org/api/vm.html) and +# [events](http://nodejs.org/api/events.html) +vm = require 'vm' +needle = require 'needle' + +# - External Modules: [coffee-script](http://coffeescript.org/) +cs = require 'coffee-script' + +### +Module call +----------- +Initializes the dynamic module handler. + +@param {Object} args +### +exports = module.exports = ( args ) => + @log = args.logger + module.exports + + +### +Try to run a JS module from a string, together with the +given parameters. If it is written in CoffeeScript we +compile it first into JS. + +@public compileString ( *src, id, params, lang* ) +@param {String} src +@param {String} id +@param {Object} params +@param {String} lang +### +exports.compileString = ( src, id, params, lang ) => + answ = + code: 200 + message: 'Successfully compiled' + src = "'use strict;'\n" + src + if lang is '0' + try + src = cs.compile src + catch err + answ.code = 400 + answ.message = 'Compilation of CoffeeScript failed at line ' + + err.location.first_line + + #FIXME not log but debug module is required to provide information to the user + sandbox = + id: id + params: params + needle: needle + log: @log + exports: {} + #TODO child_process to run module! + #Define max runtime per loop as 10 seconds, after that the child will be killed + #it can still be active after that if there was a timing function or a callback used... + #kill the child each time? how to determine whether there's still a token in the module? + try + vm.runInNewContext src, sandbox, id + '.vm' + catch err + answ.code = 400 + answ.message = 'Loading Module failed: ' + err.message + ret = + answ: answ + module: sandbox.exports \ No newline at end of file diff --git a/coffee/persistence.coffee b/coffee/persistence.coffee index c05c125..847232d 100644 --- a/coffee/persistence.coffee +++ b/coffee/persistence.coffee @@ -44,7 +44,7 @@ exports = module.exports = ( args ) => # Eventually we try to connect to the wrong port, redis will emit an error that we # need to catch and take into account when answering the isConnected function call @db.on 'error', ( err ) => - if err.message.indexOf 'ECONNREFUSED' > -1 + if err.message.indexOf( 'ECONNREFUSED' ) > -1 @connRefused = true @log.error err, 'DB | Wrong port?' @ep = new IndexedModules( 'event-poller', @db, @log ) @@ -161,7 +161,7 @@ decrypt = ( crypticText ) => if !crypticText? then return null; try dec = crypto.AES.decrypt crypticText, @crypto_key - dec.toString(crypto.enc.Utf8) + dec.toString crypto.enc.Utf8 catch err @log.warn err, 'DB | during decryption' null @@ -220,8 +220,9 @@ getSetRecords = ( set, fSingle, cb ) => # Since we retrieved an array of keys, we now execute the fSingle function # on each of them, to retrieve the ata behind the key. Our fCallback function # is used to preprocess the answer to determine correct execution - fSingle reply, fCallback( reply ) for reply in arrReply + fSingle reply, fCallback reply for reply in arrReply +# TODO remove specific functions and allow direct access to instances of this class class IndexedModules constructor: ( @setname, @db, @log ) -> @log.info "DB | Instantiated indexed modules for '#{ @setname }'" @@ -230,12 +231,50 @@ class IndexedModules @log.info "DB | storeModule(#{ @setname }): #{ mId }" @db.sadd "#{ @setname }s", mId, replyHandler "Storing '#{ @setname }' key '#{ mId }'" - @db.set "#{ @setname }:#{ mId }", data, + @db.hmset "#{ @setname }:#{ mId }", data, replyHandler "Storing '#{ @setname }:#{ mId }'" + #TODO add testing + linkModule: ( mId, userId ) => + @log.info "DB | linkModule(#{ @setname }): #{ mId } to #{ userId }" + @db.sadd "#{ @setname }:#{ mId }:users", userId, + replyHandler "Linking '#{ @setname }:#{ mId }:users' #{ userId }" + @db.sadd "user:#{ userId }:#{ @setname }s", mId, + replyHandler "Linking 'user:#{ userId }:#{ @setname }s' #{ mId }" + + #TODO add testing + unlinkModule: ( mId, userId ) => + @log.info "DB | unlinkModule(#{ @setname }): #{ mId } to #{ userId }" + @db.srem "#{ @setname }:#{ mId }:users", userId, + replyHandler "Unlinking '#{ @setname }:#{ mId }:users' #{ userId }" + @db.srem "user:#{ userId }:#{ @setname }s", mId, + replyHandler "Unlinking 'user:#{ userId }:#{ @setname }s' #{ mId }" + + #TODO add testing + publish: ( mId ) => + @log.info "DB | publish(#{ @setname }): #{ mId }" + @db.sadd "public-#{ @setname }s", mId, + replyHandler "Publishing '#{ @setname }' key '#{ mId }'" + + #TODO add testing + unpublish: ( mId ) => + @log.info "DB | unpublish(#{ @setname }): #{ mId }" + @db.srem "public-#{ @setname }s", mId, + replyHandler "Unpublishing '#{ @setname }' key '#{ mId }'" + getModule: ( mId, cb ) => @log.info "DB | getModule('#{ @setname }): #{ mId }'" - @db.get "#{ @setname }:#{ mId }", cb + @db.hgetall "#{ @setname }:#{ mId }", cb + + #TODO add testing + getModuleParams: ( mId, cb ) => + @log.info "DB | getModule('#{ @setname }): #{ mId }'" + @db.hget "#{ @setname }:#{ mId }", "params", cb + + #TODO add testing + getAvailableModuleIds: ( userId, cb ) => + @log.info "DB | getPublicModuleIds(#{ @setname })" + @db.sunion "public-#{ @setname }s", "user:#{ userId }:#{ @setname }s", cb getModuleIds: ( cb ) => @log.info "DB | getModuleIds(#{ @setname })" @@ -251,25 +290,27 @@ class IndexedModules replyHandler "Deleting '#{ @setname }' key '#{ mId }'" @db.del "#{ @setname }:#{ mId }", replyHandler "Deleting '#{ @setname }:#{ mId }'" + #TODO remove published ids + #TODO remove from linked users - storeParameters: ( mId, userId, data ) => - @log.info "DB | storeParameters(#{ @setname }): '#{ mId }:#{ userId }'" + storeUserParameters: ( mId, userId, data ) => + @log.info "DB | storeUserParameters(#{ @setname }): '#{ mId }:#{ userId }'" @db.sadd "#{ @setname }-params", "#{ mId }:#{ userId }", replyHandler "Storing '#{ @setname }' module parameters key '#{ mId }'" - @db.set "#{ @setname }-params:#{ mId }:#{ userId }", encrypt(data), + @db.set "#{ @setname }-params:#{ mId }:#{ userId }", encrypt( data ), replyHandler "Storing '#{ @setname }' module parameters '#{ mId }:#{ userId }'" - getParameters: ( mId, userId, cb ) => - @log.info "DB | getParameters(#{ @setname }): '#{ mId }:#{ userId }'" + getUserParameters: ( mId, userId, cb ) => + @log.info "DB | getUserParameters(#{ @setname }): '#{ mId }:#{ userId }'" @db.get "#{ @setname }-params:#{ mId }:#{ userId }", ( err, data ) -> cb err, decrypt data - getParametersIds: ( cb ) => - @log.info "DB | getParametersIds(#{ @setname })" + getUserParametersIds: ( cb ) => + @log.info "DB | getUserParametersIds(#{ @setname })" @db.smembers "#{ @setname }-params", cb - deleteParameters: ( mId, userId ) => - @log.info "DB | deleteParameters(#{ @setname }): '#{ mId }:#{ userId }'" + deleteUserParameters: ( mId, userId ) => + @log.info "DB | deleteUserParameters(#{ @setname }): '#{ mId }:#{ userId }'" @db.srem "#{ @setname }-params", "#{ mId }:#{ userId }", replyHandler "Deleting '#{ @setname }-params' key '#{ mId }:#{ userId }'" @db.del "#{ @setname }-params:#{ mId }:#{ userId }", @@ -283,12 +324,35 @@ class IndexedModules ### Store a string representation of an action invoker in the DB. -@public storeActionInvoker ( *aiId, data* ) +@public storeActionInvoker ( *aiId, userId, data* ) @param {String} aiId +@param {String} userId @param {String} data ### -exports.storeActionInvoker = ( aiId, data ) => - @ai.storeModule( aiId, data ) +#TODO adapt testing +exports.storeActionInvoker = ( aiId, userId, data ) => + @ai.storeModule aiId, data + @ai.linkModule aiId, userId + +### +Make an action invoker public. + +@public publishActionInvoker ( *aiId* ) +@param {String} aiId +### + +exports.publishActionInvoker = ( aiId ) => + @ai.publish aiId + +### +Make an action invoker private. + +@public unpublishActionInvoker ( *aiId* ) +@param {String} aiId +### + +exports.unpublishActionInvoker = ( aiId ) => + @ai.unpublish aiId ### Query the DB for an action invoker and pass it to cb(err, obj). @@ -300,6 +364,16 @@ Query the DB for an action invoker and pass it to cb(err, obj). exports.getActionInvoker = ( aiId, cb ) => @ai.getModule aiId, cb +### +Query the DB for action invoker required params and pass it to cb(err, obj). + +@public getActionInvokerEventPollerRequiredParams( *epId, cb* ) +@param {String} epId +@param {function} cb +### +exports.getActionInvokerRequiredParams = ( epId, cb ) => + @ai.getModuleParams epId, cb + ### Fetch all action invoker IDs and hand them to cb(err, obj). @@ -309,6 +383,25 @@ Fetch all action invoker IDs and hand them to cb(err, obj). exports.getActionInvokerIds = ( cb ) => @ai.getModuleIds cb +### +Fetch all available actin invoker IDs for a user and +hand them to cb(err, obj). + +@public getAvailableActionInvokerIds( *userId, cb* ) +@param {function} cb +### +exports.getAvailableActionInvokerIds = ( userId, cb ) => + @ai.getAvailableModuleIds userId, cb + +### +Fetch all public action invoker IDs and hand them to cb(err, obj). + +@public getPublicActionInvokerIds( *cb* ) +@param {function} cb +### +exports.getPublicActionInvokerIds = ( cb ) => + @ai.getPublicModuleIds cb + ### Fetch all action invokers and hand them to cb(err, obj). @@ -330,43 +423,43 @@ exports.deleteActionInvoker = ( aiId ) => ### Store user-specific action invoker parameters . -@public storeActionParams( *userId, aiId, data* ) +@public storeActionUserParams( *userId, aiId, data* ) @param {String} userId @param {String} aiId @param {String} data ### -exports.storeActionParams = ( aiId, userId, data ) => - @ai.storeParameters aiId, userId, data +exports.storeActionUserParams = ( aiId, userId, data ) => + @ai.storeUserParameters aiId, userId, data ### Query the DB for user-specific action module parameters, and pass it to cb(err, obj). -@public getActionParams( *userId, aiId, cb* ) +@public getActionUserParams( *userId, aiId, cb* ) @param {String} userId @param {String} aiId @param {function} cb ### -exports.getActionParams = ( aiId, userId, cb ) => - @ai.getParameters aiId, userId, cb +exports.getActionUserParams = ( aiId, userId, cb ) => + @ai.getUserParameters aiId, userId, cb ### Fetch all action params IDs and hand them to cb(err, obj). -@public getActionParamsIds( *cb* ) +@public getActionUserParamsIds( *cb* ) @param {function} cb ### -exports.getActionParamsIds = ( cb ) => - @ai.getParametersIds cb +exports.getActionUserParamsIds = ( cb ) => + @ai.getUserParametersIds cb ### Fetch all action modules and hand them to cb(err, obj). -@public deleteActionParams( *cb* ) +@public deleteActionUserParams( *cb* ) @param {function} cb ### -exports.deleteActionParams = ( aiId, userId ) => - @ai.deleteParameters aiId, userId +exports.deleteActionUserParams = ( aiId, userId ) => + @ai.deleteUserParameters aiId, userId ### @@ -376,12 +469,35 @@ exports.deleteActionParams = ( aiId, userId ) => ### Store a string representation of an event poller in the DB. -@public storeEventPoller ( *epId, data* ) +@public storeEventPoller ( *epId, userId, data* ) @param {String} epId +@param {String} userId @param {String} data ### -exports.storeEventPoller = ( epId, data ) => - @ep.storeModule( epId, data ) +#TODO adapt testing +exports.storeEventPoller = ( epId, userId, data ) => + @ep.storeModule epId, data + @ep.linkModule epId, userId + +### +Make an event poller public. + +@public publishEventPoller ( *epId* ) +@param {String} epId +### + +exports.publishEventPoller = ( epId ) => + @ep.publish epId + +### +Make an event poller private. + +@public unpublishEventPoller ( *epId* ) +@param {String} epId +### + +exports.unpublishEventPoller = ( epId ) => + @ep.unpublish epId ### Query the DB for an event poller and pass it to cb(err, obj). @@ -393,6 +509,16 @@ Query the DB for an event poller and pass it to cb(err, obj). exports.getEventPoller = ( epId, cb ) => @ep.getModule epId, cb +### +Query the DB for event poller required params and pass it to cb(err, obj). + +@public getEventPollerRequiredParams( *epId, cb* ) +@param {String} epId +@param {function} cb +### +exports.getEventPollerRequiredParams = ( epId, cb ) => + @ep.getModuleParams epId, cb + ### Fetch all event poller IDs and hand them to cb(err, obj). @@ -402,6 +528,25 @@ Fetch all event poller IDs and hand them to cb(err, obj). exports.getEventPollerIds = ( cb ) => @ep.getModuleIds cb +### +Fetch all available event poller IDs for a user and +hand them to cb(err, obj). + +@public getAvailableEventPollerIds( *userId, cb* ) +@param {function} cb +### +exports.getAvailableEventPollerIds = ( userId, cb ) => + @ep.getAvailableModuleIds userId, cb + +### +Fetch all public event poller IDs and hand them to cb(err, obj). + +@public getPublicEventPollerIds( *cb* ) +@param {function} cb +### +exports.getPublicEventPollerIds = ( cb ) => + @ep.getPublicModuleIds cb + ### Fetch all event pollers and hand them to cb(err, obj). @@ -418,48 +563,51 @@ Fetch all event pollers and hand them to cb(err, obj). @param {function} cb ### exports.deleteEventPoller = ( epId ) => + # TODO remove from public modules + # TODO remove parameters + # TODO also do this for action invokers @ep.deleteModule epId ### Store user-specific event poller parameters . -@public storeEventParams( *userId, epId, data* ) +@public storeEventUserParams( *userId, epId, data* ) @param {String} userId @param {String} epId @param {String} data ### -exports.storeEventParams = ( epId, userId, data ) => - @ep.storeParameters epId, userId, data +exports.storeEventUserParams = ( epId, userId, data ) => + @ep.storeUserParameters epId, userId, data ### Query the DB for user-specific event module parameters, and pass it to cb(err, obj). -@public getEventParams( *userId, epId, cb* ) +@public getEventUserParams( *userId, epId, cb* ) @param {String} userId @param {String} epId @param {function} cb ### -exports.getEventParams = ( epId, userId, cb ) => - @ep.getParameters epId, userId, cb +exports.getEventUserParams = ( epId, userId, cb ) => + @ep.getUserParameters epId, userId, cb ### Fetch all event params IDs and hand them to cb(err, obj). -@public getEventParamsIds( *cb* ) +@public getEventUserParamsIds( *cb* ) @param {function} cb ### -exports.getEventParamsIds = ( cb ) => - @ep.getParametersIds cb +exports.getEventUserParamsIds = ( cb ) => + @ep.getUserParametersIds cb ### Fetch all event modules and hand them to cb(err, obj). -@public deleteEventParams( *cb* ) +@public deleteEventUserParams( *cb* ) @param {function} cb ### -exports.deleteEventParams = ( epId, userId ) => - @ep.deleteParameters epId, userId +exports.deleteEventUserParams = ( epId, userId ) => + @ep.deleteUserParameters epId, userId ### @@ -528,14 +676,14 @@ exports.deleteRule = ( ruleId ) => delLinkedUserRule = ( userId ) => @db.srem "user:#{ userId }:rules", ruleId, replyHandler "Deleting rule key '#{ ruleId }' in linked user '#{ userId }'" - delLinkedUserRule( id ) for id in obj + delLinkedUserRule id for id in obj @db.del "rule:#{ ruleId }:users", replyHandler "Deleting rule '#{ ruleId }' users" @db.smembers "rule:#{ ruleId }:active-users", ( err, obj ) => delActiveUserRule = ( userId ) => @db.srem "user:#{ userId }:active-rules", ruleId, replyHandler "Deleting rule key '#{ ruleId }' in active user '#{ userId }'" - delActiveUserRule( id ) for id in obj + delActiveUserRule id for id in obj @db.del "rule:#{ ruleId }:active-users", replyHandler "Deleting rule '#{ ruleId }' active users" @@ -659,7 +807,7 @@ exports.getAllActivatedRuleIdsPerUser = ( cb ) => result[userId] = obj if --semaphore is 0 cb null, result - fFetchActiveUserRules(user) for user in obj + fFetchActiveUserRules user for user in obj ### @@ -723,7 +871,7 @@ exports.deleteUser = ( userId ) => delLinkedRuleUser = ( ruleId ) => @db.srem "rule:#{ ruleId }:users", userId, replyHandler "Deleting user key '#{ userId }' in linked rule '#{ ruleId }'" - delLinkedRuleUser( id ) for id in obj + delLinkedRuleUser id for id in obj @db.del "user:#{ userId }:rules", replyHandler "Deleting user '#{ userId }' rules" @@ -732,7 +880,7 @@ exports.deleteUser = ( userId ) => delActivatedRuleUser = ( ruleId ) => @db.srem "rule:#{ ruleId }:active-users", userId, replyHandler "Deleting user key '#{ userId }' in active rule '#{ ruleId }'" - delActivatedRuleUser( id ) for id in obj + delActivatedRuleUser id for id in obj @db.del "user:#{ userId }:active-rules", replyHandler "Deleting user '#{ userId }' rules" @@ -741,7 +889,7 @@ exports.deleteUser = ( userId ) => delRoleUser = ( roleId ) => @db.srem "role:#{ roleId }:users", userId, replyHandler "Deleting user key '#{ userId }' in role '#{ roleId }'" - delRoleUser( id ) for id in obj + delRoleUser id for id in obj @db.del "user:#{ userId }:roles", replyHandler "Deleting user '#{ userId }' roles" diff --git a/coffee/request-handler.coffee b/coffee/request-handler.coffee index f5379af..1f2fa5c 100644 --- a/coffee/request-handler.coffee +++ b/coffee/request-handler.coffee @@ -36,9 +36,12 @@ exports = module.exports = ( args ) => # Register the shutdown handler to the admin command. @objAdminCmds = - shutdown: ( args, answerHandler ) -> - answerHandler.answerSuccess 'Shutting down... BYE!' + shutdown: ( obj, cb ) -> + data = + code: 200 + message: 'Shutting down... BYE!' setTimeout args[ 'shutdown-function' ], 500 + cb null, data db args # Load the standard users from the user config file @@ -63,13 +66,22 @@ exports.handleEvent = ( req, resp ) -> req.on 'data', ( data ) -> body += data req.on 'end', -> - obj = qs.parse body - # If required event properties are present we process the event # - if obj and obj.event and obj.eventid - resp.send 'Thank you for the event: ' + obj.event + ' (' + obj.eventid + ')!' - db.pushEvent obj + if req.session and req.session.user + obj = qs.parse body + # If required event properties are present we process the event # + if obj and obj.event + timestamp = ( new Date ).toISOString() + rand = ( Math.floor Math.random() * 10e9 ).toString( 16 ).toUpperCase() + obj.eventid = "#{ obj.event }_#{ timestamp }_#{ rand }" + answ = + code: 200 + message: "Thank you for the event: #{ obj.eventid }" + resp.send answ.code, answ + db.pushEvent obj + else + resp.send 400, 'Your event was missing important parameters!' else - resp.send 400, 'Your event was missing important parameters!' + resp.send 401, 'Please login!' ### @@ -170,6 +182,7 @@ renderPage = ( name, req, resp, msg ) -> code = 200 data = message: msg + user: req.session.user # Try to grab the script belonging to this page. But don't bother if it's not existing try @@ -187,21 +200,22 @@ renderPage = ( name, req, resp, msg ) -> content = getTemplate 'error' script = getScript 'error' code = 404 - data = - message: 'Invalid Page!' - - content = mustache.render content, data + data.message = 'Invalid Page!' if req.session.user menubar = getTemplate 'menubar' - view = - user: req.session.user, - content: content, - script: script, - remote_scripts: remote_scripts, + pageElements = + content: content + script: script + remote_scripts: remote_scripts menubar: menubar - resp.send code, mustache.render skeleton, view + + # First we render the page by including all page elements into the skeleton + page = mustache.render skeleton, pageElements + + # Then we complete the rendering by adding the data, and send the result to the user + resp.send code, mustache.render page, data ### Present the desired forge page to the user. @@ -230,19 +244,17 @@ objects.* @public handleUser( *req, resp* ) ### exports.handleUserCommand = ( req, resp ) => - if not req.session or not req.session.user - resp.send 401, 'Login first!' - else + if req.session and req.session.user body = '' req.on 'data', ( data ) -> body += data req.on 'end', => obj = qs.parse body - @userRequestHandler req.session.user, obj, ( err, obj ) -> - if err - resp.send 404, 'Rethink your request!' - else - resp.send obj + @userRequestHandler req.session.user, obj, ( obj ) -> + resp.send obj.code, obj + else + resp.send 401, 'Login first!' + ### Present the admin console to the user if he's allowed to see it. @@ -276,46 +288,20 @@ objects.* @public handleAdminCommand( *req, resp* ) ### exports.handleAdminCommand = ( req, resp ) => - if req.session and req.session.user - if req.session.user.isAdmin is "true" - q = req.query - @log.info 'RH | Received admin request: ' + req.originalUrl - if q.cmd - @objAdminCmds[q.cmd]? q, answerHandler req, resp, true + if req.session and + req.session.user and + req.session.user.isAdmin is "true" + body = '' + req.on 'data', ( data ) -> + body += data + req.on 'end', => + obj = qs.parse body + @log.info 'RH | Received admin request: ' + obj.command + if obj.command and @objAdminCmds[obj.command] + @objAdminCmds[obj.command] obj, ( err, obj ) -> + resp.send obj.code, obj else resp.send 404, 'Command unknown!' - else - resp.send renderPage 'unauthorized', req.session else - resp.sendfile getHandlerPath 'login' - - -#TODO remove this ugly legacy blob -answerHandler = (req, resp, ntbr) -> - request = req - response = resp - needsToBeRendered = ntbr - hasBeenAnswered = false - ret = - answerSuccess: (msg) -> - if not hasBeenAnswered - if needsToBeRendered - response.send renderPage 'command_answer', request.session, msg - else - response.send msg - hasBeenAnswered = true - , - answerError: (msg) -> - if not hasBeenAnswered - if needsToBeRendered - response.send 400, renderPage 'error', request.session, msg - else - response.send 400, msg - hasBeenAnswered = true - , - isAnswered: -> hasBeenAnswered - setTimeout(() -> - ret.answerError 'Strange... maybe try again?' - , 5000) - ret - + resp.send 401, 'You need to be logged in as admin!' + diff --git a/js-coffee/components-manager.js b/js-coffee/components-manager.js index d1025ab..32ef44d 100644 --- a/js-coffee/components-manager.js +++ b/js-coffee/components-manager.js @@ -1,204 +1,246 @@ +// Generated by CoffeeScript 1.6.3 /* -# 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 - */ +Components Manager +================== +> The components manager takes care of the dynamic JS modules and the rules. +> Event Poller and Action Invoker modules are loaded as strings and stored in the database, +> then compiled into node modules and rules and used in the engine and event poller. +*/ -// # **Loads Modules:** +(function() { + var commandFunctions, db, dynmod, events, exports, fs, path, vm, + _this = this; -// # - [Persistence](persistence.html) -var fs = require('fs'), - path = require('path'), - db = require('./persistence'), - events = require('events'), - log, ee, - eventHandlers = [], - funcLoadAction, funcLoadRule; + db = require('./persistence'); -exports = module.exports = function(args) { - args = args || {}; - ee = new events.EventEmitter(); - log = args.logger; - db(args); - return module.exports; -}; + dynmod = require('./dynamic-modules'); -exports.addListener = function( evt, eh ) { - ee.addListener( evt, eh ); - //TODO as soon as an event handler is added it needs to receive the full list of existing and activated rules -} + fs = require('fs'); -exports.processRequest = function( user, obj, cb ) { - console.log('module manager needs to process request: '); - console.log(obj.command); - var answ = { - test: 'object', - should: 'work' - } - cb(null, answ); -} + vm = require('vm'); -exports.requireFromString = function(src, name, dir) { - if(!dir) dir = __dirname; - var id = path.resolve(dir, name, name + '.vm'); - var vm = require('vm'), - // FIXME not log but debug module is required to provide information to the user - sandbox = { - id: id, // use this to gather kill info - needle: require('needle'), //https://github.com/tomas/needle - log: log, - exports: {} - }; - //TODO child_process to run module! - // Define max runtime per loop as 10 seconds, after that the child will be killed - // it can still be active after that if there was a timing function or a callback used... - // kill the child each time? how to determine whether there's still a token in the module? - try { - var mod = vm.runInNewContext(src, sandbox, id); - - } catch (err) { - log.error('ML', 'Error running module in sandbox: ' + err.message); - } - return sandbox.exports; -}; + path = require('path'); -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 + '"'); - } -}; + events = require('events'); -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.info('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); - } - }); - }); - }); -}; - - -exports.storeEventModule = function (objUser, obj, answHandler) { - try { - // TODO in the future we might want to link the modules close to the user - // and allow for e.g. private modules - // we need a child process to run this code and kill it after invocation - var m = exports.requireFromString(obj.data, obj.id); - obj.methods = Object.keys(m); - answHandler.answerSuccess('Thank you for the event module!'); - db.storeEventModule(obj.id, obj); - } catch (err) { - answHandler.answerError(err.message); - console.error(err); - } -}; - -exports.getAllEventModules = function ( objUser, obj, answHandler ) { - db.getEventModules(function(err, obj) { - if(err) answHandler.answerError('Failed fetching event modules: ' + err.message); - else answHandler.answerSuccess(obj); - }); -}; - -exports.storeActionModule = function (objUser, obj, answHandler) { - var m = exports.requireFromString(obj.data, obj.id); - obj.methods = Object.keys(m); - answHandler.answerSuccess('Thank you for the action module!'); - db.storeActionModule(obj.id, obj); -}; - -exports.getAllActionModules = function ( objUser, obj, answHandler ) { - db.getActionModules(function(err, obj) { - if(err) answHandler.answerError('Failed fetching action modules: ' + err.message); - else answHandler.answerSuccess(obj); - }); -}; - -exports.storeRule = function (objUser, obj, answHandler) { - //TODO fix, twice same logic - var cbEventModule = function (lstParams) { - return function(err, data) { - if(err) { - err.addInfo = 'fetching event module'; - log.error('MM', err); - } - if(!err && data) { - if(data.params) { - lstParams.eventmodules[data.id] = data.params; - } - } - if(--semaphore === 0) answHandler.answerSuccess(lstParams); - }; - }; - var cbActionModule = function (lstParams) { - return function(err, data) { - if(err) { - err.addInfo = 'fetching action module'; - log.error('MM', err); - } - if(!err && data) { - if(data.params) { - lstParams.actionmodules[data.id] = data.params; - } - } - if(--semaphore === 0) answHandler.answerSuccess(lstParams); - }; - }; + /* + Module call + ----------- + Initializes the HTTP listener and its request handler. - var semaphore = 1; - var lst = { - eventmodules: {}, - actionmodules: {} - }; - try { - var objRule = JSON.parse(obj.data); - for(var i = 0; i < objRule.actions.length; i++) { - semaphore++; - db.getActionModule(objRule.actions[i].module.split('->')[0], cbActionModule(lst)); - } - db.getEventModule(objRule.event.split('->')[0], cbEventModule(lst)); - db.storeRule(objRule.id, objUser.username, obj.data); - ee.emit('newRule', objRule); - // for( var i = 0; i < eventHandlers.length; i++ ) { - // eventHandlers[i]( objRule ); - // } - } catch(err) { - answHandler.answerError(err.message); - log.error('MM', err); - } + @param {Object} args + */ -}; + + exports = module.exports = function(args) { + _this.log = args.logger; + _this.ee = new events.EventEmitter(); + db(args); + dynmod(args); + return module.exports; + }; + + exports.addListener = function(evt, eh) { + return _this.ee.addListener(evt, eh); + }; + + exports.processRequest = function(user, obj, cb) { + var answ; + if (commandFunctions[obj.command]) { + return answ = commandFunctions[obj.command](user, obj, cb); + } else { + return cb({ + code: 404, + message: 'Strange request!' + }); + } + }; + + commandFunctions = { + forge_event_poller: function(user, obj, cb) { + var answ; + answ = { + code: 200 + }; + return db.getEventPoller(obj.id, function(err, mod) { + var cm, id, name, src, _ref; + if (mod) { + answ.code = 409; + answ.message = 'Event Poller module name already existing: ' + obj.id; + } else { + src = obj.data; + cm = dynmod.compileString(src, obj.id, {}, obj.lang); + answ = cm.answ; + if (answ.code === 200) { + events = []; + _ref = cm.module; + for (name in _ref) { + id = _ref[name]; + events.push(name); + } + _this.log.info("CM | Storing new eventpoller with events " + events); + answ.message = "Event Poller module successfully stored! Found following event(s): " + events; + db.storeEventPoller(obj.id, user.username, { + code: obj.data, + lang: obj.lang, + params: obj.params, + events: events + }); + if (obj["public"] === 'true') { + db.publishEventPoller(obj.id); + } + } + } + return cb(answ); + }); + }, + get_event_pollers: function(user, obj, cb) { + return db.getAvailableEventPollerIds(user.username, function(err, obj) { + var fGetEvents, id, oRes, sem, _i, _len, _results; + oRes = {}; + sem = obj.length; + fGetEvents = function(id) { + return db.getEventPoller(id, function(err, obj) { + oRes[id] = obj.events; + if (--sem === 0) { + return cb({ + code: 200, + message: oRes + }); + } + }); + }; + _results = []; + for (_i = 0, _len = obj.length; _i < _len; _i++) { + id = obj[_i]; + _results.push(fGetEvents(id)); + } + return _results; + }); + }, + get_event_poller_params: function(user, obj, cb) { + return db.getEventPollerRequiredParams(obj.id, function(err, obj) { + return cb({ + code: 200, + message: obj + }); + }); + }, + get_action_invokers: function(user, obj, cb) { + return db.getAvailableActionInvokerIds(user.username, function(err, obj) { + var fGetActions, id, oRes, sem, _i, _len, _results; + oRes = {}; + sem = obj.length; + fGetActions = function(id) { + return db.getActionInvoker(id, function(err, obj) { + oRes[id] = obj.actions; + if (--sem === 0) { + return cb({ + code: 200, + message: oRes + }); + } + }); + }; + _results = []; + for (_i = 0, _len = obj.length; _i < _len; _i++) { + id = obj[_i]; + _results.push(fGetActions(id)); + } + return _results; + }); + }, + get_action_invoker_params: function(user, obj, cb) { + return db.getActionInvokerRequiredParams(obj.id, function(err, obj) { + return cb({ + code: 200, + message: obj + }); + }); + }, + forge_action_invoker: function(user, obj, cb) { + var answ; + answ = { + code: 200 + }; + return db.getActionInvoker(obj.id, function(err, mod) { + var actions, cm, id, name, src, _ref; + if (mod) { + answ.code = 409; + answ.message = 'Action Invoker module name already existing: ' + obj.id; + } else { + src = obj.data; + cm = dynmod.compileString(src, obj.id, {}, obj.lang); + answ = cm.answ; + if (answ.code === 200) { + actions = []; + _ref = cm.module; + for (name in _ref) { + id = _ref[name]; + actions.push(name); + } + _this.log.info("CM | Storing new eventpoller with actions " + actions); + answ.message = "Action Invoker module successfully stored! Found following action(s): " + actions; + db.storeActionInvoker(obj.id, user.username, { + code: obj.data, + lang: obj.lang, + params: obj.params, + actions: actions + }); + if (obj["public"] === 'true') { + db.publishActionInvoker(obj.id); + } + } + } + return cb(answ); + }); + }, + get_rules: function(user, obj, cb) { + return console.log('CM | Implement get_rules'); + }, + forge_rule: function(user, obj, cb) { + obj.event = JSON.parse(obj.event); + console.log(obj); + return db.getRule(obj.id, function(err, objRule) { + var answ, id, modules, params, rule; + if (objRule !== null) { + answ = { + code: 409, + message: 'Rule name already existing!' + }; + } else { + answ = { + code: 200, + message: 'Rule stored and activated!' + }; + rule = { + id: obj.id, + event: "" + obj.event.module + " -> " + obj.event["function"], + conditions: JSON.parse(obj.conditions), + actions: JSON.parse(obj.actions) + }; + console.log(rule); + modules = JSON.parse(obj.event.action_params); + console.log('store rule'); + db.storeRule(rule.id, JSON.stringify(rule)); + console.log('link rule'); + db.linkRule(rule.id, user.username); + console.log('activate rule'); + db.activateRule(rule.id, user.username); + console.log('store event params'); + db.storeEventUserParams(obj.event.module, user.username, obj.event_params); + console.log('store action params'); + for (id in modules) { + params = modules[id]; + db.storeActionUserParams(id, user.username, params); + } + _this.ee.emit('newRule', rule); + } + return cb(answ); + }); + } + }; + +}).call(this); diff --git a/js-coffee/dynamic-modules.js b/js-coffee/dynamic-modules.js new file mode 100644 index 0000000..6cfa83b --- /dev/null +++ b/js-coffee/dynamic-modules.js @@ -0,0 +1,84 @@ +// Generated by CoffeeScript 1.6.3 +/* + +Dynamic Modules +=============== +> Compiles CoffeeScript modules and loads JS modules in a VM, together +> with only a few allowed node.js modules. +*/ + + +(function() { + var cs, exports, needle, vm, + _this = this; + + vm = require('vm'); + + needle = require('needle'); + + cs = require('coffee-script'); + + /* + Module call + ----------- + Initializes the dynamic module handler. + + @param {Object} args + */ + + + exports = module.exports = function(args) { + _this.log = args.logger; + return module.exports; + }; + + /* + Try to run a JS module from a string, together with the + given parameters. If it is written in CoffeeScript we + compile it first into JS. + + @public compileString ( *src, id, params, lang* ) + @param {String} src + @param {String} id + @param {Object} params + @param {String} lang + */ + + + exports.compileString = function(src, id, params, lang) { + var answ, err, ret, sandbox; + answ = { + code: 200, + message: 'Successfully compiled' + }; + src = "'use strict;'\n" + src; + if (lang === '0') { + try { + src = cs.compile(src); + } catch (_error) { + err = _error; + answ.code = 400; + answ.message = 'Compilation of CoffeeScript failed at line ' + err.location.first_line; + } + } + sandbox = { + id: id, + params: params, + needle: needle, + log: _this.log, + exports: {} + }; + try { + vm.runInNewContext(src, sandbox, id + '.vm'); + } catch (_error) { + err = _error; + answ.code = 400; + answ.message = 'Loading Module failed: ' + err.message; + } + return ret = { + answ: answ, + module: sandbox.exports + }; + }; + +}).call(this); diff --git a/js-coffee/persistence.js b/js-coffee/persistence.js index e689166..0a27248 100644 --- a/js-coffee/persistence.js +++ b/js-coffee/persistence.js @@ -49,7 +49,7 @@ Persistence connect_timeout: 2000 }); _this.db.on('error', function(err) { - if (err.message.indexOf('ECONNREFUSED' > -1)) { + if (err.message.indexOf('ECONNREFUSED') > -1) { _this.connRefused = true; return _this.log.error(err, 'DB | Wrong port?'); } @@ -284,14 +284,20 @@ Persistence this.setname = setname; this.db = db; this.log = log; - this.deleteParameters = __bind(this.deleteParameters, this); - this.getParametersIds = __bind(this.getParametersIds, this); - this.getParameters = __bind(this.getParameters, this); - this.storeParameters = __bind(this.storeParameters, this); + this.deleteUserParameters = __bind(this.deleteUserParameters, this); + this.getUserParametersIds = __bind(this.getUserParametersIds, this); + this.getUserParameters = __bind(this.getUserParameters, this); + this.storeUserParameters = __bind(this.storeUserParameters, this); this.deleteModule = __bind(this.deleteModule, this); this.getModules = __bind(this.getModules, this); this.getModuleIds = __bind(this.getModuleIds, this); + this.getAvailableModuleIds = __bind(this.getAvailableModuleIds, this); + this.getModuleParams = __bind(this.getModuleParams, this); this.getModule = __bind(this.getModule, this); + this.unpublish = __bind(this.unpublish, this); + this.publish = __bind(this.publish, this); + this.unlinkModule = __bind(this.unlinkModule, this); + this.linkModule = __bind(this.linkModule, this); this.storeModule = __bind(this.storeModule, this); this.log.info("DB | Instantiated indexed modules for '" + this.setname + "'"); } @@ -299,12 +305,44 @@ Persistence IndexedModules.prototype.storeModule = function(mId, data) { this.log.info("DB | storeModule(" + this.setname + "): " + mId); this.db.sadd("" + this.setname + "s", mId, replyHandler("Storing '" + this.setname + "' key '" + mId + "'")); - return this.db.set("" + this.setname + ":" + mId, data, replyHandler("Storing '" + this.setname + ":" + mId + "'")); + return this.db.hmset("" + this.setname + ":" + mId, data, replyHandler("Storing '" + this.setname + ":" + mId + "'")); + }; + + IndexedModules.prototype.linkModule = function(mId, userId) { + this.log.info("DB | linkModule(" + this.setname + "): " + mId + " to " + userId); + this.db.sadd("" + this.setname + ":" + mId + ":users", userId, replyHandler("Linking '" + this.setname + ":" + mId + ":users' " + userId)); + return this.db.sadd("user:" + userId + ":" + this.setname + "s", mId, replyHandler("Linking 'user:" + userId + ":" + this.setname + "s' " + mId)); + }; + + IndexedModules.prototype.unlinkModule = function(mId, userId) { + this.log.info("DB | unlinkModule(" + this.setname + "): " + mId + " to " + userId); + this.db.srem("" + this.setname + ":" + mId + ":users", userId, replyHandler("Unlinking '" + this.setname + ":" + mId + ":users' " + userId)); + return this.db.srem("user:" + userId + ":" + this.setname + "s", mId, replyHandler("Unlinking 'user:" + userId + ":" + this.setname + "s' " + mId)); + }; + + IndexedModules.prototype.publish = function(mId) { + this.log.info("DB | publish(" + this.setname + "): " + mId); + return this.db.sadd("public-" + this.setname + "s", mId, replyHandler("Publishing '" + this.setname + "' key '" + mId + "'")); + }; + + IndexedModules.prototype.unpublish = function(mId) { + this.log.info("DB | unpublish(" + this.setname + "): " + mId); + return this.db.srem("public-" + this.setname + "s", mId, replyHandler("Unpublishing '" + this.setname + "' key '" + mId + "'")); }; IndexedModules.prototype.getModule = function(mId, cb) { this.log.info("DB | getModule('" + this.setname + "): " + mId + "'"); - return this.db.get("" + this.setname + ":" + mId, cb); + return this.db.hgetall("" + this.setname + ":" + mId, cb); + }; + + IndexedModules.prototype.getModuleParams = function(mId, cb) { + this.log.info("DB | getModule('" + this.setname + "): " + mId + "'"); + return this.db.hget("" + this.setname + ":" + mId, "params", cb); + }; + + IndexedModules.prototype.getAvailableModuleIds = function(userId, cb) { + this.log.info("DB | getPublicModuleIds(" + this.setname + ")"); + return this.db.sunion("public-" + this.setname + "s", "user:" + userId + ":" + this.setname + "s", cb); }; IndexedModules.prototype.getModuleIds = function(cb) { @@ -323,26 +361,26 @@ Persistence return this.db.del("" + this.setname + ":" + mId, replyHandler("Deleting '" + this.setname + ":" + mId + "'")); }; - IndexedModules.prototype.storeParameters = function(mId, userId, data) { - this.log.info("DB | storeParameters(" + this.setname + "): '" + mId + ":" + userId + "'"); + IndexedModules.prototype.storeUserParameters = function(mId, userId, data) { + this.log.info("DB | storeUserParameters(" + this.setname + "): '" + mId + ":" + userId + "'"); this.db.sadd("" + this.setname + "-params", "" + mId + ":" + userId, replyHandler("Storing '" + this.setname + "' module parameters key '" + mId + "'")); return this.db.set("" + this.setname + "-params:" + mId + ":" + userId, encrypt(data), replyHandler("Storing '" + this.setname + "' module parameters '" + mId + ":" + userId + "'")); }; - IndexedModules.prototype.getParameters = function(mId, userId, cb) { - this.log.info("DB | getParameters(" + this.setname + "): '" + mId + ":" + userId + "'"); + IndexedModules.prototype.getUserParameters = function(mId, userId, cb) { + this.log.info("DB | getUserParameters(" + this.setname + "): '" + mId + ":" + userId + "'"); return this.db.get("" + this.setname + "-params:" + mId + ":" + userId, function(err, data) { return cb(err, decrypt(data)); }); }; - IndexedModules.prototype.getParametersIds = function(cb) { - this.log.info("DB | getParametersIds(" + this.setname + ")"); + IndexedModules.prototype.getUserParametersIds = function(cb) { + this.log.info("DB | getUserParametersIds(" + this.setname + ")"); return this.db.smembers("" + this.setname + "-params", cb); }; - IndexedModules.prototype.deleteParameters = function(mId, userId) { - this.log.info("DB | deleteParameters(" + this.setname + "): '" + mId + ":" + userId + "'"); + IndexedModules.prototype.deleteUserParameters = function(mId, userId) { + this.log.info("DB | deleteUserParameters(" + this.setname + "): '" + mId + ":" + userId + "'"); this.db.srem("" + this.setname + "-params", "" + mId + ":" + userId, replyHandler("Deleting '" + this.setname + "-params' key '" + mId + ":" + userId + "'")); return this.db.del("" + this.setname + "-params:" + mId + ":" + userId, replyHandler("Deleting '" + this.setname + "-params:" + mId + ":" + userId + "'")); }; @@ -359,14 +397,40 @@ Persistence /* Store a string representation of an action invoker in the DB. - @public storeActionInvoker ( *aiId, data* ) + @public storeActionInvoker ( *aiId, userId, data* ) @param {String} aiId + @param {String} userId @param {String} data */ - exports.storeActionInvoker = function(aiId, data) { - return _this.ai.storeModule(aiId, data); + exports.storeActionInvoker = function(aiId, userId, data) { + _this.ai.storeModule(aiId, data); + return _this.ai.linkModule(aiId, userId); + }; + + /* + Make an action invoker public. + + @public publishActionInvoker ( *aiId* ) + @param {String} aiId + */ + + + exports.publishActionInvoker = function(aiId) { + return _this.ai.publish(aiId); + }; + + /* + Make an action invoker private. + + @public unpublishActionInvoker ( *aiId* ) + @param {String} aiId + */ + + + exports.unpublishActionInvoker = function(aiId) { + return _this.ai.unpublish(aiId); }; /* @@ -382,6 +446,19 @@ Persistence return _this.ai.getModule(aiId, cb); }; + /* + Query the DB for action invoker required params and pass it to cb(err, obj). + + @public getActionInvokerEventPollerRequiredParams( *epId, cb* ) + @param {String} epId + @param {function} cb + */ + + + exports.getActionInvokerRequiredParams = function(epId, cb) { + return _this.ai.getModuleParams(epId, cb); + }; + /* Fetch all action invoker IDs and hand them to cb(err, obj). @@ -394,6 +471,31 @@ Persistence return _this.ai.getModuleIds(cb); }; + /* + Fetch all available actin invoker IDs for a user and + hand them to cb(err, obj). + + @public getAvailableActionInvokerIds( *userId, cb* ) + @param {function} cb + */ + + + exports.getAvailableActionInvokerIds = function(userId, cb) { + return _this.ai.getAvailableModuleIds(userId, cb); + }; + + /* + Fetch all public action invoker IDs and hand them to cb(err, obj). + + @public getPublicActionInvokerIds( *cb* ) + @param {function} cb + */ + + + exports.getPublicActionInvokerIds = function(cb) { + return _this.ai.getPublicModuleIds(cb); + }; + /* Fetch all action invokers and hand them to cb(err, obj). @@ -421,54 +523,54 @@ Persistence /* Store user-specific action invoker parameters . - @public storeActionParams( *userId, aiId, data* ) + @public storeActionUserParams( *userId, aiId, data* ) @param {String} userId @param {String} aiId @param {String} data */ - exports.storeActionParams = function(aiId, userId, data) { - return _this.ai.storeParameters(aiId, userId, data); + exports.storeActionUserParams = function(aiId, userId, data) { + return _this.ai.storeUserParameters(aiId, userId, data); }; /* Query the DB for user-specific action module parameters, and pass it to cb(err, obj). - @public getActionParams( *userId, aiId, cb* ) + @public getActionUserParams( *userId, aiId, cb* ) @param {String} userId @param {String} aiId @param {function} cb */ - exports.getActionParams = function(aiId, userId, cb) { - return _this.ai.getParameters(aiId, userId, cb); + exports.getActionUserParams = function(aiId, userId, cb) { + return _this.ai.getUserParameters(aiId, userId, cb); }; /* Fetch all action params IDs and hand them to cb(err, obj). - @public getActionParamsIds( *cb* ) + @public getActionUserParamsIds( *cb* ) @param {function} cb */ - exports.getActionParamsIds = function(cb) { - return _this.ai.getParametersIds(cb); + exports.getActionUserParamsIds = function(cb) { + return _this.ai.getUserParametersIds(cb); }; /* Fetch all action modules and hand them to cb(err, obj). - @public deleteActionParams( *cb* ) + @public deleteActionUserParams( *cb* ) @param {function} cb */ - exports.deleteActionParams = function(aiId, userId) { - return _this.ai.deleteParameters(aiId, userId); + exports.deleteActionUserParams = function(aiId, userId) { + return _this.ai.deleteUserParameters(aiId, userId); }; /* @@ -479,14 +581,40 @@ Persistence /* Store a string representation of an event poller in the DB. - @public storeEventPoller ( *epId, data* ) + @public storeEventPoller ( *epId, userId, data* ) @param {String} epId + @param {String} userId @param {String} data */ - exports.storeEventPoller = function(epId, data) { - return _this.ep.storeModule(epId, data); + exports.storeEventPoller = function(epId, userId, data) { + _this.ep.storeModule(epId, data); + return _this.ep.linkModule(epId, userId); + }; + + /* + Make an event poller public. + + @public publishEventPoller ( *epId* ) + @param {String} epId + */ + + + exports.publishEventPoller = function(epId) { + return _this.ep.publish(epId); + }; + + /* + Make an event poller private. + + @public unpublishEventPoller ( *epId* ) + @param {String} epId + */ + + + exports.unpublishEventPoller = function(epId) { + return _this.ep.unpublish(epId); }; /* @@ -502,6 +630,19 @@ Persistence return _this.ep.getModule(epId, cb); }; + /* + Query the DB for event poller required params and pass it to cb(err, obj). + + @public getEventPollerRequiredParams( *epId, cb* ) + @param {String} epId + @param {function} cb + */ + + + exports.getEventPollerRequiredParams = function(epId, cb) { + return _this.ep.getModuleParams(epId, cb); + }; + /* Fetch all event poller IDs and hand them to cb(err, obj). @@ -514,6 +655,31 @@ Persistence return _this.ep.getModuleIds(cb); }; + /* + Fetch all available event poller IDs for a user and + hand them to cb(err, obj). + + @public getAvailableEventPollerIds( *userId, cb* ) + @param {function} cb + */ + + + exports.getAvailableEventPollerIds = function(userId, cb) { + return _this.ep.getAvailableModuleIds(userId, cb); + }; + + /* + Fetch all public event poller IDs and hand them to cb(err, obj). + + @public getPublicEventPollerIds( *cb* ) + @param {function} cb + */ + + + exports.getPublicEventPollerIds = function(cb) { + return _this.ep.getPublicModuleIds(cb); + }; + /* Fetch all event pollers and hand them to cb(err, obj). @@ -541,54 +707,54 @@ Persistence /* Store user-specific event poller parameters . - @public storeEventParams( *userId, epId, data* ) + @public storeEventUserParams( *userId, epId, data* ) @param {String} userId @param {String} epId @param {String} data */ - exports.storeEventParams = function(epId, userId, data) { - return _this.ep.storeParameters(epId, userId, data); + exports.storeEventUserParams = function(epId, userId, data) { + return _this.ep.storeUserParameters(epId, userId, data); }; /* Query the DB for user-specific event module parameters, and pass it to cb(err, obj). - @public getEventParams( *userId, epId, cb* ) + @public getEventUserParams( *userId, epId, cb* ) @param {String} userId @param {String} epId @param {function} cb */ - exports.getEventParams = function(epId, userId, cb) { - return _this.ep.getParameters(epId, userId, cb); + exports.getEventUserParams = function(epId, userId, cb) { + return _this.ep.getUserParameters(epId, userId, cb); }; /* Fetch all event params IDs and hand them to cb(err, obj). - @public getEventParamsIds( *cb* ) + @public getEventUserParamsIds( *cb* ) @param {function} cb */ - exports.getEventParamsIds = function(cb) { - return _this.ep.getParametersIds(cb); + exports.getEventUserParamsIds = function(cb) { + return _this.ep.getUserParametersIds(cb); }; /* Fetch all event modules and hand them to cb(err, obj). - @public deleteEventParams( *cb* ) + @public deleteEventUserParams( *cb* ) @param {function} cb */ - exports.deleteEventParams = function(epId, userId) { - return _this.ep.deleteParameters(epId, userId); + exports.deleteEventUserParams = function(epId, userId) { + return _this.ep.deleteUserParameters(epId, userId); }; /* diff --git a/js-coffee/request-handler.js b/js-coffee/request-handler.js index 3c2e7be..a3634b1 100644 --- a/js-coffee/request-handler.js +++ b/js-coffee/request-handler.js @@ -11,7 +11,7 @@ Request Handler (function() { - var answerHandler, crypto, db, dirHandlers, exports, fs, getHandlerPath, getRemoteScripts, getScript, getTemplate, mustache, path, qs, renderPage, + var crypto, db, dirHandlers, exports, fs, getHandlerPath, getRemoteScripts, getScript, getTemplate, mustache, path, qs, renderPage, _this = this; db = require('./persistence'); @@ -33,9 +33,14 @@ Request Handler _this.log = args.logger; _this.userRequestHandler = args['request-service']; _this.objAdminCmds = { - shutdown: function(args, answerHandler) { - answerHandler.answerSuccess('Shutting down... BYE!'); - return setTimeout(args['shutdown-function'], 500); + shutdown: function(obj, cb) { + var data; + data = { + code: 200, + message: 'Shutting down... BYE!' + }; + setTimeout(args['shutdown-function'], 500); + return cb(null, data); } }; db(args); @@ -67,13 +72,24 @@ Request Handler return body += data; }); return req.on('end', function() { - var obj; - obj = qs.parse(body); - if (obj && obj.event && obj.eventid) { - resp.send('Thank you for the event: ' + obj.event + ' (' + obj.eventid + ')!'); - return db.pushEvent(obj); + var answ, obj, rand, timestamp; + if (req.session && req.session.user) { + obj = qs.parse(body); + if (obj && obj.event) { + timestamp = (new Date).toISOString(); + rand = (Math.floor(Math.random() * 10e9)).toString(16).toUpperCase(); + obj.eventid = "" + obj.event + "_" + timestamp + "_" + rand; + answ = { + code: 200, + message: "Thank you for the event: " + obj.eventid + }; + resp.send(answ.code, answ); + return db.pushEvent(obj); + } else { + return resp.send(400, 'Your event was missing important parameters!'); + } } else { - return resp.send(400, 'Your event was missing important parameters!'); + return resp.send(401, 'Please login!'); } }); }; @@ -199,12 +215,13 @@ Request Handler renderPage = function(name, req, resp, msg) { - var code, content, data, err, menubar, pathSkel, remote_scripts, script, skeleton, view; + var code, content, data, err, menubar, page, pageElements, pathSkel, remote_scripts, script, skeleton; pathSkel = path.join(dirHandlers, 'skeleton.html'); skeleton = fs.readFileSync(pathSkel, 'utf8'); code = 200; data = { - message: msg + message: msg, + user: req.session.user }; try { script = getScript(name); @@ -219,22 +236,19 @@ Request Handler content = getTemplate('error'); script = getScript('error'); code = 404; - data = { - message: 'Invalid Page!' - }; + data.message = 'Invalid Page!'; } - content = mustache.render(content, data); if (req.session.user) { menubar = getTemplate('menubar'); } - view = { - user: req.session.user, + pageElements = { content: content, script: script, remote_scripts: remote_scripts, menubar: menubar }; - return resp.send(code, mustache.render(skeleton, view)); + page = mustache.render(skeleton, pageElements); + return resp.send(code, mustache.render(page, data)); }; /* @@ -272,9 +286,7 @@ Request Handler exports.handleUserCommand = function(req, resp) { var body; - if (!req.session || !req.session.user) { - return resp.send(401, 'Login first!'); - } else { + if (req.session && req.session.user) { body = ''; req.on('data', function(data) { return body += data; @@ -282,14 +294,12 @@ Request Handler return req.on('end', function() { var obj; obj = qs.parse(body); - return _this.userRequestHandler(req.session.user, obj, function(err, obj) { - if (err) { - return resp.send(404, 'Rethink your request!'); - } else { - return resp.send(obj); - } + return _this.userRequestHandler(req.session.user, obj, function(obj) { + return resp.send(obj.code, obj); }); }); + } else { + return resp.send(401, 'Login first!'); } }; @@ -331,59 +341,27 @@ Request Handler exports.handleAdminCommand = function(req, resp) { - var q, _base, _name; - if (req.session && req.session.user) { - if (req.session.user.isAdmin === "true") { - q = req.query; - _this.log.info('RH | Received admin request: ' + req.originalUrl); - if (q.cmd) { - return typeof (_base = _this.objAdminCmds)[_name = q.cmd] === "function" ? _base[_name](q, answerHandler(req, resp, true)) : void 0; + var body; + if (req.session && req.session.user && req.session.user.isAdmin === "true") { + body = ''; + req.on('data', function(data) { + return body += data; + }); + return req.on('end', function() { + var obj; + obj = qs.parse(body); + _this.log.info('RH | Received admin request: ' + obj.command); + if (obj.command && _this.objAdminCmds[obj.command]) { + return _this.objAdminCmds[obj.command](obj, function(err, obj) { + return resp.send(obj.code, obj); + }); } else { return resp.send(404, 'Command unknown!'); } - } else { - return resp.send(renderPage('unauthorized', req.session)); - } + }); } else { - return resp.sendfile(getHandlerPath('login')); + return resp.send(401, 'You need to be logged in as admin!'); } }; - answerHandler = function(req, resp, ntbr) { - var hasBeenAnswered, needsToBeRendered, request, response, ret; - request = req; - response = resp; - needsToBeRendered = ntbr; - hasBeenAnswered = false; - ret = { - answerSuccess: function(msg) { - if (!hasBeenAnswered) { - if (needsToBeRendered) { - response.send(renderPage('command_answer', request.session, msg)); - } else { - response.send(msg); - } - } - return hasBeenAnswered = true; - }, - answerError: function(msg) { - if (!hasBeenAnswered) { - if (needsToBeRendered) { - response.send(400, renderPage('error', request.session, msg)); - } else { - response.send(400, msg); - } - } - return hasBeenAnswered = true; - }, - isAnswered: function() { - return hasBeenAnswered; - } - }; - setTimeout(function() { - return ret.answerError('Strange... maybe try again?'); - }, 5000); - return ret; - }; - }).call(this); diff --git a/testing/test_persistence.coffee b/testing/test_persistence.coffee index 59c8bbc..fc68859 100644 --- a/testing/test_persistence.coffee +++ b/testing/test_persistence.coffee @@ -139,73 +139,76 @@ exports.EventQueue = # Test ACTION INVOKER ### exports.ActionInvoker = + setUp: ( cb ) => + @action1id = 'test-action-invoker_1' + @action2id = 'test-action-invoker_2' + @action1 = + code: 'unit-test action invoker 1 content' + reqparams:'[param11,param12]' + @action2 = + code: 'unit-test action invoker 2 content' + reqparams:'[param21,param22]' + cb() + + tearDown: ( cb ) => + @db.deleteActionInvoker @action1id + @db.deleteActionInvoker @action2id + cb() + testCreateAndRead: ( test ) => test.expect 3 - - id = 'test-action-invoker' - action = 'unit-test action invoker content' - # store an entry to start with - @db.storeActionInvoker id, action + @db.storeActionInvoker @action1id, @action1 # test that the ID shows up in the set @db.getActionInvokerIds ( err , obj ) => - test.ok id in obj, + test.ok @action1id in obj, 'Expected key not in action-invokers set' # the retrieved object really is the one we expected - @db.getActionInvoker id, ( err , obj ) => - test.strictEqual obj, action, + @db.getActionInvoker @action1id, ( err , obj ) => + test.deepEqual obj, @action1, 'Retrieved Action Invoker is not what we expected' # Ensure the action invoker is in the list of all existing ones @db.getActionInvokers ( err , obj ) => - test.deepEqual action, obj[id], + test.deepEqual @action1, obj[@action1id], 'Action Invoker ist not in result set' - @db.deleteActionInvoker id test.done() testUpdate: ( test ) => test.expect 2 - id = 'test-action-invoker' - action = 'unit-test action invoker content' - actionNew = 'unit-test action invoker new content' - # store an entry to start with - @db.storeActionInvoker id, action - @db.storeActionInvoker id, actionNew + @db.storeActionInvoker @action1id, @action1 + @db.storeActionInvoker @action1id, @action2 # the retrieved object really is the one we expected - @db.getActionInvoker id, ( err , obj ) => - test.strictEqual obj, actionNew, + @db.getActionInvoker @action1id, ( err , obj ) => + test.deepEqual obj, @action2, 'Retrieved Action Invoker is not what we expected' # Ensure the action invoker is in the list of all existing ones @db.getActionInvokers ( err , obj ) => - test.deepEqual actionNew, obj[id], + test.deepEqual @action2, obj[@action1id], 'Action Invoker ist not in result set' - @db.deleteActionInvoker id test.done() testDelete: ( test ) => test.expect 2 - id = 'test-action-invoker' - action = 'unit-test action invoker content' - # store an entry to start with - @db.storeActionInvoker id, action + @db.storeActionInvoker @action1id, @action1 # Ensure the action invoker has been deleted - @db.deleteActionInvoker id - @db.getActionInvoker id, ( err , obj ) => + @db.deleteActionInvoker @action1id + @db.getActionInvoker @action1id, ( err , obj ) => test.strictEqual obj, null, 'Action Invoker still exists' # Ensure the ID has been removed from the set @db.getActionInvokerIds ( err , obj ) => - test.ok id not in obj, + test.ok @action1id not in obj, 'Action Invoker key still exists in set' test.done() @@ -214,33 +217,29 @@ exports.ActionInvoker = test.expect 3 semaphore = 2 - action1name = 'test-action-invoker_1' - action2name = 'test-action-invoker_2' - action1 = 'unit-test action invoker 1 content' - action2 = 'unit-test action invoker 2 content' fCheckInvoker = ( modname, mod ) => myTest = test forkEnds = () -> myTest.done() if --semaphore is 0 ( err, obj ) => - myTest.strictEqual mod, obj, + myTest.deepEqual mod, obj, "Invoker #{ modname } does not equal the expected one" - @db.deleteActionInvoker modname forkEnds() - @db.storeActionInvoker action1name, action1 - @db.storeActionInvoker action2name, action2 + @db.storeActionInvoker @action1id, @action1 + @db.storeActionInvoker @action2id, @action2 @db.getActionInvokerIds ( err, obj ) => - test.ok action1name in obj and action2name in obj, + test.ok @action1id in obj and @action2id in obj, 'Not all action invoker Ids in set' - @db.getActionInvoker action1name, fCheckInvoker action1name, action1 - @db.getActionInvoker action2name, fCheckInvoker action2name, action2 + @db.getActionInvoker @action1id, fCheckInvoker @action1id, @action1 + @db.getActionInvoker @action2id, fCheckInvoker @action2id, @action2 ### # Test ACTION INVOKER PARAMS ### +#TODO add tests for required parameters per module exports.ActionInvokerParams = testCreateAndRead: ( test ) => test.expect 2 @@ -250,18 +249,18 @@ exports.ActionInvokerParams = params = 'shouldn\'t this be an object?' # store an entry to start with - @db.storeActionParams actionId, userId, params + @db.storeActionUserParams actionId, userId, params # test that the ID shows up in the set - @db.getActionParamsIds ( err, obj ) => + @db.getActionUserParamsIds ( err, obj ) => test.ok actionId+':'+userId in obj, 'Expected key not in action-params set' # the retrieved object really is the one we expected - @db.getActionParams actionId, userId, ( err, obj ) => + @db.getActionUserParams actionId, userId, ( err, obj ) => test.strictEqual obj, params, 'Retrieved action params is not what we expected' - @db.deleteActionParams actionId, userId + @db.deleteActionUserParams actionId, userId test.done() testUpdate: ( test ) => @@ -273,14 +272,14 @@ exports.ActionInvokerParams = paramsNew = 'shouldn\'t this be a new object?' # store an entry to start with - @db.storeActionParams actionId, userId, params - @db.storeActionParams actionId, userId, paramsNew + @db.storeActionUserParams actionId, userId, params + @db.storeActionUserParams actionId, userId, paramsNew # the retrieved object really is the one we expected - @db.getActionParams actionId, userId, ( err, obj ) => + @db.getActionUserParams actionId, userId, ( err, obj ) => test.strictEqual obj, paramsNew, 'Retrieved action params is not what we expected' - @db.deleteActionParams actionId, userId + @db.deleteActionUserParams actionId, userId test.done() testDelete: ( test ) => @@ -291,15 +290,15 @@ exports.ActionInvokerParams = params = 'shouldn\'t this be an object?' # store an entry to start with and delte it right away - @db.storeActionParams actionId, userId, params - @db.deleteActionParams actionId, userId + @db.storeActionUserParams actionId, userId, params + @db.deleteActionUserParams actionId, userId # Ensure the action params have been deleted - @db.getActionParams actionId, userId, ( err, obj ) => + @db.getActionUserParams actionId, userId, ( err, obj ) => test.strictEqual obj, null, 'Action params still exists' # Ensure the ID has been removed from the set - @db.getActionParamsIds ( err, obj ) => + @db.getActionUserParamsIds ( err, obj ) => test.ok actionId+':'+userId not in obj, 'Action Params key still exists in set' test.done() @@ -309,73 +308,76 @@ exports.ActionInvokerParams = # Test EVENT POLLER ### exports.EventPoller = + setUp: ( cb ) => + @event1id = 'test-event-poller_1' + @event2id = 'test-event-poller_2' + @event1 = + code: 'unit-test event poller 1 content' + reqparams:'[param11,param12]' + @event2 = + code: 'unit-test event poller 2 content' + reqparams:'[param21,param22]' + cb() + + tearDown: ( cb ) => + @db.deleteEventPoller @event1id + @db.deleteEventPoller @event2id + cb() + testCreateAndRead: ( test ) => test.expect 3 - - id = 'test-event-poller' - event = 'unit-test event poller content' - # store an entry to start with - @db.storeEventPoller id, event + @db.storeEventPoller @event1id, @event1 # test that the ID shows up in the set @db.getEventPollerIds ( err , obj ) => - test.ok id in obj, + test.ok @event1id in obj, 'Expected key not in event-pollers set' # the retrieved object really is the one we expected - @db.getEventPoller id, ( err , obj ) => - test.strictEqual obj, event, + @db.getEventPoller @event1id, ( err , obj ) => + test.deepEqual obj, @event1, 'Retrieved Event Poller is not what we expected' # Ensure the event poller is in the list of all existing ones @db.getEventPollers ( err , obj ) => - test.deepEqual event, obj[id], + test.deepEqual @event1, obj[@event1id], 'Event Poller ist not in result set' - @db.deleteEventPoller id test.done() testUpdate: ( test ) => test.expect 2 - id = 'test-event-poller' - event = 'unit-test event poller content' - eventNew = 'unit-test event poller new content' - # store an entry to start with - @db.storeEventPoller id, event - @db.storeEventPoller id, eventNew + @db.storeEventPoller @event1id, @event1 + @db.storeEventPoller @event1id, @event2 # the retrieved object really is the one we expected - @db.getEventPoller id, ( err , obj ) => - test.strictEqual obj, eventNew, + @db.getEventPoller @event1id, ( err , obj ) => + test.deepEqual obj, @event2, 'Retrieved Event Poller is not what we expected' # Ensure the event poller is in the list of all existing ones @db.getEventPollers ( err , obj ) => - test.deepEqual eventNew, obj[id], + test.deepEqual @event2, obj[@event1id], 'Event Poller ist not in result set' - @db.deleteEventPoller id test.done() testDelete: ( test ) => test.expect 2 - id = 'test-event-poller' - event = 'unit-test event poller content' - # store an entry to start with - @db.storeEventPoller id, event + @db.storeEventPoller @event1id, @event1 # Ensure the event poller has been deleted - @db.deleteEventPoller id - @db.getEventPoller id, ( err , obj ) => + @db.deleteEventPoller @event1id + @db.getEventPoller @event1id, ( err , obj ) => test.strictEqual obj, null, 'Event Poller still exists' # Ensure the ID has been removed from the set @db.getEventPollerIds ( err , obj ) => - test.ok id not in obj, + test.ok @event1id not in obj, 'Event Poller key still exists in set' test.done() @@ -384,28 +386,23 @@ exports.EventPoller = test.expect 3 semaphore = 2 - event1name = 'test-event-poller_1' - event2name = 'test-event-poller_2' - event1 = 'unit-test event poller 1 content' - event2 = 'unit-test event poller 2 content' - fCheckPoller = ( modname, mod ) => + fCheckInvoker = ( modname, mod ) => myTest = test forkEnds = () -> myTest.done() if --semaphore is 0 ( err, obj ) => - myTest.strictEqual mod, obj, + myTest.deepEqual mod, obj, "Invoker #{ modname } does not equal the expected one" - @db.deleteEventPoller modname forkEnds() - @db.storeEventPoller event1name, event1 - @db.storeEventPoller event2name, event2 + @db.storeEventPoller @event1id, @event1 + @db.storeEventPoller @event2id, @event2 @db.getEventPollerIds ( err, obj ) => - test.ok event1name in obj and event2name in obj, + test.ok @event1id in obj and @event2id in obj, 'Not all event poller Ids in set' - @db.getEventPoller event1name, fCheckPoller event1name, event1 - @db.getEventPoller event2name, fCheckPoller event2name, event2 + @db.getEventPoller @event1id, fCheckInvoker @event1id, @event1 + @db.getEventPoller @event2id, fCheckInvoker @event2id, @event2 ### @@ -420,18 +417,18 @@ exports.EventPollerParams = params = 'shouldn\'t this be an object?' # store an entry to start with - @db.storeEventParams eventId, userId, params + @db.storeEventUserParams eventId, userId, params # test that the ID shows up in the set - @db.getEventParamsIds ( err, obj ) => + @db.getEventUserParamsIds ( err, obj ) => test.ok eventId+':'+userId in obj, 'Expected key not in event-params set' # the retrieved object really is the one we expected - @db.getEventParams eventId, userId, ( err, obj ) => + @db.getEventUserParams eventId, userId, ( err, obj ) => test.strictEqual obj, params, 'Retrieved event params is not what we expected' - @db.deleteEventParams eventId, userId + @db.deleteEventUserParams eventId, userId test.done() testUpdate: ( test ) => @@ -443,14 +440,14 @@ exports.EventPollerParams = paramsNew = 'shouldn\'t this be a new object?' # store an entry to start with - @db.storeEventParams eventId, userId, params - @db.storeEventParams eventId, userId, paramsNew + @db.storeEventUserParams eventId, userId, params + @db.storeEventUserParams eventId, userId, paramsNew # the retrieved object really is the one we expected - @db.getEventParams eventId, userId, ( err, obj ) => + @db.getEventUserParams eventId, userId, ( err, obj ) => test.strictEqual obj, paramsNew, 'Retrieved event params is not what we expected' - @db.deleteEventParams eventId, userId + @db.deleteEventUserParams eventId, userId test.done() testDelete: ( test ) => @@ -461,15 +458,15 @@ exports.EventPollerParams = params = 'shouldn\'t this be an object?' # store an entry to start with and delete it right away - @db.storeEventParams eventId, userId, params - @db.deleteEventParams eventId, userId + @db.storeEventUserParams eventId, userId, params + @db.deleteEventUserParams eventId, userId # Ensure the event params have been deleted - @db.getEventParams eventId, userId, ( err, obj ) => + @db.getEventUserParams eventId, userId, ( err, obj ) => test.strictEqual obj, null, 'Event params still exists' # Ensure the ID has been removed from the set - @db.getEventParamsIds ( err, obj ) => + @db.getEventUserParamsIds ( err, obj ) => test.ok eventId+':'+userId not in obj, 'Event Params key still exists in set' test.done() diff --git a/webpages/handlers/coffee/admin.coffee b/webpages/handlers/coffee/admin.coffee new file mode 100644 index 0000000..d034aca --- /dev/null +++ b/webpages/handlers/coffee/admin.coffee @@ -0,0 +1,20 @@ + +fOnLoad = () -> + document.title = 'Administrate' + $( '#pagetitle' ).text 'Hi {{{user.username}}}, issue your commands please:' + $( '#but_submit' ).click () -> + data = + command: $( '#inp_command' ).val() + $.post( 'admincommand', data ) + .done ( data ) -> + $( '#info' ).text data.message + $( '#info' ).attr 'class', 'success' + .fail ( err ) -> + if err.responseText is '' + err.responseText = 'No Response from Server!' + $( '#info' ).text 'Error: ' + err.responseText + $( '#info' ).attr 'class', 'error' + if err.status is 401 + window.location.href = 'admin' + +window.addEventListener 'load', fOnLoad, true diff --git a/webpages/handlers/coffee/error.coffee b/webpages/handlers/coffee/error.coffee deleted file mode 100644 index a3bcce2..0000000 --- a/webpages/handlers/coffee/error.coffee +++ /dev/null @@ -1,7 +0,0 @@ - -fOnLoad = () -> - document.title = 'Error!' - $( '#pagetitle' ).text 'Error processing your request!' - $( '#pagetitle' ).attr 'class', 'error' - -window.addEventListener 'load', fOnLoad, true diff --git a/webpages/handlers/coffee/forge_action_invoker.coffee b/webpages/handlers/coffee/forge_action_invoker.coffee index e69de29..60acefc 100644 --- a/webpages/handlers/coffee/forge_action_invoker.coffee +++ b/webpages/handlers/coffee/forge_action_invoker.coffee @@ -0,0 +1,80 @@ + +fOnLoad = () -> + document.title = 'Forge Action Invoker' + $( '#pagetitle' ).text "{{{user.username}}}, forge your custom action invoker!" + + # Setup the ACE editor + editor = ace.edit "editor" + editor.setTheme "ace/theme/monokai" + editor.getSession().setMode "ace/mode/coffee" + editor.setShowPrintMargin false + + $( '#editor_mode' ).change ( el ) -> + if $( this ).val() is '0' + editor.getSession().setMode "ace/mode/coffee" + else + editor.getSession().setMode "ace/mode/javascript" + + # Add parameter list functionality + fChangeCrosses = () -> + $( '#tableParams img' ).each ( id ) -> + par = $( this ).closest 'tr' + if par.is ':last-child' or par.is ':only-child' + $( this ).hide() + else + $( this ).show() + + $( '#tableParams' ).on 'click', 'img', () -> + par = $( this ).closest 'tr' + if not par.is ':last-child' + par.remove() + fChangeCrosses() + + $( '#tableParams' ).on 'keyup', 'input', () -> + par = $( this ).closest( 'tr' ) + if par.is ':last-child' + tr = $ '
| ' ).css 'width', '20px' + tr.append $( ' | ' ).text name + inp = $( '' ).attr( 'type', 'password' ).attr 'id', "#{ name }" + tr.append $( ' | ' ).text( ' :' ).append inp + table.append tr + fAppendParam name for name in arrParams + .fail ( err ) -> + console.log err + $( '#info' ).text 'Error fetching event poller params' + $( '#info' ).attr 'class', 'error' + + +#FIXME Add possibility for custom event via text input +#FIXME Add conditions + + + # Init Event Pollers + obj = + command: 'get_event_pollers' + $.post( '/usercommand', obj ) + .done ( data ) -> + fAppendEvent = ( id, name ) -> + $( '#select_event' ).append $( ' |