cf-crud-service-api-builder
Advanced tools
Comparing version 0.1.3 to 0.5.0
module.exports = buildApi | ||
var routes = | ||
{ get: require('./endpoints/get') | ||
, post: require('./endpoints/post') | ||
, put: require('./endpoints/put') | ||
, patch: require('./endpoints/patch') | ||
, 'delete': require('./endpoints/delete') | ||
} | ||
{ get: require('./endpoints/get') | ||
, post: require('./endpoints/post') | ||
, put: require('./endpoints/put') | ||
, patch: require('./endpoints/patch') | ||
, 'delete': require('./endpoints/delete') | ||
} | ||
, EventEmitter = require('events').EventEmitter | ||
, createPipe = require('piton-pipe').createPipe | ||
function buildApi(service, urlRoot, router, logger, middleware, verbs) { | ||
function Api() { EventEmitter.call(this) } | ||
Api.prototype = Object.create(EventEmitter.prototype) | ||
var hooks = | ||
{ 'create:request': createPipe() | ||
, 'create:response': createPipe() | ||
, 'read:response': createPipe() | ||
, 'update:request': createPipe() | ||
, 'update:response': createPipe() | ||
, 'partialUpdate:request': createPipe() | ||
, 'partialUpdate:response': createPipe() | ||
} | ||
Api.prototype.hook = function (name, fn) { | ||
if (!hooks[name]) throw new Error('No hook exists for: ' + name) | ||
hooks[name].add(fn) | ||
} | ||
var api = new Api() | ||
if (!Array.isArray(middleware) && typeof middleware !== 'function') throw new Error('Middleware is not defined') | ||
@@ -20,5 +42,7 @@ | ||
verbs.forEach(function (verb) { | ||
routes[verb](service, urlRoot, router, logger, middleware) | ||
routes[verb](service, urlRoot, router, logger, middleware, api.emit.bind(api), hooks) | ||
}) | ||
return api | ||
} |
module.exports = del | ||
function del(service, urlRoot, router, logger, middleware) { | ||
function del(service, urlRoot, router, logger, middleware, emit) { | ||
@@ -15,3 +15,4 @@ router.delete(urlRoot + '/:id', middleware, function (req, res) { | ||
} else { | ||
res.send(204) | ||
emit('delete', req) | ||
res.sendStatus(204) | ||
} | ||
@@ -18,0 +19,0 @@ }) |
@@ -6,3 +6,3 @@ module.exports = get | ||
function get(service, urlRoot, router, logger, middleware) { | ||
function get(service, urlRoot, router, logger, middleware, emit, hooks) { | ||
@@ -16,3 +16,9 @@ router.get(urlRoot + '/:id', middleware, function (req, res) { | ||
if (!entity) return res.status(404).json({ status: 'Not found' }) | ||
res.json(entity) | ||
hooks['read:response'].run(entity, function (error, postHookEntity) { | ||
if (error) { | ||
var message = 'Error running response hook for "' + service.name + '" service' | ||
return res.status(400).json({ error: message }) | ||
} | ||
res.status(200).json(postHookEntity) | ||
}) | ||
}) | ||
@@ -45,8 +51,15 @@ }) | ||
} | ||
res.json( | ||
{ results: data | ||
, page: req.query.pagination.page | ||
, pageSize: req.query.pagination.pageSize | ||
, totalItems: count | ||
}) | ||
hooks['read:response'].run(data, function (error, postHookData) { | ||
if (error) { | ||
var message = 'Error running response hook for "' + service.name + '" service' | ||
return res.status(400).json({ error: message }) | ||
} | ||
res.json( | ||
{ results: postHookData | ||
, page: req.query.pagination.page | ||
, pageSize: req.query.pagination.pageSize | ||
, totalItems: count | ||
}) | ||
}) | ||
}) | ||
@@ -53,0 +66,0 @@ }) |
module.exports = patch | ||
function patch(service, urlRoot, router, logger, middleware) { | ||
function patch(service, urlRoot, router, logger, middleware, emit, hooks) { | ||
@@ -8,11 +8,20 @@ router.patch(urlRoot + '/:id', middleware, function (req, res) { | ||
req.body._id = req.params.id | ||
service.partialUpdate(req.body, {}, function (error, updatedObject) { | ||
if (error) { | ||
res.status(400).json(error) | ||
} else { | ||
res.status(200).json(updatedObject) | ||
} | ||
hooks['partialUpdate:request'].run(req.body, function (error, postHookBody) { | ||
if (error) return res.status(400).json(error) | ||
service.partialUpdate(postHookBody, {}, function (error, updatedObject) { | ||
if (error) { | ||
res.status(400).json(error) | ||
} else { | ||
emit('partialUpdate', req, updatedObject) | ||
hooks['partialUpdate:response'].run(updatedObject, function (error, postHookUpdatedObject) { | ||
if (error) return res.status(400).json(error) | ||
res.status(200).json(postHookUpdatedObject) | ||
}) | ||
} | ||
}) | ||
}) | ||
}) | ||
} |
module.exports = post | ||
function post(service, urlRoot, router, logger, middleware) { | ||
function post(service, urlRoot, router, logger, middleware, emit, hooks) { | ||
router.post(urlRoot, middleware, function (req, res) { | ||
logger.debug('POST received', JSON.stringify(req.body)) | ||
service.create(req.body, function (error, newObject) { | ||
if (error) { | ||
res.status(400).json(error) | ||
} else { | ||
res.status(201).json(newObject) | ||
} | ||
hooks['create:request'].run(req.body, function (error, postHookBody) { | ||
if (error) return res.status(400).json(error) | ||
service.create(postHookBody, function (error, newObject) { | ||
if (error) { | ||
res.status(400).json(error) | ||
} else { | ||
emit('create', req, newObject) | ||
hooks['create:response'].run(newObject, function (error, postHookNewObject) { | ||
if (error) return res.status(400).json(error) | ||
res.status(201).json(postHookNewObject) | ||
}) | ||
} | ||
}) | ||
}) | ||
@@ -14,0 +21,0 @@ }) |
@@ -6,3 +6,3 @@ module.exports = put | ||
function put(service, urlRoot, router, logger, middleware) { | ||
function put(service, urlRoot, router, logger, middleware, emit, hooks) { | ||
@@ -35,2 +35,3 @@ // Optional :id url param to allow for arrays to be PUT | ||
} else { | ||
emit('update', req, updatedObject) | ||
cb(null, updatedObject) | ||
@@ -41,9 +42,16 @@ } | ||
async.map(req.body, update, function (error, updatedObjects) { | ||
var status = updateError ? 400 : 200 | ||
// If array has a length of 1 then only return first item | ||
res.status(status).json(updatedObjects.length === 1 ? updatedObjects[0] : updatedObjects) | ||
hooks['update:request'].run(req.body, function (error, postHookBody) { | ||
if (error) return res.status(400).json(error) | ||
async.map(postHookBody, update, function (error, updatedObjects) { | ||
var status = updateError ? 400 : 200 | ||
hooks['update:response'].run(updatedObjects, function (error, postHookUpdatedObject) { | ||
if (error) return res.status(400).json(error) | ||
// If array has a length of 1 then only return first item | ||
res.status(status).json(postHookUpdatedObject.length === 1 ? postHookUpdatedObject[0] : postHookUpdatedObject) | ||
}) | ||
}) | ||
}) | ||
}) | ||
} |
@@ -10,18 +10,18 @@ module.exports = createFilterParser | ||
, ignoredTypes = [ Object, Array ] | ||
, type = null | ||
, type = getType(key, parentKey) | ||
if (parentKey) { | ||
type = schema.schema[parentKey].type | ||
} else { | ||
type = schema.schema[key].type | ||
} | ||
// Skip ignored types and Schemata Arrays | ||
if (ignoredTypes.indexOf(type) === -1 && !type.arraySchema) { | ||
if (Array.isArray(value)) { | ||
var newValue = [] | ||
value.forEach(function (item) { | ||
newValue.push(schema.castProperty(type, item)) | ||
if (isMongoOperator(key) && Array.isArray(value)) { | ||
value = value.map(function (item) { | ||
// Recursively cast objects like `{ $in: [1, 2, 3 }` | ||
if (typeof item === 'object' && null !== item) return parseObject(item) | ||
// Do a simple cast if they arent objects | ||
return schema.castProperty(type, item) | ||
}) | ||
value = newValue | ||
} else if (Array.isArray(value)) { | ||
value = value.map(function (item) { | ||
return schema.castProperty(type, item) | ||
}) | ||
} else if (typeof value === 'object' && null !== value) { | ||
@@ -38,3 +38,18 @@ value = parseObject(value, key) | ||
function getType(key, parentKey) { | ||
if (parentKey) { | ||
return schema.schema[parentKey].type | ||
} else if (isMongoOperator(key)) { | ||
return {} | ||
} else { | ||
return schema.schema[key].type | ||
} | ||
} | ||
// Key starts with $ e.g. $or, $and | ||
function isMongoOperator(key) { | ||
return key.match(/^\$/) | ||
} | ||
return parseObject | ||
} |
@@ -5,3 +5,3 @@ { | ||
"description": "Build an HTTP API for a crud-service", | ||
"version": "0.1.3", | ||
"version": "0.5.0", | ||
"tags": [], | ||
@@ -27,16 +27,19 @@ "repository": { | ||
"dependencies": { | ||
"lodash.assign": "^2.4.1" | ||
"async": "^1.5.0", | ||
"lodash.assign": "^3.2.0", | ||
"piton-pipe": "0.0.4" | ||
}, | ||
"devDependencies": { | ||
"async": "^0.9.0", | ||
"crud-service": "^0.1.0", | ||
"express": "^3.18.4", | ||
"istanbul": "0", | ||
"jshint": "2", | ||
"mocha": "1", | ||
"save": "0.0.20", | ||
"schemata": "^2.0.2", | ||
"supertest": "^0.15.0", | ||
"validity": "0.0.3" | ||
"body-parser": "^1.14.2", | ||
"crud-service": "^0.2.0", | ||
"express": "^4.13.3", | ||
"istanbul": "^0.4.1", | ||
"jshint": "^2.9.1-rc1", | ||
"mc-logger": "0.0.0", | ||
"mocha": "^2.3.4", | ||
"save": "^2.1.1", | ||
"schemata": "^3.0.0", | ||
"supertest": "^1.1.0", | ||
"validity": "^0.0.3" | ||
} | ||
} |
@@ -21,2 +21,48 @@ # cf-crud-service-api-builder | ||
### Hooks | ||
When using the api builder, you can hook into certain actions to manipulate the request data before it is sent to the database or the response data before it is sent to the requester. | ||
```js | ||
var api = crudServiceApiBuilder(service, '/article', router, logger, middleware) | ||
api.hook('create:request', function (data, cb) { | ||
// do whatever you like with the data | ||
cb(null, data) | ||
}) | ||
``` | ||
Supported hooks are: | ||
* `create:request` | ||
* `create:response` | ||
* `read:response` | ||
* `update:request` | ||
* `update:response` | ||
* `partialUpdate:request` | ||
* `partialUpdate:response` | ||
### Events | ||
When using the api builder, you can listen for certain events so that you can add hooks to perform your own actions after a request has been succesful. e.g | ||
```js | ||
var api = crudServiceApiBuilder(service, '/article', router, logger, middleware) | ||
api.on('create', function (req, newArticle) { | ||
// do whatever you like with the req and article object | ||
}) | ||
``` | ||
Supported events are: | ||
* `create` | ||
* `update` | ||
* `partialUpdate` | ||
* `delete` | ||
## Credits | ||
@@ -23,0 +69,0 @@ Built by developers at [Clock](http://clock.co.uk). |
@@ -5,3 +5,3 @@ var createGetEndpoint = require('../../endpoints/get') | ||
, request = require('supertest') | ||
, logger = { debug: noop, info: noop, warn: noop, error: noop } | ||
, logger = require('mc-logger') | ||
, assert = require('assert') | ||
@@ -11,7 +11,8 @@ , async = require('async') | ||
, qs = require('querystring') | ||
, createPipe = require('piton-pipe').createPipe | ||
function noop() {} | ||
describe('GET endpoint', function () { | ||
var hooks = { 'read:response': createPipe() } | ||
describe('GET /prefix/:id', function () { | ||
@@ -28,3 +29,3 @@ | ||
var app = express() | ||
createGetEndpoint(service, '/things', app, logger, []) | ||
createGetEndpoint(service, '/things', app, logger, [], null, hooks) | ||
request(app) | ||
@@ -42,3 +43,3 @@ .get('/things/1') | ||
var app = express() | ||
createGetEndpoint(service, '/things', app, logger, []) | ||
createGetEndpoint(service, '/things', app, logger, [], null, hooks) | ||
request(app) | ||
@@ -88,3 +89,3 @@ .get('/things/2') | ||
var app = express() | ||
createGetEndpoint(service, '/things', app, logger, []) | ||
createGetEndpoint(service, '/things', app, logger, [], null, hooks) | ||
request(app) | ||
@@ -128,3 +129,3 @@ .get('/things') | ||
var app = express() | ||
createGetEndpoint(service, '/things', app, logger, []) | ||
createGetEndpoint(service, '/things', app, logger, [], null, hooks) | ||
request(app) | ||
@@ -131,0 +132,0 @@ .get('/things?' + qs.stringify({ pagination: JSON.stringify({ page: 3, pageSize: 7 }) })) |
@@ -69,2 +69,21 @@ var schemata = require('schemata') | ||
it('should not throw for keys that start with $', function () { | ||
assert.doesNotThrow(function () { | ||
var params = { $or: [] } | ||
params = filterParser(params) | ||
}, /Cannot read property 'type' of undefined/) | ||
}) | ||
it('should work for keys that start with $', function () { | ||
var params = { $or: [ { string: 1 } ] } | ||
params = filterParser(params) | ||
assert.equal(typeof params.$or[0].string, 'string') | ||
}) | ||
it('should recursively for keys that start with $', function () { | ||
var params = { $or: [ { string: { $in: [1] } }, { string: { $size: 0 } } ] } | ||
params = filterParser(params) | ||
assert.equal(params.$or[0].string.$in[0], '1') | ||
assert.equal(params.$or[1].string.$size, '0') | ||
}) | ||
}) | ||
@@ -71,0 +90,0 @@ |
@@ -7,6 +7,4 @@ module.exports = createService | ||
, validity = require('validity') | ||
, logger = { debug: noop, info: noop, warn: noop, error: noop } | ||
, logger = require('mc-logger') | ||
function noop() {} | ||
function createService() { | ||
@@ -13,0 +11,0 @@ return new Service('thing', save('thing', { logger: logger }), createSchema()) |
33139
19
827
72
3
11
+ Addedasync@^1.5.0
+ Addedpiton-pipe@0.0.4
+ Addedasync@1.5.2(transitive)
+ Addedlodash._baseassign@3.2.0(transitive)
+ Addedlodash._basecopy@3.0.1(transitive)
+ Addedlodash._bindcallback@3.0.1(transitive)
+ Addedlodash._createassigner@3.1.1(transitive)
+ Addedlodash._getnative@3.9.1(transitive)
+ Addedlodash._isiterateecall@3.0.9(transitive)
+ Addedlodash.assign@3.2.0(transitive)
+ Addedlodash.isarguments@3.1.0(transitive)
+ Addedlodash.isarray@3.0.4(transitive)
+ Addedlodash.keys@3.1.2(transitive)
+ Addedlodash.restparam@3.6.1(transitive)
+ Addedpiton-pipe@0.0.4(transitive)
- Removedlodash._basebind@2.4.1(transitive)
- Removedlodash._basecreate@2.4.1(transitive)
- Removedlodash._basecreatecallback@2.4.1(transitive)
- Removedlodash._basecreatewrapper@2.4.1(transitive)
- Removedlodash._createwrapper@2.4.1(transitive)
- Removedlodash._isnative@2.4.1(transitive)
- Removedlodash._objecttypes@2.4.1(transitive)
- Removedlodash._setbinddata@2.4.1(transitive)
- Removedlodash._shimkeys@2.4.1(transitive)
- Removedlodash._slice@2.4.1(transitive)
- Removedlodash.assign@2.4.1(transitive)
- Removedlodash.bind@2.4.1(transitive)
- Removedlodash.identity@2.4.1(transitive)
- Removedlodash.isfunction@2.4.1(transitive)
- Removedlodash.isobject@2.4.1(transitive)
- Removedlodash.keys@2.4.1(transitive)
- Removedlodash.noop@2.4.1(transitive)
- Removedlodash.support@2.4.1(transitive)
Updatedlodash.assign@^3.2.0