Comparing version 0.4.7 to 0.5.0
94
index.js
// Dependencies | ||
// ------------ | ||
var url = require('url'); | ||
var express = require('express'); | ||
var mongoose = require('mongoose'); | ||
var lingo = require('lingo'); | ||
var url = require('url'); | ||
var Controller = require('./Controller'); | ||
var exec = require('./middleware/exec'); | ||
var headers = require('./middleware/headers'); | ||
var configure = require('./middleware/configure'); | ||
var query = require('./middleware/query'); | ||
var send = require('./middleware/send'); | ||
var validation = require('./middleware/validation'); | ||
var documents = require('./middleware/documents'); | ||
// Private Members | ||
// --------------- | ||
var controllers = []; | ||
@@ -26,2 +21,8 @@ // Module Definition | ||
controllers.forEach(function (controller) { | ||
var route = url.resolve('/', controller.get('plural')); | ||
controller.initialize(); | ||
app.use(route, controller); | ||
}); | ||
return app; | ||
@@ -33,77 +34,8 @@ }; | ||
baucis.rest = function (options) { | ||
if (!options.singular) throw new Error('Must provide the Mongoose schema name'); | ||
var controller = Controller(options); | ||
var controller = express(); | ||
var basePathOption = options.basePath ? options.basePath.replace(/\/?$/, '/') : '/'; | ||
var basePath = url.resolve('/', basePathOption); | ||
var basePathWithId = url.resolve(basePath, ':id'); | ||
var basePathWithOptionalId = url.resolve(basePath, ':id?'); | ||
controller.set('model', mongoose.model(options.singular)); | ||
controller.set('plural', options.plural || lingo.en.pluralize(options.singular)); | ||
controller.set('findBy', options.findBy || '_id'); | ||
controller.set('basePath', basePath); | ||
controller.set('basePathWithId', basePathWithId); | ||
controller.set('basePathWithOptionalId', basePathWithOptionalId); | ||
Object.keys(options).forEach(function (key) { | ||
controller.set(key, options[key]); | ||
}); | ||
controller.use(express.json()); | ||
// Initialize baucis state | ||
controller.all(basePathWithOptionalId, function (request, response, next) { | ||
request.baucis = {}; | ||
next(); | ||
}); | ||
// Allow/Accept headers | ||
controller.all(basePathWithId, headers.allow); | ||
controller.all(basePathWithId, headers.accept); | ||
controller.all(basePath, headers.allow); | ||
controller.all(basePath, headers.accept); | ||
if (options.configure) options.configure(controller); | ||
// Set Link header if desired | ||
if (options.relations === true) { | ||
controller.head(basePathWithId, headers.link); | ||
controller.get(basePathWithId, headers.link); | ||
controller.post(basePathWithId, headers.link); | ||
controller.put(basePathWithId, headers.link); | ||
controller.head(basePath, headers.linkCollection); | ||
controller.get(basePath, headers.linkCollection); | ||
controller.post(basePath, headers.linkCollection); | ||
controller.put(basePath, headers.linkCollection); | ||
} | ||
// Add all pre-query middleware | ||
if (options.all) controller.all(basePathWithOptionalId, options.all); | ||
if (options.head) controller.head(basePathWithOptionalId, options.head); | ||
if (options.get) controller.get(basePathWithOptionalId, options.get); | ||
if (options.post) controller.post(basePathWithOptionalId, options.post); | ||
if (options.put) controller.put(basePathWithOptionalId, options.put); | ||
if (options.del) controller.del(basePathWithOptionalId, options.del); | ||
// Add routes for singular documents | ||
if (options.head !== false) controller.head(basePathWithId, query.head, configure.controller, configure.query, exec.count, documents.lastModified, documents.send); | ||
if (options.get !== false) controller.get(basePathWithId, query.get, configure.controller, configure.query, exec.exec, documents.lastModified, documents.send); | ||
if (options.post !== false) controller.post(basePathWithId, query.post, configure.controller, configure.query, exec.exec, documents.lastModified, documents.send); | ||
if (options.put !== false) controller.put(basePathWithId, query.put, configure.controller, configure.query, documents.lastModified, documents.send); | ||
if (options.del !== false) controller.del(basePathWithId, query.del, configure.controller, configure.query, exec.exec, documents.lastModified, documents.send); | ||
// Add routes for collections of documents | ||
if (options.head !== false) controller.head(basePath, configure.conditions, query.headCollection, configure.controller, configure.query, exec.count, documents.lastModified, documents.send); | ||
if (options.get !== false) controller.get(basePath, configure.conditions, query.getCollection, configure.controller, configure.query, exec.exec, documents.lastModified, documents.send); | ||
if (options.post !== false) controller.post(basePath, query.postCollection, documents.lastModified, documents.send); | ||
if (options.put !== false) controller.put(basePath, query.putCollection, configure.controller, configure.query, exec.exec, documents.lastModified, documents.send); | ||
if (options.del !== false) controller.del(basePath, configure.conditions, query.delCollection, configure.controller, configure.query, exec.exec, documents.lastModified, documents.send); | ||
// Publish unless told not to | ||
if (options.publish !== false) app.use(url.resolve('/', controller.get('plural')), controller); | ||
if (options.publish !== false) controllers.push(controller); | ||
return controller; | ||
}; |
@@ -12,100 +12,106 @@ // Private Members | ||
var middleware = module.exports = { | ||
// Retrieve header for the addressed document | ||
head: function (request, response, next) { | ||
var Model = request.app.get('model'); | ||
request.baucis.noBody = true; | ||
request.baucis.query = Model.findOne(getFindCondition(request)); | ||
next(); | ||
}, | ||
// Retrieve documents matching conditions | ||
headCollection: function (request, response, next) { | ||
var Model = request.app.get('model'); | ||
request.baucis.noBody = true; | ||
request.baucis.query = Model.find(request.baucis.conditions); | ||
next(); | ||
}, | ||
// Retrive the addressed document | ||
get: function (request, response, next) { | ||
var Model = request.app.get('model'); | ||
request.baucis.query = Model.findOne(getFindCondition(request)); | ||
next(); | ||
}, | ||
// Retrieve documents matching conditions | ||
getCollection: function (request, response, next) { | ||
var Model = request.app.get('model'); | ||
request.baucis.query = Model.find(request.baucis.conditions); | ||
next(); | ||
}, | ||
// Treat the addressed document as a collection, and push | ||
// the addressed object to it | ||
post: function (request, response, next) { | ||
response.send(405); // method not allowed (as of yet unimplemented) | ||
}, | ||
// Create a new document and return its ID | ||
postCollection: function (request, response, next) { | ||
var body = request.body; | ||
var Model = request.app.get('model'); | ||
instance: { | ||
// Retrieve header for the addressed document | ||
head: function (request, response, next) { | ||
var Model = request.app.get('model'); | ||
request.baucis.noBody = true; | ||
request.baucis.query = Model.findOne(getFindCondition(request)); | ||
next(); | ||
}, | ||
// Retrive the addressed document | ||
get: function (request, response, next) { | ||
var Model = request.app.get('model'); | ||
request.baucis.query = Model.findOne(getFindCondition(request)); | ||
next(); | ||
}, | ||
// Treat the addressed document as a collection, and push | ||
// the addressed object to it | ||
post: function (request, response, next) { | ||
response.send(405); // method not allowed (as of yet unimplemented) | ||
}, | ||
// Replace the addressed document, or create it if it doesn't exist | ||
put: function (request, response, next) { | ||
var Model = request.app.get('model'); | ||
var id = request.params.id; | ||
// Must be an object or array | ||
if (!body || typeof body !== 'object') { | ||
return next(new Error('Must supply a document or array to POST')); | ||
} | ||
if (request.body._id && id !== request.body._id) { | ||
return next(new Error('ID mismatch')); | ||
} | ||
// Make it an array if it wasn't already | ||
if (!Array.isArray(body)) body = [ body ]; | ||
// Can't send id for update, even if unchanged | ||
delete request.body._id; | ||
// No empty arrays | ||
if (body.length === 0) return next(new Error('Array was empty.')); | ||
Model.findOne(getFindCondition(request), function (error, doc) { | ||
if (error) return next(error); | ||
if (!doc) return next(new Error('No document with that ID was found')); | ||
response.status(201); | ||
doc.set(request.body); | ||
Model.create(body, function (error) { | ||
if (error) return next(error); | ||
var documents = Array.prototype.slice.apply(arguments).slice(1); | ||
request.baucis.documents = documents.length === 1 ? documents[0] : documents; | ||
doc.save(function (error, savedDoc) { | ||
if (error) return next(error); | ||
request.baucis.documents = savedDoc; | ||
next(); | ||
}); | ||
}); | ||
}, | ||
// Delete the addressed object | ||
del: function (request, response, next) { | ||
var Model = request.app.get('model'); | ||
request.baucis.query = Model.remove(getFindCondition(request)); | ||
next(); | ||
}); | ||
} | ||
}, | ||
// Replace the addressed document, or create it if it doesn't exist | ||
put: function (request, response, next) { | ||
var Model = request.app.get('model'); | ||
var id = request.params.id; | ||
collection: { | ||
// Retrieve documents matching conditions | ||
head: function (request, response, next) { | ||
var Model = request.app.get('model'); | ||
request.baucis.noBody = true; | ||
request.baucis.query = Model.find(request.baucis.conditions); | ||
next(); | ||
}, | ||
// Retrieve documents matching conditions | ||
get: function (request, response, next) { | ||
var Model = request.app.get('model'); | ||
request.baucis.query = Model.find(request.baucis.conditions); | ||
next(); | ||
}, | ||
// Create a new document and return its ID | ||
post: function (request, response, next) { | ||
var body = request.body; | ||
var Model = request.app.get('model'); | ||
if (request.body._id && id !== request.body._id) { | ||
return next(new Error('ID mismatch')); | ||
} | ||
// Must be an object or array | ||
if (!body || typeof body !== 'object') { | ||
return next(new Error('Must supply a document or array to POST')); | ||
} | ||
// Can't send id for update, even if unchanged | ||
delete request.body._id; | ||
// Make it an array if it wasn't already | ||
if (!Array.isArray(body)) body = [ body ]; | ||
Model.findOne(getFindCondition(request), function (error, doc) { | ||
if (error) return next(error); | ||
if (!doc) return next(new Error('No document with that ID was found')); | ||
// No empty arrays | ||
if (body.length === 0) return next(new Error('Array was empty.')); | ||
doc.set(request.body); | ||
response.status(201); | ||
doc.save(function (error, savedDoc) { | ||
Model.create(body, function (error) { | ||
if (error) return next(error); | ||
request.baucis.documents = savedDoc; | ||
var documents = Array.prototype.slice.apply(arguments).slice(1); | ||
request.baucis.documents = documents.length === 1 ? documents[0] : documents; | ||
next(); | ||
}); | ||
}); | ||
}, | ||
// Replace all docs with given docs ... | ||
putCollection: function (request, response, next) { | ||
response.send(405); // method not allowed (as of yet unimplemented) | ||
}, | ||
// Delete the addressed object | ||
del: function (request, response, next) { | ||
var Model = request.app.get('model'); | ||
request.baucis.query = Model.remove(getFindCondition(request)); | ||
next(); | ||
}, | ||
// Delete all documents matching conditions | ||
delCollection: function (request, response, next) { | ||
var Model = request.app.get('model'); | ||
request.baucis.query = Model.remove(request.baucis.conditions); | ||
next(); | ||
}, | ||
// Replace all docs with given docs ... | ||
put: function (request, response, next) { | ||
response.send(405); // method not allowed (as of yet unimplemented) | ||
}, | ||
// Delete all documents matching conditions | ||
del: function (request, response, next) { | ||
var Model = request.app.get('model'); | ||
request.baucis.query = Model.remove(request.baucis.conditions); | ||
next(); | ||
} | ||
} | ||
}; |
{ | ||
"name": "baucis", | ||
"version": "0.4.7", | ||
"version": "0.5.0", | ||
"main": "index.js", | ||
@@ -5,0 +5,0 @@ "scripts": { |
138
README.md
@@ -1,2 +0,2 @@ | ||
baucis v0.4.7 | ||
baucis v0.5.0 | ||
=============== | ||
@@ -8,8 +8,4 @@ | ||
Those versions published to npm represent release versions. Those versions not published to npm are development releases. | ||
Baucis uses [semver](http://semver.org). | ||
Relase versions of baucis can be considered stable. Baucis uses [semver](http://semver.org). | ||
Please report issues on GitHub if bugs are encountered. | ||
![David Rjckaert III - Philemon and Baucis Giving Hospitality to Jupiter and Mercury](http://github.com/wprl/baucis/raw/master/david_rijckaert_iii-philemon_and_baucis.jpg "Hermes is like: 'Hey Baucis, don't kill that goose. And thanks for the REST.'") | ||
@@ -104,22 +100,2 @@ | ||
Use plain old Connect/Express middleware, including pre-existing modules like `passport`. For example, set the `all` option to add middleware to be called before all the model's API routes. | ||
baucis.rest({ | ||
singular: 'vegetable', | ||
all: function (request, response, next) { | ||
if (request.isAuthenticated()) return next(); | ||
return response.send(401); | ||
} | ||
}); | ||
Or, set some middleware for specific HTTP verbs or disable verbs completely: | ||
baucis.rest({ | ||
singular: 'vegetable', | ||
get: [middleware1, middleware2], | ||
post: middleware3, | ||
del: false, | ||
put: false | ||
}); | ||
`baucis.rest` returns an instance of the controller created to handle the schema's API routes. | ||
@@ -132,14 +108,69 @@ | ||
var controller = baucis.rest({ | ||
singular: 'robot', | ||
configure: function (controller) { | ||
// Add middleware before all other rotues in the controller | ||
controller.use(express.cookieParser()); | ||
} | ||
singular: 'robot' | ||
}); | ||
// Add middleware after default controller routes | ||
// Add middleware before API routes | ||
controller.use(function () { ... }); | ||
// Do other stuff... | ||
controller.set('some option name', 'value'); | ||
controller.listen(3000); | ||
Customize them with plain old Express/Connect middleware, including pre-existing modules like `passport`. Middleware can be registered like so: | ||
controller.request(function (request, response, next) { | ||
if (request.isAuthenticated()) return next(); | ||
return response.send(401); | ||
}); | ||
Baucis adds middleware registration functions for three stages of the request cycle: | ||
| Name | Description | | ||
| ---- | ----------- | | ||
| request | This stage of middleware will be called after baucis applies defaults based on the request, but before the Mongoose query is generated | | ||
| query | This stage of middleware will be called after baucis applies defaults to the Mongoose query object, but before the documents or count is retrieved from the databased. The query can be accessed in your custom middleware via `request.baucis.query`. | | ||
| documents | This stage of middleware will be called after baucis executes the query, but before the documents or count are sent in the response. The documents/count can be accessed in your custom middleware via `request.baucis.documents`. | | ||
Each of these functions has three forms: | ||
The first form is the most specific. The first argument lets you specify whether the middleware applies to document instances (paths like `/foos/:id`) or to collection requests (paths like `/foos`). The second argument is a space-delimted list of HTTP verbs that the middleware should be applied to. The third argument is the middleware function to add or an array of middleware functions. | ||
controller.request('instance', 'head get del', middleware); | ||
controller.request('collection', 'post', middleware); | ||
To add middleware that applies to both document instances and collections, the first argument is omitted: | ||
controller.query('post put', function (request, response, next) { | ||
// do something with request.baucis.query | ||
next(); | ||
}); | ||
To apply middleware to all API routes, just pass the function or array: | ||
controller.request(function (request, response, next) { | ||
if (request.isAuthenticated()) return next(); | ||
return response.send(401); | ||
}); | ||
controller.documents(function (request, response, next) { | ||
var ok = true; | ||
if (typeof request.baucis.documents === 'number') return next(); | ||
[].concat(request.baucis.documents).forEach(function (doc) { | ||
if (!ok) return; | ||
if (doc.owner !== request.user.id) { | ||
ok = false; | ||
next(new Error('User does not own this.')); | ||
} | ||
}); | ||
if (ok) next(); | ||
}); | ||
To disable verbs completely: | ||
baucis.rest({ | ||
singular: 'vegetable', | ||
del: false, | ||
put: false | ||
}); | ||
Controller Options | ||
@@ -154,33 +185,34 @@ ------------------ | ||
| publish | Set to `false` to not publish the controller's endpoints when `baucis()` is called. | | ||
| select | Select or deselect fields for all queries | | ||
| select | Select or deselect fields for all queries e.g. `'foo +bar -password'` | | ||
| findBy | Use another field besides `_id` for entity queries. | | ||
| lastModified | Set the `Last-Modified` HTTP header useing the given field. Currently this field must be a `Date`. | | ||
| restrict | Alter the query based on request parameters. | | ||
| configure | Add middleware to the controller before generated paths. | | ||
An example of embedding a controller within another controller | ||
var subcontroller = baucis.rest({ | ||
singular: 'bar', | ||
basePath: '/:fooId/bars', | ||
publish: false, | ||
select: 'foo +bar -password', | ||
findBy: 'baz', | ||
lastModified: 'modifiedDate', | ||
restrict: function (query, request) { | ||
// Only retrieve bars that are children of the given foo | ||
query.where('parent', request.params.fooId); | ||
} | ||
publish: false | ||
}); | ||
subcontroller.query(function (request, response, next) { | ||
// Only retrieve bars that are children of the given foo | ||
request.baucis.query.where('parent', request.params.fooId); | ||
next(); | ||
}); | ||
// Didn't publish, so have to manually initialize | ||
subcontroller.initialize(); | ||
var controller = baucis.rest({ | ||
singular: 'foo', | ||
configure: function (controller) { | ||
// Embed the subcontroller at /foos/:fooId/bars | ||
controller.use(subcontroller); | ||
singular: 'foo' | ||
}); | ||
// Embed arbitrary middleware at /foos/qux | ||
controller.use('/qux', function (request, response, next) { | ||
// Do something cool… | ||
next(); | ||
}); | ||
} | ||
// Embed the subcontroller at /foos/:fooId/bars | ||
controller.use(subcontroller); | ||
// Embed arbitrary middleware at /foos/qux | ||
controller.use('/qux', function (request, response, next) { | ||
// Do something cool… | ||
next(); | ||
}); | ||
@@ -187,0 +219,0 @@ |
controller.request('put', [ midleware, ... ]); | ||
controller.query('get', [ ... ]); | ||
controller.document('delete', [ ... ]); | ||
controller.doc('delete', [ ... ]); | ||
controller.queryDocument | ||
controller.queryCollection | ||
controller.query('document', 'get put', [ ... ]); | ||
controller.query('collection', 'get put', [ ... ]); | ||
controller.document([ ... ]); // all verbs | ||
controller.document('put get delete', [ ... ]); // some verbs | ||
controller.on('baucis:document', 'post put', [ ... ]); | ||
controller.on('query', 'post put', [ ... ]); | ||
controller.register('baucis:document', 'post put', [ ... ]); | ||
controller.register('query', 'post put', [ ... ]); | ||
controller.baucis('query', 'post put', [ ... ]); | ||
var controller = baucis.rest({ | ||
request: { | ||
all: middleware1 | ||
}, | ||
query: { | ||
post: [], | ||
put: [], | ||
get: [], | ||
delete: [], | ||
head: [] | ||
}, | ||
document: { | ||
post: [], | ||
put: [], | ||
get: [] | ||
} | ||
}) | ||
// TODO make baucis.rest call controllers' initialze() fn if it hasn't been called already by dev? | ||
// --- | ||
@@ -9,0 +42,0 @@ |
@@ -7,2 +7,3 @@ var mongoose = require('mongoose'); | ||
var server; | ||
var controller; | ||
@@ -34,11 +35,12 @@ var fixture = module.exports = { | ||
baucis.rest({ | ||
controller = baucis.rest({ | ||
singular: 'vegetable', | ||
lastModified: 'lastModified', | ||
all: function (request, response, next) { | ||
if (request.query.block === "true") return response.send(401); | ||
next(); | ||
} | ||
lastModified: 'lastModified' | ||
}); | ||
controller.request(function (request, response, next) { | ||
if (request.query.block === 'true') return response.send(401); | ||
next(); | ||
}); | ||
app = express(); | ||
@@ -45,0 +47,0 @@ app.use('/api/v1', baucis()); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
288877
34
1521
223