mdoq refactor, resources, routes

This commit is contained in:
Ritchie
2012-03-02 17:04:18 -07:00
parent 7e3fdbe36e
commit ae2eb1dc0a
14 changed files with 366 additions and 29 deletions

View File

@@ -0,0 +1,4 @@
test:
@mocha
.PHONY: test

View File

@@ -14,8 +14,8 @@ Extensible, distributed resource server.
## Hello World
var dpd = require('deployd')
, server = dpd()
, client = require('mdoq').require('mdoq-http').use('http://localhost:3000')
, server = dpd('My Todo App')
, client = require('mdoq').require('mdoq-http').use('http://user:pass@localhost:3000')
, resources = client.use('/resources')
, todos = client.use('/todos')
;
@@ -36,25 +36,21 @@ Extensible, distributed resource server.
}
};
resources.use('/todos').post(description, function(err, description) {
console.info('added todo description to resource graph');
});
server.listen('localhost', 3000);
Once the server is listening, we can interact with resources over http:
server.on('listening', function () {
todos.post({title: 'feed the dog'}, function(err, todo) {
console.log(todo._id); // the todos unique identifier
todos
.use('/' + todo._id)
.put({completed: true}, function (err, todo) {
console.log(todo); // {title: 'feed the dog', completed: true}
})
;
})
})
server.on('listening', function() {
// describe a new type of resource
resources.use('/todos').post(description, function(err, description) {
console.info('added todo description to resource graph');
// todos are now available
todos.post({title: 'feed the dog'}, function(err, todo) {
console.log(todo._id); // the todos unique identifier
});
});
});
## Resource Graph

37
examples/todo.server.js Normal file
View File

@@ -0,0 +1,37 @@
var dpd = require('deployd')
, server = dpd('My Todo App')
, client = require('mdoq').require('mdoq-http').use('http://user:pass@localhost:3000')
, resources = client.use('/resources')
, todos = client.use('/todos')
;
var description = {
type: 'data',
name: 'todo',
properties: {
title: {
description: 'the title of the todo',
type: 'string',
required: true
},
completed: {
description: 'the state of the todo',
type: 'boolean',
default: false
}
}
};
server.on('listening', function() {
// describe a new type of resource
resources.use('/todos').post(description, function(err, description) {
console.info('added todo description to resource graph');
// todos are now available
todos.post({title: 'feed the dog'}, function(err, todo) {
console.log('err', err, 'todo', todo);
});
});
});
server.listen(3000);

1
lib/client.js Normal file
View File

@@ -0,0 +1 @@
module.exports = require('mdoq').require('mdoq-http');

View File

@@ -10,6 +10,6 @@ var express = require('express')
* Export the deployd constructor.
*/
module.exports = function () {
return require('./server');
module.exports = function (name) {
return require('./server')(name);
}

View File

@@ -1,3 +1,5 @@
var resources = require('./storage').use('/resources');
var debug = require('mdoq').util.debug;
module.exports = require('./storage')
.use('/resources')
;

44
lib/router.js Normal file
View File

@@ -0,0 +1,44 @@
/**
* Dependencies
*/
var storage = require('./storage')
, resources = require('./resources')
, revalidator = require('revalidator')
;
module.exports = function (req, res, next) {
var path = req.url.split('?')[0];
resources.get({path: path}, function (err, resource) {
// HACK - REMOVE ONCE `first()` LANDS IN MDOQ-MONGODB
resource = resource && resource[0] || resource;
if(resource && tryingToModify(req)) {
if(resource.settings) {
var result = revalidator.validate(req.body, {properties: resource.settings});
if(result.valid) {
storage.proxy().call(this, req, res, next);
} else {
res.send(result);
}
} else {
storage.proxy().call(this, req, res, next);
}
} else if(resource) {
storage.exec(req, function (err, r) {
res.send(err || r);
});
} else {
next();
}
})
}
function tryingToModify(req) {
var method = req.method;
return method === 'POST' || method === 'PUT';
}

View File

@@ -1,8 +1,37 @@
var express = require('express')
, server = module.exports = express.createServer()
var dpd = require('./deployd')
, name = dpd.name
, express = require('express')
, server = express.createServer(express.bodyParser())
, resources = require('./resources')
, router = require('./router')
, types = require('./types')
;
server.get('/resources', function () {
})
/**
* Serve storage at each resource route.
*/
server.use(router);
/**
* Serve resources over http.
*/
server.use('/resources', resources.proxy());
/**
* Serve resource types over http.
*/
server.get('/types', function (req, res) {
res.send(types);
});
/**
* Export a function to mount the new server to a name.
*/
module.exports = function (name) {
server.name = name || 'deployd';
return server;
};

View File

@@ -1 +1,4 @@
module.exports = require('mdoq').require('mdoq-mongodb');
module.exports = require('mdoq')
.require('mdoq-mongodb')
.use('mongodb://localhost/dpd-testing')
;

12
lib/types.js Normal file
View File

@@ -0,0 +1,12 @@
module.exports = {
Collection: {
defaultPath: '/my-objects'
},
UserCollection: {
label: 'Users Collection',
defaultPath: '/users'
}
};

View File

@@ -1,7 +1,7 @@
{
"author": "Ritchie Martori",
"name": "deployd",
"version": "0.0.0",
"version": "0.3.0",
"repository": {
"url": "git://github.com/deployd/deployd.git"
},
@@ -9,6 +9,11 @@
"node": ">= 0.6.x"
},
"main":"index",
"dependencies": {},
"dependencies": {
"mdoq-http": "0.2.2",
"mdoq-mongodb": "0.2.2",
"revalidator": "0.1.0",
"express": "2.5.8"
},
"devDependencies": {}
}

91
test/deployd.test.js Normal file
View File

@@ -0,0 +1,91 @@
var expect = require('chai').expect
, mdoq = require('mdoq')
, client = require('../lib/client').use(mdoq.util.debug)
, dpd = require('../')
, server = dpd('My Todo App')
, tclient = client.use('http://localhost:3003')
, resources = tclient.use('/resources')
, todos = tclient.use('/todos')
, routes = tclient.use('/routes')
;
describe('Booting a server', function(){
it('should have the given name', function() {
var name = 'my simple server'
, server = dpd(name);
expect(server.name).to.equal(name);
})
it('should have a default name of deployd', function() {
var server = dpd();
expect(server.name).to.equal('deployd');
})
it('should start listening on the given port', function(done) {
var server = dpd('test server')
, port = 2304;
server.on('listening', function () {
// server emits listening more than once!
if(!server.closing) server.close();
});
server.on('close', function () {
server.closing = true;
done();
});
server.listen(port);
})
})
describe('Resource Types', function(){
it('should be able to be described from a client', function(done) {
var resource = {
type: 'Collection',
path: '/todos',
settings: {
title: {
description: 'the title of the todo',
type: 'string',
required: true
},
completed: {
description: 'the state of the todo',
type: 'boolean',
default: false
}
}
};
server.on('listening', function() {
// describe a new type of resource
resources.post(resource, done);
});
server.listen(3003);
})
it('should now have a resource type', function(done) {
resources.get(function (err, todos) {
expect(todos).to.exist;
expect(todos).to.have.length(1);
expect(todos[0].path).to.equal('/todos');
done(err);
})
})
it('should save a new todo', function(done) {
var eg = {title: 'feed the dog'};
todos.post(eg, function (err) {
todos.get(eg, function (err, todo) {
// HACK - need first()
if(todo.length) todo = todo[0];
expect(todo.title).to.equal(eg.title);
done(err);
})
})
})
})

89
test/resources.test.js Normal file
View File

@@ -0,0 +1,89 @@
var resources = require('../')
, expect = require('chai').expect
, resources = require('../lib/resources')
;
var description = {
type: 'Data',
properties: {
title: {
description: 'the title of the todo',
type: 'string',
required: true
},
completed: {
description: 'the state of the todo',
type: 'boolean',
default: false
}
}
};
function clean() {
describe('resources clean', function(){
it('should remove all resources', function(done) {
resources.del(function (err, res) {
resources.get(function (err, res) {
expect(res).to.not.exist;
done(err);
})
})
})
})
}
describe('Resource Actions', function(){
clean();
describe('Adding', function(){
it('should add a new resource', function(done) {
resources.post(description, function (err, r) {
description._id = r._id;
expect(r._id).to.exist;
resources.get({_id: r._id}, function (err, r) {
expect(r).to.exist;
done(err);
})
})
})
})
describe('resources.put()', function(){
it('should update the resource', function(done) {
resources
.get({_id: description._id})
// change title to task
.put({$set: {'properties.title': null, 'properties.task': description.properties.title}}, function (err, r) {
resources.get({_id: description._id}, function (err, r) {
expect(r.properties.task).to.eql(description.properties.title);
done(err);
})
})
})
})
describe('resources.get()', function(){
it('should get the resource', function(done) {
resources.get({_id: description._id}, function (err, r) {
expect(r).to.exist;
done(err);
})
})
})
describe('resources.del()', function(){
it('should delete the resource', function(done) {
resources.del({_id: description._id}, function (err, r) {
resources.get({_id: description._id}, function (err, r) {
expect(r).to.not.exist;
done(err);
})
})
})
})
clean();
})

24
test/types.test.js Normal file
View File

@@ -0,0 +1,24 @@
var expect = require('chai').expect
, client = require('../lib/client').use('http://localhost:3003')
, dpd = require('../')
, server = dpd('My Todo App')
, types = client.use('/types')
;
describe('GET /types', function(){
it('should respond with a list of resource definitions', function(done) {
types.get(function (err, types) {
expect(types).to.eql({
Collection: {
defaultPath: '/my-objects'
},
UserCollection: {
label: 'Users Collection',
defaultPath: '/users'
}
});
done(err);
})
})
})