Merge branch 'master' of github.com:Deployd/Deployd

This commit is contained in:
Jeff Cross
2011-11-25 12:27:35 -07:00
12 changed files with 288 additions and 93 deletions

View File

@@ -32,6 +32,27 @@ app.configure(function(){
app.use(express.static(__dirname + '/../public'));
});
app.get('/routes', function(req, res) {
var routes = []
, format = ' - '
, method
, supported = {GET:1, POST:1, DELETE:1, PUT:1}
;
app.routes.all().forEach(function(route){
method = route.method.toUpperCase();
if(supported[method]) {
routes.push(
method
+ format.substr(method.length, format.length)
+ route.path
);
}
});
res.send(routes);
});
app.get('/', function(req, res) {
res.send({home: true});
});

View File

@@ -29,7 +29,12 @@ function collection(db, model, fn) {
module.exports = {
find: ready(function(model) {
var query = model.toQuery() || {};
var query = model.toQuery() || {}
, id = model.get('_id')
, _id = id && ObjectID(id)
;
if(_id) query._id = _id;
collection(db, model, function(err, collection) {
// TODO limit 1 for models, allow all for collections
@@ -44,11 +49,15 @@ module.exports = {
var query = model.toQuery()
, changes = model.attributes
, options = {safe: true, upsert: true}
, id = model.get('_id')
, _id = id && ObjectID(id)
, callback = function(err, result) {
model.refresh(changes);
}
;
if(_id && query) query._id = _id;
collection(db, model, function(err, collection) {
if(query) {
collection.update(query, changes, options, callback);

View File

@@ -67,25 +67,37 @@ Model = module.exports = emitter.spawn({
},
isNew: function() {
return !this._id;
return !this.get('_id');
},
isReady: function() {
return this.state === states.ready;
},
save: function() {
this.sync(states.write);
return this;
},
fetch: function() {
this.sync(states.read);
return this;
},
save: function() {
var model = this
, state = states.write
;
model.isAllowed(state, function() {
model.sync(state);
});
return this;
},
remove: function() {
this.sync(states.remove);
var model = this
, state = states.remove
;
model.isAllowed(state, function() {
model.sync(state);
});
return this;
},
@@ -104,10 +116,14 @@ Model = module.exports = emitter.spawn({
model.attributes = {};
model.set(changes || model.attributes);
model.isAllowed(model.state, function() {
if(model.state === states.read) {
model.isAllowed(model.state, function() {
model.sync(states.ready);
});
} else {
model.sync(states.ready);
});
}
return model;
},
@@ -117,41 +133,74 @@ Model = module.exports = emitter.spawn({
read: 'root',
write: 'root',
remove: 'root',
create: 'root',
special: {
_id: 'root'
_id: {read: 'root', write: 'root'}
}
},
isAllowed: function(action, fn) {
if(action === 'write' && this.isNew()) action = 'create';
var permissions = this.allowed
, special = permissions.special
, rights = permissions[action]
, requiresUser = rights === 'user'
, actor = this.actor()
, allowed = true
, model = this
;
if(requiresUser && !actor) {
model.error('Only logged in users can ' + action);
fn();
return;
}
if(permissions && rights && rights !== 'public') {
if(rights === 'creator') {
if(actor !== this.get('creator') && actor === this.get('_id')) {
model.error('The current user must be the creator to ' + action, 'Not Allowed');
}
} else {
// check permission against actor
Model
.spawn({collection: 'users', allowed: false})
.set({_id: actor})
.notify(function(json) {
allowed = !!(json.groups && json.groups[rights]);
if(!allowed)
model.error('The current user does not have permissions to ' + action, 'Not Allowed');
fn();
})
.fetch()
;
return;
}
// check permission against actor
Model
.spawn({collection: 'users', allowed: false})
.set({_id: actor})
.notify(function(json) {
// TODO cache groups on the req
var groups = json.groups
, root = groups && groups.root
, isCreator = actor === this.get('creator') || this.get('_id')
, requiresCreator = rights === 'creator' && !model.isNew() && rights === 'creator'
, allowed = root || (groups && groups[rights]) || (requiresCreator && isCreator)
;
if(special) {
Object.getOwnPropertyNames(special).forEach(function(key) {
var perms = special[key]
, right = perms[action]
, allowed = right === 'public' || (groups && groups[right]) || root
;
if(!allowed) {
if(action === 'read') {
// TODO build select object where {key: 0}
delete model.attributes[key];
} else {
model.error('The current user cannot ' + action + ' the key: ', key, 'Not Allowed');
}
}
})
}
if(requiresCreator && !isCreator) {
model.error('The current user must be the creator to ' + action, 'Not Allowed');
}
if(!allowed)
model.error('The current user does not have permissions to ' + action, 'Not Allowed');
fn();
})
.fetch()
;
return;
}
// default to responding
@@ -214,7 +263,8 @@ Model = module.exports = emitter.spawn({
if(_self.attributes[p] != changes[p]) {
_self.attributes[p] = changes[p];
if(p !== '_id') {
// TODO make '$' inspection less coupled and more secure
if(p !== '_id' && p.substr(0,1) != '$') {
_self.isValid(p, changes[p]);
}
@@ -348,11 +398,18 @@ Model = module.exports = emitter.spawn({
});
var spawn = module.exports.spawn;
var spawn = module.exports.spawn
, _models = {}
;
module.exports.refreshSettings = function(collection) {
_models[collection].updateSettings();
}
module.exports.spawn = function(model) {
var instance = spawn.apply(this, arguments);
if(model && model.collection) {
_models[instance.collection] = instance;
instance.updateSettings();
}
return instance;

View File

@@ -15,7 +15,8 @@ module.exports = Model.spawn({
allowed: {
read: 'creator',
write: 'creator',
remove: 'creator'
remove: 'creator',
create: 'user'
},
save: function() {

View File

@@ -0,0 +1,32 @@
var Settings = require('../settings/settings')
, Setting = require('../settings/settings')
, Model = require('../../model')
;
var models = module.exports.models = {};
var refresh = module.exports.refresh = function() {
Settings
.spawn()
.find({plugin: 'graph'})
.notify(function(model) {
var m = models[model.name] = Model.spawn();
m.description = model.description;
m.allowed = model.allowed;
m.collection = model.collection;
m.plugin = model.plugin;
})
.fetch()
;
}
// initial refresh
refresh();
// create or update general graph settings
Setting
.spawn()
.set({plugin: 'graph', name: 'graph'})
.save()
;

View File

@@ -2,17 +2,10 @@ var app = require('../../app')
, config = require('../../config').load()
, Settings = require('./settings')
, Setting = require('./setting')
, graph = require('../graph')
, Model = require('../../model')
;
app.post('/setting', function (req, res) {
Setting
.spawn()
.set(req.body)
.notify(res)
.save()
;
});
app.get('/settings', function(req, res) {
Settings
.spawn()
@@ -26,9 +19,13 @@ app.post('/settings', function(req, res) {
Settings
.spawn()
.for(req)
.find({name: req.body.name, plugin: req.body.name})
.find({name: req.body.name, plugin: req.body.plugin})
.set(req.body)
.notify(res)
.notify(function(json) {
if(req.body.plugin === 'graph') graph.refresh();
else if(json.collection) Model.refreshSettings(json.collection);
res.send(json);
})
.save()
;
});
@@ -37,7 +34,7 @@ app.get('/settings/:name', function(req, res) {
Setting
.spawn()
.for(req)
.find({name: req.param('name')})
.find({plugin: req.param('name')})
.notify(res)
.fetch()
;

34
lib/plugins/user/group.js Normal file
View File

@@ -0,0 +1,34 @@
var Model = require('../../model');
var Group = module.exports = Model.spawn({
collection: 'groups',
plugin: 'user',
description: {
name: {type: 'string', unique: true},
creator: 'string'
},
allowed: {
read: 'root',
write: 'creator',
remove: 'creator',
create: 'root'
}
});
var defaults = ['root', 'admin', 'public'];
defaults.forEach(function(group) {
var g = {name: group, creator: 'root'};
Group
.spawn()
.unlock()
.find(g)
.set(g)
.save()
;
});

View File

@@ -1,6 +1,8 @@
var app = require('../../app')
, Group = require('./group')
, User = require('./user')
, Users = require('./users')
, ObjectID = require('mongodb').BSON
;
function user(action, params, req, res) {
@@ -41,12 +43,31 @@ app.del('/me', function(req, res) {
if(u) user('remove', u, req, res);
});
app.get('/user/:email', function(req, res) {
user('fetch', {email: req.param('email')}, req, res);
app.get('/user/:id', function(req, res) {
User
.spawn()
.for(req)
.find({_id: req.param('id')})
.notify(res)
.fetch()
;
});
app.post('/user/:email/group', function(req, res) {
user('save', {email: req.param('email'), group: req.param('group')}, req, res);
var changes = {}
, group = req.body && req.body.group
;
// TODO validate group
changes['groups.' + group] = 1;
User
.spawn()
.for(req)
.find({email: req.param('email')})
.set({$set: changes})
.notify(res)
.save()
;
});
app.get('/users', function(req, res) {

View File

@@ -15,13 +15,18 @@ module.exports = Model.spawn({
name: 'string',
auth: 'string',
removed: 'boolean',
group: 'string'
groups: 'object'
},
allowed: {
read: 'creator',
read: 'public',
write: 'creator',
remove: 'creator'
remove: 'creator',
create: 'public',
special: {
groups: {read: 'public', write: 'root'},
email: {read: 'creator'}
}
},
toJSON: function() {

View File

@@ -9,7 +9,7 @@
"main": "index.js",
"engines": {
"node": ">= 0.4.0"
},
},
"dependencies": {
"express": ">= 2.0.0",
"underscore": ">= 1.0.0",

View File

@@ -2,7 +2,7 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>JSON-RPC Demo for JQuery Terminal Emulator</title>
<title>Deployd REPL</title>
<meta name="author" content="Jakub Jankiewicz - jcubic&#64;onet.pl"/>
<meta name="Description" content="Demonstration for JQuery Terminal Emulator using call automaticly JSON-RPC service (in php) with authentication."/>
<link rel="sitemap" type="application/xml" title="Sitemap" href=""/>
@@ -23,31 +23,58 @@ jQuery(document).ready(function($) {
$('body, html').scrollTop($('html').height())
}
function help(argument) {
terminal.echo('----------------------------------------------------------------------');
terminal.echo('Usage:');
terminal.echo("\t settings - show a list of app and plugin settings");
terminal.echo("\t routes - show a list of app urls");
terminal.echo("\t users - show a list of app users");
terminal.echo("\t me - show the currently logged in user");
terminal.echo("\t logout - go back to the dashboard");
terminal.echo("\t clear - clear the output");
terminal.echo("\t help - show this help message");
terminal.echo(' ')
terminal.echo('Code Examples:');
terminal.echo("\t d('/settings')");
terminal.echo("\t d('/users')");
terminal.echo("\t d('/user', {name: 'Joe', email: 'j@joes.com', password: '1234'})");
terminal.echo("\t d('/user/login', {email: 'j@joes.com', password: '1234'})");
terminal.echo("\t d('/search/users', {email: 'j@joes.com'})");
terminal.echo("\t d('/search/groups', {})");
terminal.echo('----------------------------------------------------------------------');
}
var terminal = $('#terminal').terminal(function(command, term) {
switch(command) {
case 'exit':
case 'logout':
window.location = '/dashboard';
break;
case 'help':
help();
break;
case 'me':
d('/me');
break;
case 'routes':
d('/routes');
break;
case 'users':
d('/users');
break;
case 'settings':
d('/settings');
break;
default:
eval(command);
var result = eval(command);
result && terminal.echo(JSON.stringify(result));
break;
}
}, {
greetings: 'Deployd REPL',
prompt: 'deployd >'
prompt: 'deployd $'
});
terminal.echo('----------------------------------------------------------------------');
terminal.echo('Examples:');
terminal.echo("\t d('/settings') or just 'settings'");
terminal.echo("\t d('/users')");
terminal.echo("\t d('/user', {name: 'Joe', email: 'j@joes.com', password: '1234'})");
terminal.echo("\t d('/user/login', {email: 'j@joes.com', password: '1234'})");
terminal.echo("\t d('/search/users', {email: 'j@joes.com'})");
terminal.echo('----------------------------------------------------------------------');
help();
jQuery.ajaxSetup({
complete: function(res) {

View File

@@ -33,16 +33,15 @@ var tests = {
}
},
'2. find user by id': {
route: '/user/' + user.email,
'3. add a user to group': {
route: '/user/' + user.email + '/group',
data: {group: 'root'},
expect: {
_id: 'toExist',
password: 'toNotExist',
errors: 'toNotExist'
}
},
'3. login a user': {
'4. login a user': {
route: '/user/login',
data: user,
expect: {
@@ -56,7 +55,7 @@ var tests = {
}
},
'4. get current user': {
'5. get current user': {
route: '/me',
expect: {
email: user.email,
@@ -66,7 +65,7 @@ var tests = {
}
},
'5. searching users': {
'6. searching users': {
route: '/search?type=users&find={"email": "skawful@gmail.com"}',
expect: {
results: 'toExist',
@@ -110,15 +109,7 @@ var tests = {
}
},
'10. add a user to group': {
route: '/user/' + user.email + '/group',
data: {group: 'author'},
expect: {
group: 'author'
}
},
'11. only 1 user per email': {
'10. only 1 user per email': {
route: '/search/users',
data: {email: user.email},
expect: {
@@ -126,7 +117,7 @@ var tests = {
}
},
'12. only 1 app per name': {
'11. only 1 app per name': {
route: '/search/apps',
data: {name: app.name},
expect: {
@@ -134,20 +125,20 @@ var tests = {
}
},
'13. only 1 user': {
'12. only 1 user': {
route: '/search/users',
data: {},
expect: {
results: 'toContainOne'
}
},
'14. delete a user': {
route: '/me?method=delete',
expect: {
errors: 'toNotExist'
}
}
// '13. delete a user': {
// route: '/me?method=delete',
// expect: {
// errors: 'toNotExist'
// }
// }
};