Refactor and migrate to Express 0.4.x

This commit is contained in:
Typicode
2014-09-16 22:44:02 +02:00
parent 3ae7c5bcec
commit a76ca913ae
4 changed files with 155 additions and 86 deletions

View File

@ -8,12 +8,16 @@
"test": "test" "test": "test"
}, },
"dependencies": { "dependencies": {
"body-parser": "^1.8.1",
"chalk": "^0.4.0", "chalk": "^0.4.0",
"cors": "^2.3.0", "cors": "^2.3.0",
"express": "^3.4.8", "errorhandler": "^1.2.0",
"express": "^4.9.0",
"lowdb": "^0.3.0", "lowdb": "^0.3.0",
"method-override": "^2.1.2", "method-override": "^2.1.2",
"minimist": "0.0.8", "minimist": "0.0.8",
"morgan": "^1.3.1",
"serve-static": "^1.6.1",
"superagent": "~0.15.7", "superagent": "~0.15.7",
"underscore": "~1.5.2", "underscore": "~1.5.2",
"underscore.inflections": "~0.2.1", "underscore.inflections": "~0.2.1",

View File

@ -1,5 +1,5 @@
var _ = require('underscore') var _ = require('underscore')
var low = require('lowdb') var low = require('lowdb')
var utils = require('./utils') var utils = require('./utils')
var routes = {} var routes = {}
@ -9,56 +9,75 @@ routes.db = function(req, res, next) {
res.jsonp(low.db) res.jsonp(low.db)
} }
// GET /:resource
// GET /:resource?q=
// GET /:resource?attr=&attr= // GET /:resource?attr=&attr=
// GET /:parent/:parentId/:resource // GET /:parent/:parentId/:resource?attr=&attr=
// GET /*?*&limit=
// GET /*?*&offset=&limit=
routes.list = function(req, res, next) { routes.list = function(req, res, next) {
var props = {}
var resource
var _start = req.query._start // Filters list
var _end = req.query._end var filters = {}
delete req.query._start // Result array
delete req.query._end var array
if (req.params.parent) { // Remove offset and limit from req.query to avoid filtering using those
props[req.params.parent.slice(0, - 1) + 'Id'] = +req.params.parentId // parameters
} var offset = req.query.offset
var limit = req.query.limit
for (var key in req.query) { delete req.query.offset
if (key !== 'callback' && key != 'q') props[key] = utils.toNative(req.query[key]) delete req.query.limit
}
if(req.query.q !== undefined) { if (req.query.q) {
var q = req.query.q.toLowerCase(),
keys = _.keys(low(req.params.resource).first()),
callback = function(element) {
for(var i in keys) {
var value = element[keys[i]];
if (value === q || (_.isString(value) && value.toLowerCase().indexOf(q) !== -1)) { var q = req.query.q.toLowerCase()
return true;
}
}
return false; array = low(req.params.resource).where(function(obj) {
for (var key in obj) {
var value = obj[key]
if (_.isString(value) && value.toLowerCase().indexOf(q) !== -1) {
return true
} }
}
}).value()
resource = low(req.params.resource).where(callback).value()
} else if (_(props).isEmpty()) {
resource = low(req.params.resource).value()
} else { } else {
resource = low(req.params.resource).where(props).value()
// Add :parentId filter in case URL is like /:parent/:parentId/:resource
if (req.params.parent) {
filters[req.params.parent.slice(0, - 1) + 'Id'] = +req.params.parentId
}
// Add query parameters filters
// Convert query parameters to their native counterparts
for (var key in req.query) {
if (key !== 'callback') {
filters[key] = utils.toNative(req.query[key])
}
}
// Filter
if (_(filters).isEmpty()) {
array = low(req.params.resource).value()
} else {
array = low(req.params.resource).where(filters).value()
}
} }
if (_start) { // Slicing result
res.setHeader('X-Count', resource.length) if (limit) {
res.setHeader('Access-Control-Expose-Headers', 'X-Count') res.setHeader('X-Total-Count', array.length)
res.setHeader('Access-Control-Expose-Headers', 'X-Total-Count')
resource = resource.slice(_start, _end) offset = offset || 0
array = array.slice(offset, limit)
} }
res.jsonp(resource) res.jsonp(array)
} }
// GET /:resource/:id // GET /:resource/:id
@ -67,7 +86,11 @@ routes.show = function(req, res, next) {
.get(+req.params.id) .get(+req.params.id)
.value() .value()
res.jsonp(resource) if (resource) {
res.jsonp(resource)
} else {
res.status(404).jsonp({})
}
} }
// POST /:resource // POST /:resource
@ -94,7 +117,11 @@ routes.update = function(req, res, next) {
.update(+req.params.id, req.body) .update(+req.params.id, req.body)
.value() .value()
res.jsonp(resource) if (resource) {
res.jsonp(resource)
} else {
res.status(404).jsonp({})
}
} }
// DELETE /:resource/:id // DELETE /:resource/:id
@ -108,7 +135,7 @@ routes.destroy = function(req, res, next) {
low(item[0]).remove(item[1]); low(item[0]).remove(item[1]);
}) })
res.send(204) res.status(204).end()
} }
module.exports = routes module.exports = routes

View File

@ -1,10 +1,15 @@
var fs = require('fs') var fs = require('fs')
var express = require('express')
var cors = require('cors')
var http = require('http')
var path = require('path') var path = require('path')
var http = require('http')
var express = require('express')
var logger = require('morgan')
var cors = require('cors')
var methodOverride = require('method-override') var methodOverride = require('method-override')
var bodyParser = require('body-parser')
var serveStatic = require('serve-static')
var errorhandler = require('errorhandler')
var low = require('lowdb') var low = require('lowdb')
var utils = require('./utils') var utils = require('./utils')
var routes = require('./routes') var routes = require('./routes')
@ -13,36 +18,38 @@ low._.createId = utils.createId
var server = express() var server = express()
server.set('port', process.env.PORT || 3000) server.set('port', process.env.PORT || 3000)
server.use(express.logger('dev')) server.use(logger('dev'))
server.use(express.json()) server.use(bodyParser.json())
server.use(express.urlencoded()) server.use(bodyParser.urlencoded({ extended: false }))
server.use(methodOverride()) server.use(methodOverride())
if (fs.existsSync(process.cwd() + '/public')) { if (fs.existsSync(process.cwd() + '/public')) {
server.use(express.static(process.cwd() + '/public')); server.use(serveStatic(process.cwd() + '/public'));
} else { } else {
server.use(express.static(path.join(__dirname, './public'))); server.use(serveStatic(path.join(__dirname, './public')));
} }
server.use(cors({ origin: true, credentials: true })) server.use(cors({ origin: true, credentials: true }))
server.use(server.router)
if ('development' == server.get('env')) { server.get('/db', routes.db)
server.use(express.errorHandler());
server.route('/:resource')
.get(routes.list)
.post(routes.create)
server.route('/:resource/:id')
.get(routes.show)
.put(routes.update)
.patch(routes.update)
.delete(routes.destroy)
server.get('/:parent/:parentId/:resource', routes.list)
if (process.env.NODE_ENV === 'development') {
// only use in development
server.use(errorhandler())
} }
server.get( '/db' , routes.db)
server.get( '/:resource' , routes.list)
server.get( '/:parent/:parentId/:resource' , routes.list)
server.get( '/:resource/:id' , routes.show)
server.post( '/:resource' , routes.create)
server.put( '/:resource/:id' , routes.update)
server.patch( '/:resource/:id' , routes.update)
server.delete('/:resource/:id' , routes.destroy)
server.low = low server.low = low
module.exports = server module.exports = server

View File

@ -61,43 +61,45 @@ describe('Server', function() {
}) })
}) })
describe('GET /:resource?q=value', function() { describe('GET /:resource?q=', function() {
it('should respond with json and filter all begin of fields of resources', function(done) { it('should respond with json and make a full-text search', function(done) {
request(server) request(server)
.get('/tags?q=photo') .get('/tags?q=pho')
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect([low.db.tags[1], low.db.tags[2]]) .expect([low.db.tags[1], low.db.tags[2]])
.expect(200, done) .expect(200, done)
}) })
it('should respond with json and filter everywhere of all fields of resources', function(done) { it('should return an empty array when nothing is matched', function(done) {
request(server) request(server)
.get('/tags?q=t') .get('/tags?q=nope')
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(low.db.tags) .expect([])
.expect(200, done) .expect(200, done)
})
it('should not respond anything when the query does not many any data', function(done) {
request(server)
.get('/tags?q=nope')
.expect('Content-Type', /json/)
.expect([])
.expect(200, done)
}) })
}) })
describe('GET /:resource?_start=&_end=', function() { describe('GET /:resource?limit=', function() {
it('should respond with sliced array', function(done) { it('should respond with a sliced array', function(done) {
request(server) request(server)
.get('/comments?_start=1&_end=2') .get('/comments?limit=2')
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect('x-total-count', low.db.comments.length.toString())
.expect('Access-Control-Expose-Headers', 'X-Total-Count')
.expect(low.db.comments.slice(0, 2))
.expect(200, done)
})
})
describe('GET /:resource?offset=&limit=', function() {
it('should respond with a sliced array', function(done) {
request(server)
.get('/comments?offset=1&limit=2')
.expect('Content-Type', /json/)
.expect('x-total-count', low.db.comments.length.toString())
.expect('Access-Control-Expose-Headers', 'X-Total-Count')
.expect(low.db.comments.slice(1, 2)) .expect(low.db.comments.slice(1, 2))
.expect(200) .expect(200, done)
.end(function(err, res){
assert.equal(res.headers['x-count'], 5)
done()
})
}) })
}) })
@ -122,8 +124,17 @@ describe('Server', function() {
.expect(low.db.posts[0]) .expect(low.db.posts[0])
.expect(200, done) .expect(200, done)
}) })
it('should respond with 404 if resource is not found', function(done) {
request(server)
.get('/posts/9001')
.expect('Content-Type', /json/)
.expect({})
.expect(404, done)
})
}) })
describe('POST /:resource', function() { describe('POST /:resource', function() {
it('should respond with json and create a resource', function(done) { it('should respond with json and create a resource', function(done) {
request(server) request(server)
@ -150,10 +161,20 @@ describe('Server', function() {
.expect(200) .expect(200)
.end(function(err, res){ .end(function(err, res){
if (err) return done(err) if (err) return done(err)
// assert it was created in database too
assert.deepEqual(low.db.posts[0], {id: 1, body: 'bar', booleanValue: true, integerValue: 1}) assert.deepEqual(low.db.posts[0], {id: 1, body: 'bar', booleanValue: true, integerValue: 1})
done() done()
}) })
}) })
it('should respond with 404 if resource is not found', function(done) {
request(server)
.put('/posts/9001')
.send({id: 1, body: 'bar', booleanValue: 'true', integerValue: '1'})
.expect('Content-Type', /json/)
.expect({})
.expect(404, done)
})
}) })
describe('PATCH /:resource/:id', function() { describe('PATCH /:resource/:id', function() {
@ -166,10 +187,20 @@ describe('Server', function() {
.expect(200) .expect(200)
.end(function(err, res){ .end(function(err, res){
if (err) return done(err) if (err) return done(err)
// assert it was created in database too
assert.deepEqual(low.db.posts[0], {id: 1, body: 'bar'}) assert.deepEqual(low.db.posts[0], {id: 1, body: 'bar'})
done() done()
}) })
}) })
it('should respond with 404 if resource is not found', function(done) {
request(server)
.patch('/posts/9001')
.send({body: 'bar'})
.expect('Content-Type', /json/)
.expect({})
.expect(404, done)
})
}) })
describe('DELETE /:resource/:id', function() { describe('DELETE /:resource/:id', function() {