Various updates.

This commit is contained in:
Julien Fontanet 2013-07-06 17:07:38 +02:00
parent a39a0a9d84
commit 54019d7843
8 changed files with 243 additions and 117 deletions

View File

@ -15,7 +15,9 @@
"dependencies": { "dependencies": {
"extendable": ">=0.0.3", "extendable": ">=0.0.3",
"hashy": ">=0.1.0", "hashy": ">=0.1.0",
"underscore": ">=1.4.4" "underscore": ">=1.4.4",
"xmlrpc": ">=1.1.0",
"q": ">=0.9.6"
}, },
"devDependencies": {}, "devDependencies": {},
"optionalDependencies": {}, "optionalDependencies": {},

View File

@ -1,4 +1,5 @@
var _ = require('underscore'); var _ = require('underscore');
var Q = require('q');
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@ -18,30 +19,49 @@ function Api(xo)
this.xo = xo; this.xo = xo;
} }
Api.prototype.exec = function (session, req, res) { Api.prototype.exec = function (session, request, response) {
var method = this.get(req.method); var method = this.get(request.method);
if (!method) if (!method)
{ {
res.sendError(Api.err.INVALID_METHOD); response.sendError(Api.err.INVALID_METHOD);
return; return;
} }
try try
{ {
var result = method.call(this.xo, session, req, res); var result = method.call(this.xo, session, request, response); // @todo
if (undefined !== result)
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) catch (e)
{ {
res.sendError(e); response.sendError(e);
} }
}; };
Api.prototype.get = function (name) { Api.prototype.get = function (name) {
/* jshint noempty: false */
var parts = name.split('.'); var parts = name.split('.');
var current = Api.fn; var current = Api.fn;
@ -65,7 +85,6 @@ Api.prototype.get = function (name) {
} }
return undefined; return undefined;
;
}; };
module.exports = function (xo) { module.exports = function (xo) {
@ -102,21 +121,25 @@ Api.err = {
// XO errors. // XO errors.
////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////
'ALREADY_AUTHENTICATED': err(0, 'already authenticated'), 'NOT_IMPLEMENTED': err(0, 'not implemented'),
// Invalid email & passwords or token. 'NO_SUCH_OBJECT': err(1, 'no such object'),
'INVALID_CREDENTIAL': err(1, 'invalid credential'),
// Not authenticated or not enough permissions. // 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.fn.api = { Api.fn.api = {
'getVersion' : function (session, req, res) { 'getVersion' : function () {
return '0.1'; return '0.1';
}, },
}; };
@ -154,7 +177,7 @@ Api.fn.session = {
}).done(); }).done();
}, },
'signInWithToken': function (session, req, res) { 'signInWithToken': function (session, req) {
var p_token = req.params.token; var p_token = req.params.token;
if (!p_token) if (!p_token)
@ -183,28 +206,28 @@ Api.fn.session = {
return true; return true;
}, },
'getUser': deprecated(function (session, req, res) { 'getUser': deprecated(function (session) {
var user_id = session.get('user_id'); var user_id = session.get('user_id');
if (undefined === user_id) if (undefined === user_id)
{ {
return null; 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); return session.get('user_id', null);
}; },
'createToken': 'token.create' 'createToken': 'token.create',
'destroyToken': 'token.delete', 'destroyToken': 'token.delete',
}; };
// User management. // User management.
Api.fn.user = { Api.fn.user = {
'create': function (session, req, res) { 'create': function (session, req) {
var p_email = req.params.email; var p_email = req.params.email;
var p_pass = req.params.password; var p_pass = req.params.password;
var p_perm = req.params.permission; var p_perm = req.params.permission;
@ -214,44 +237,45 @@ Api.fn.user = {
throw Api.err.INVALID_PARAMS; throw Api.err.INVALID_PARAMS;
} }
var user = new this.users.model({ return this.users.add({
'email': p_email, 'email': p_email,
'password': p_pass, 'password': p_pass,
'permission': p_perm, '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) { 'delete': function () {
var p_id = req.params.id; throw Api.err.NOT_IMPLEMENTED;
var user
}, },
'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. // Token management.
Api.fn.token = { Api.fn.token = {
'create': function (session, req, res) { 'create': function (session) {
var user_id = session.get('user_id'); var user_id = session.get('user_id');
/* jshint laxbreak: true */
if ((undefined === user_id) if ((undefined === user_id)
|| session.has('token_id')) || session.has('token_id'))
{ {
throw Api.err.UNAUTHORIZED; throw Api.err.UNAUTHORIZED;
} }
// @todo Token permission.
// @todo Ugly. // @todo Ugly.
var token = this.tokens.model.generate(user_id); var token = this.tokens.model.generate(user_id);
this.tokens.add(token); this.tokens.add(token);
@ -259,7 +283,7 @@ Api.fn.token = {
return token.id; return token.id;
}, },
'delete': function (session, req, res) { 'delete': function (session, req) {
var p_token = req.params.token; var p_token = req.params.token;
if (!this.tokens.get(p_token)) if (!this.tokens.get(p_token))
@ -272,7 +296,75 @@ Api.fn.token = {
}, },
}; };
// VM // Pool management.
Api.fn.vm = { 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 () {
},
}; };

View File

@ -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.items = [];
this.next_id = 0; this.next_id = 0;
@ -9,8 +15,8 @@ var Collection = function (items) {
{ {
this.add(items); this.add(items);
} }
}; }
util.inherits(Collection, EventEmitter); require('util').inherits(Collection, require('events').EventEmitter);
Collection.prototype.model = require('model'); Collection.prototype.model = require('model');
@ -18,15 +24,18 @@ Collection.prototype.model = require('model');
* Adds new items to this collection. * Adds new items to this collection.
*/ */
Collection.prototype.add = function (items) { Collection.prototype.add = function (items) {
var array = true;
if (!_.isArray(items)) if (!_.isArray(items))
{ {
items = [items]; items = [items];
array = false;
} }
_.each(items, function (item) { _.each(items, function (item, i) {
if ( !(item instanceof this.model) ) if ( !(item instanceof this.model) )
{ {
item = new this.model(item); item = new this.model(item);
items[i] = item;
} }
var error = item.validate(); var error = item.validate();
@ -45,11 +54,23 @@ Collection.prototype.add = function (items) {
} }
// Existing items are ignored. // 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) { _.each(ids, function (id) {
delete this.items[id]; delete this.items[id];
}); });
// @todo Maybe return a more meaningful value.
/* jshint newcap: false */
return Q(true);
}; };
/** /**
* Updates existing items. * Updates existing items.
*/ */
Collection.prototype.update = function (items) { Collection.prototype.update = function (items) {
var array = true;
if (!_.isArray(items)) if (!_.isArray(items))
{ {
items = [items]; items = [items];
array = false;
} }
_.each(items, function (item) { _.each(items, function (properties, i) {
if (item instanceof this.model) if (properties instanceof this.model)
{ {
item = item.properties; properties = properties.properties;
} }
// @todo // @todo
@ -89,14 +116,23 @@ Collection.prototype.update = function (items) {
// throw error; // throw error;
// } // }
var id = item.id; var id = properties.id;
var item = this.items[id];
// Missing items are ignored. // 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. * - Updates existing items.
* - Removes missing items. * - Removes missing items.
*/ */
Collection.prototype.set = function (items) { Collection.prototype.set = function (/*items*/) {
throw 'not implemented'; throw 'not implemented';
}; };
Model.extend = require('extendable'); Collection.extend = require('extendable');
// Export. // Export.
module.exports = Model; module.exports = Collection;

View File

@ -1,3 +0,0 @@
module.exports = require('events').EventEmitter;
module.exports.extend = require('extendable');

View File

@ -1,6 +1,6 @@
var EventEmitter = require('events').EventEmitter; var _ = require('underscore');
var util = require('util'); var Response = require('./response');
var Session = require('session'); 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) function json_api_call(session, transport, message)
{ {
var req;
try 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( if (e instanceof SyntaxError)
api.err {
); new Response(transport, null).sendError(
return; api.err
);
return;
}
} }
/* jshint laxbreak: true */
if (!req.method || !req.params if (!req.method || !req.params
|| (undefined === req.id) || (undefined === req.id)
|| ('2.0' !== req.jsonrpc)) || ('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) { require('socket.io').listen(8080).sockets.on('connection', function (socket) {
var transport = function (message) { var transport = function (message) {
socket.send(data); socket.send(message);
}; };
var session = new Session(); var session = new Session();
@ -135,6 +107,9 @@ require('net').createServer(function (socket) {
return; 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('<path>'); // @todo }).listen('<path>'); // @todo

View File

@ -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 = {}; this.properties = {};
if (properties) if (properties)
{ {
this.set(properties); this.set(properties);
} }
}; }
util.inherits(Model, require('events').EventEmitter); require('util').inherits(Model, require('events').EventEmitter);
/** /**
* Initializes the model after construction. * Initializes the model after construction.
@ -21,7 +25,7 @@ Model.prototype.initialize = function () {};
* @returns {undefined|mixed} Returns something else than undefined if * @returns {undefined|mixed} Returns something else than undefined if
* there was an error. * there was an error.
*/ */
Model.prototype.validate = function (properties) {}; Model.prototype.validate = function (/*properties*/) {};
/** /**
* Gets property. * Gets property.

View File

@ -1,5 +1,5 @@
module.exports = require('model').extend({ module.exports = require('model').extend({
'close': function () { 'close': function () {
session.emit('close'); this.emit('close');
}, },
}); });

View File

@ -1,14 +1,13 @@
var _ = require('underscore');
var crypto = require('crypto'); var crypto = require('crypto');
var hashy = require('hashy'); var hashy = require('hashy');
var Q = require('q'); var Q = require('q');
var Collection = require('collection'); var Collection = require('./collection');
var Model = require('model'); var Model = require('./model');
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
var check = function () { var check = function () {
var errors = undefined; var errors;
var validator = new require('validator').Validator(); var validator = new require('validator').Validator();
validator.error = function (err) { validator.error = function (err) {
@ -53,8 +52,6 @@ var Token = Model.extend({
}, },
}); });
user.set('password', '123');
var User = Model.extend({ var User = Model.extend({
'default': { 'default': {
'permission': 'none', '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, 'model': User,
}); });
var Servers = Collection.extend({
'model': Server,
});
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
function Xo() function Xo()
{ {
this.servers = new Servers();
this.tokens = new Tokens(); this.tokens = new Tokens();
this.users = new Users(); this.users = new Users();
//
} }
module.exports = function () { module.exports = function () {
return new Xo; return new Xo();
}; };