From 54019d7843638827fd5b0e78003ea6ff513be71d Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Sat, 6 Jul 2013 17:07:38 +0200 Subject: [PATCH] Various updates. --- package.json | 4 +- src/api.js | 170 +++++++++++++++++++++++++++++++++---------- src/collection.js | 68 +++++++++++++---- src/event-emitter.js | 3 - src/main.js | 65 +++++------------ src/model.js | 14 ++-- src/session.js | 2 +- src/xo.js | 34 +++++++-- 8 files changed, 243 insertions(+), 117 deletions(-) delete mode 100644 src/event-emitter.js diff --git a/package.json b/package.json index 83d17b5..275a99f 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,9 @@ "dependencies": { "extendable": ">=0.0.3", "hashy": ">=0.1.0", - "underscore": ">=1.4.4" + "underscore": ">=1.4.4", + "xmlrpc": ">=1.1.0", + "q": ">=0.9.6" }, "devDependencies": {}, "optionalDependencies": {}, diff --git a/src/api.js b/src/api.js index 1215ded..d033612 100644 --- a/src/api.js +++ b/src/api.js @@ -1,4 +1,5 @@ var _ = require('underscore'); +var Q = require('q'); ////////////////////////////////////////////////////////////////////// @@ -18,30 +19,49 @@ function Api(xo) this.xo = xo; } -Api.prototype.exec = function (session, req, res) { - var method = this.get(req.method); +Api.prototype.exec = function (session, request, response) { + var method = this.get(request.method); if (!method) { - res.sendError(Api.err.INVALID_METHOD); + response.sendError(Api.err.INVALID_METHOD); return; } try { - var result = method.call(this.xo, session, req, res); - if (undefined !== result) + var result = method.call(this.xo, session, request, response); // @todo + + if (undefined === result) { - res.sendResult(result); + /* jshint noempty:false */ } + else if (Q.isPromise(result)) + { + result.then( + function (result) { + response.sendResult(result); + }, + function (error) { + response.sendError(error); + } + ).done(); + } + else + { + response.sendResult(result); + } + } catch (e) { - res.sendError(e); + response.sendError(e); } }; Api.prototype.get = function (name) { + /* jshint noempty: false */ + var parts = name.split('.'); var current = Api.fn; @@ -65,7 +85,6 @@ Api.prototype.get = function (name) { } return undefined; - ; }; module.exports = function (xo) { @@ -102,21 +121,25 @@ Api.err = { // XO errors. ////////////////////////////////////////////////////////////////// - 'ALREADY_AUTHENTICATED': err(0, 'already authenticated'), + 'NOT_IMPLEMENTED': err(0, 'not implemented'), - // Invalid email & passwords or token. - 'INVALID_CREDENTIAL': err(1, 'invalid credential'), + 'NO_SUCH_OBJECT': err(1, 'no such object'), // Not authenticated or not enough permissions. - 'UNAUTHORIZED': err(2, 'not authenticated'), -)}; + 'UNAUTHORIZED': err(2, 'not authenticated or not enough permissions'), + + // Invalid email & passwords or token. + 'INVALID_CREDENTIAL': err(3, 'invalid credential'), + + 'ALREADY_AUTHENTICATED': err(4, 'already authenticated'), +}; ////////////////////////////////////////////////////////////////////// Api.fn = {}; Api.fn.api = { - 'getVersion' : function (session, req, res) { + 'getVersion' : function () { return '0.1'; }, }; @@ -154,7 +177,7 @@ Api.fn.session = { }).done(); }, - 'signInWithToken': function (session, req, res) { + 'signInWithToken': function (session, req) { var p_token = req.params.token; if (!p_token) @@ -183,28 +206,28 @@ Api.fn.session = { return true; }, - 'getUser': deprecated(function (session, req, res) { + 'getUser': deprecated(function (session) { var user_id = session.get('user_id'); if (undefined === user_id) { return null; } - return _.pick(users.get(user_id), 'id', 'email'); - }); + return _.pick(this.users.get(user_id), 'id', 'email'); + }), - 'getUserId': function (session, req, res) { + 'getUserId': function (session) { return session.get('user_id', null); - }; + }, - 'createToken': 'token.create' + 'createToken': 'token.create', 'destroyToken': 'token.delete', }; // User management. Api.fn.user = { - 'create': function (session, req, res) { + 'create': function (session, req) { var p_email = req.params.email; var p_pass = req.params.password; var p_perm = req.params.permission; @@ -214,44 +237,45 @@ Api.fn.user = { throw Api.err.INVALID_PARAMS; } - var user = new this.users.model({ + return this.users.add({ 'email': p_email, 'password': p_pass, 'permission': p_perm, + }).then(function (user) { + return user.get('id'); }); - - // @todo How to save it and to retrieve its unique id? }, - 'delete': function (session, req, res) { - var p_id = req.params.id; - - var user + 'delete': function () { + throw Api.err.NOT_IMPLEMENTED; }, - 'changePassword': function (session, req, res) { - + 'changePassword': function () { + throw Api.err.NOT_IMPLEMENTED; }, - 'getAll': function (session, req, res) { - + 'getAll': function () { + throw Api.err.NOT_IMPLEMENTED; }, - 'set': function (session, req, res) { - + 'set': function () { + throw Api.err.NOT_IMPLEMENTED; }, }; // Token management. Api.fn.token = { - 'create': function (session, req, res) { + 'create': function (session) { var user_id = session.get('user_id'); + /* jshint laxbreak: true */ if ((undefined === user_id) || session.has('token_id')) { throw Api.err.UNAUTHORIZED; } + // @todo Token permission. + // @todo Ugly. var token = this.tokens.model.generate(user_id); this.tokens.add(token); @@ -259,7 +283,7 @@ Api.fn.token = { return token.id; }, - 'delete': function (session, req, res) { + 'delete': function (session, req) { var p_token = req.params.token; if (!this.tokens.get(p_token)) @@ -272,7 +296,75 @@ Api.fn.token = { }, }; -// VM -Api.fn.vm = { +// Pool management. +Api.fn.server = { + 'add': function (session, req, res) { + var host = req.params.host; // @todo p_ prefixes. + var username = req.params.username; + var password = req.params.username; + if (!host || !username || !password) + { + throw Api.err.INVALID_PARAMS; + } + + var user_id = session.get('user_id'); + if (undefined === user_id) + { + throw Api.err.UNAUTHORIZED; + } + + var user = this.users.get(user_id); + if (!user.hasPermission('admin')) + { + throw Api.err.UNAUTHORIZED; + } + + // @todo We are storing passwords which is bad! + // Can we use tokens instead? + this.servers.add({ + 'host': host, + 'username': username, + 'password': password, + }).then(function (server) { + // @todo Connect the server. + + res.sendResult(''+ server.get('id')); + }).done(); + }, + + 'remove': function (session, req, res) { + var p_id = req.params.id; + + var user_id = session.get('user_id'); + if (undefined === user_id) + { + throw Api.err.UNAUTHORIZED; + } + + var user = this.users.get(user_id); + if (!user.hasPermission('admin')) + { + throw Api.err.UNAUTHORIZED; + } + + if (!this.servers.exists(p_id)) + { + throw Api.err.NO_SUCH_OBJECT; + } + + // @todo Disconnect the server. + + this.servers.remove(p_id).then(function () { + res.sendResult(true); + }).done(); + }, + + 'connect': function () { + + }, + + 'disconnect': function () { + + }, }; diff --git a/src/collection.js b/src/collection.js index 4a980f3..3cc1382 100644 --- a/src/collection.js +++ b/src/collection.js @@ -1,6 +1,12 @@ -// @todo Add events. +var _ = require('underscore'); +var Q = require('q'); + +// @todo Add events. +function Collection(items) +{ + // Parent constructor. + Collection.super_.call(this); -var Collection = function (items) { this.items = []; this.next_id = 0; @@ -9,8 +15,8 @@ var Collection = function (items) { { this.add(items); } -}; -util.inherits(Collection, EventEmitter); +} +require('util').inherits(Collection, require('events').EventEmitter); Collection.prototype.model = require('model'); @@ -18,15 +24,18 @@ Collection.prototype.model = require('model'); * Adds new items to this collection. */ Collection.prototype.add = function (items) { + var array = true; if (!_.isArray(items)) { items = [items]; + array = false; } - _.each(items, function (item) { + _.each(items, function (item, i) { if ( !(item instanceof this.model) ) { item = new this.model(item); + items[i] = item; } var error = item.validate(); @@ -45,11 +54,23 @@ Collection.prototype.add = function (items) { } // Existing items are ignored. - if (!this.items[id]) + if (this.items[id]) { - this.items[id] = item; + return Q.reject('cannot add existing items!'); } + + this.items[id] = item; }); + + /* jshint newcap: false */ + return Q(array ? items : items[0]); +}; + +/** + * + */ +Collection.prototype.exists = function (id) { + return (undefined !== this.items[id]); }; /** @@ -64,21 +85,27 @@ Collection.prototype.remove = function (ids) { _.each(ids, function (id) { delete this.items[id]; }); + + // @todo Maybe return a more meaningful value. + /* jshint newcap: false */ + return Q(true); }; /** * Updates existing items. */ Collection.prototype.update = function (items) { + var array = true; if (!_.isArray(items)) { items = [items]; + array = false; } - _.each(items, function (item) { - if (item instanceof this.model) + _.each(items, function (properties, i) { + if (properties instanceof this.model) { - item = item.properties; + properties = properties.properties; } // @todo @@ -89,14 +116,23 @@ Collection.prototype.update = function (items) { // throw error; // } - var id = item.id; + var id = properties.id; + + var item = this.items[id]; // Missing items are ignored. - if (this.items[id]) + if (!item) { - this.items[id].set(item); + return Q.reject('missing item!'); } + + item.set(properties); + + items[i] = item; }); + + /* jshint newcap: false */ + return Q(array ? items : items[0]); }; /** @@ -106,11 +142,11 @@ Collection.prototype.update = function (items) { * - Updates existing items. * - Removes missing items. */ -Collection.prototype.set = function (items) { +Collection.prototype.set = function (/*items*/) { throw 'not implemented'; }; -Model.extend = require('extendable'); +Collection.extend = require('extendable'); // Export. -module.exports = Model; +module.exports = Collection; diff --git a/src/event-emitter.js b/src/event-emitter.js deleted file mode 100644 index 60c6ed9..0000000 --- a/src/event-emitter.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = require('events').EventEmitter; - -module.exports.extend = require('extendable'); diff --git a/src/main.js b/src/main.js index a5f48a9..aeacb0e 100644 --- a/src/main.js +++ b/src/main.js @@ -1,6 +1,6 @@ -var EventEmitter = require('events').EventEmitter; -var util = require('util'); -var Session = require('session'); +var _ = require('underscore'); +var Response = require('./response'); +var Session = require('./session'); //-------------------------------------- @@ -9,54 +9,26 @@ var api = require('./api')(xo); ////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////// - -function Response(transport, id) -{ - this.transport = transport; - this.id = id; -} - -Response.prototype.sendResult = function (value) -{ - this.transport(JSON.stringify({ - 'jsonrpc': '2.0', - 'result': value, - 'id': this.id, - })); - - // Prevents results/errors to be sent more than once. - delete this.transport; -}; - -Response.prototype.sendError = function (error) -{ - this.transport(JSON.stringify({ - 'jsonrpc': '2.0', - 'error': error, - 'id': this.id, - })); - - // Prevents results/errors to be sent more than once. - delete this.transport; -}; - -////////////////////////////////////////////////////////////////////// - function json_api_call(session, transport, message) { + var req; + try { - var req = JSON.parse(message.toString()); + req = JSON.parse(message.toString()); } - catch (e if e instanceof SyntaxError) + catch (e) { - new Response(transport, null).sendError( - api.err - ); - return; + if (e instanceof SyntaxError) + { + new Response(transport, null).sendError( + api.err + ); + return; + } } + /* jshint laxbreak: true */ if (!req.method || !req.params || (undefined === req.id) || ('2.0' !== req.jsonrpc)) @@ -84,7 +56,7 @@ function json_api_call(session, transport, message) require('socket.io').listen(8080).sockets.on('connection', function (socket) { var transport = function (message) { - socket.send(data); + socket.send(message); }; var session = new Session(); @@ -135,6 +107,9 @@ require('net').createServer(function (socket) { return; } - json_api_call(session, transport, buffer.toString()); + json_api_call(session, transport, buffer.slice(0, length).toString()); + + // @todo Check it frees the memory. + buffer = buffer.slice(length); }); }).listen(''); // @todo diff --git a/src/model.js b/src/model.js index 7ef7f06..bcf88e4 100644 --- a/src/model.js +++ b/src/model.js @@ -1,14 +1,18 @@ -// @todo Add events. +var _ = require('underscore'); + +function Model(properties) +{ + // Parent constructor. + Model.super_.call(this); -var Model = function (properties) { this.properties = {}; if (properties) { this.set(properties); } -}; -util.inherits(Model, require('events').EventEmitter); +} +require('util').inherits(Model, require('events').EventEmitter); /** * Initializes the model after construction. @@ -21,7 +25,7 @@ Model.prototype.initialize = function () {}; * @returns {undefined|mixed} Returns something else than undefined if * there was an error. */ -Model.prototype.validate = function (properties) {}; +Model.prototype.validate = function (/*properties*/) {}; /** * Gets property. diff --git a/src/session.js b/src/session.js index c2502d6..fcb7388 100644 --- a/src/session.js +++ b/src/session.js @@ -1,5 +1,5 @@ module.exports = require('model').extend({ 'close': function () { - session.emit('close'); + this.emit('close'); }, }); diff --git a/src/xo.js b/src/xo.js index e628c72..c5a3b3e 100644 --- a/src/xo.js +++ b/src/xo.js @@ -1,14 +1,13 @@ -var _ = require('underscore'); var crypto = require('crypto'); var hashy = require('hashy'); var Q = require('q'); -var Collection = require('collection'); -var Model = require('model'); +var Collection = require('./collection'); +var Model = require('./model'); ////////////////////////////////////////////////////////////////////// var check = function () { - var errors = undefined; + var errors; var validator = new require('validator').Validator(); validator.error = function (err) { @@ -53,8 +52,6 @@ var Token = Model.extend({ }, }); -user.set('password', '123'); - var User = Model.extend({ 'default': { 'permission': 'none', @@ -99,6 +96,22 @@ var User = Model.extend({ } }); }, + + 'hasPermission': function (permission) { + var perms = { + 'none': 0, + 'read': 1, + 'write': 2, + 'admin': 3, + }; + + return (perms[this.get('permission')] >= perms[permission]); + }, +}); + +var Server = Model.extend({ + 'validate': function () { + }, }); ////////////////////////////////////////////////////////////////////// @@ -113,14 +126,21 @@ var Users = Collection.extend({ 'model': User, }); +var Servers = Collection.extend({ + 'model': Server, +}); + ////////////////////////////////////////////////////////////////////// function Xo() { + this.servers = new Servers(); this.tokens = new Tokens(); this.users = new Users(); + + // } module.exports = function () { - return new Xo; + return new Xo(); };