Files
2016-07-03 18:50:11 +02:00

270 lines
6.8 KiB
JavaScript

var express = require('express')
var _ = require('lodash')
var pluralize = require('pluralize')
var utils = require('../utils')
module.exports = function (db, name) {
// Create router
var router = express.Router()
// Embed function used in GET /name and GET /name/id
function embed (resource, e) {
e && [].concat(e)
.forEach(function (externalResource) {
if (db.get(externalResource).value) {
var query = {}
var singularResource = pluralize.singular(name)
query[singularResource + 'Id'] = resource.id
resource[externalResource] = db.get(externalResource).filter(query).value()
}
})
}
// Expand function used in GET /name and GET /name/id
function expand (resource, e) {
e && [].concat(e)
.forEach(function (innerResource) {
var plural = pluralize(innerResource)
if (db.get(plural).value()) {
var prop = innerResource + 'Id'
resource[innerResource] = db.get(plural).getById(resource[prop]).value()
}
})
}
// GET /name
// GET /name?q=
// GET /name?attr=&attr=
// GET /name?_end=&
// GET /name?_start=&_end=&
// GET /name?_embed=&_expand=
function list (req, res, next) {
// Resource chain
var chain = db.get(name)
// Remove q, _start, _end, ... from req.query to avoid filtering using those
// parameters
var q = req.query.q
var _start = req.query._start
var _end = req.query._end
var _sort = req.query._sort
var _order = req.query._order
var _limit = req.query._limit
var _embed = req.query._embed
var _expand = req.query._expand
delete req.query.q
delete req.query._start
delete req.query._end
delete req.query._sort
delete req.query._order
delete req.query._limit
delete req.query._embed
delete req.query._expand
// Automatically delete query parameters that can't be found
// in the database
Object.keys(req.query).forEach(function (query) {
var arr = db.get(name).value()
for (var i in arr) {
if (
_.has(arr[i], query) ||
query === 'callback' ||
query === '_' ||
query.indexOf('_lte') !== -1 ||
query.indexOf('_gte') !== -1 ||
query.indexOf('_ne') !== -1 ||
query.indexOf('_like') !== -1
) return
}
delete req.query[query]
})
if (q) {
// Full-text search
q = q.toLowerCase()
chain = chain.filter(function (obj) {
for (var key in obj) {
var value = obj[key]
if (db._.deepQuery(value, q)) {
return true
}
}
})
}
Object.keys(req.query).forEach(function (key) {
// Don't take into account JSONP query parameters
// jQuery adds a '_' query parameter too
if (key !== 'callback' && key !== '_') {
// Always use an array, in case req.query is an array
var arr = [].concat(req.query[key])
chain = chain.filter(function (element) {
return arr
.map(utils.toNative)
.map(function (value) {
var isDifferent = key.indexOf('_ne') !== -1
var isRange = key.indexOf('_lte') !== -1 || key.indexOf('_gte') !== -1
var isLike = key.indexOf('_like') !== -1
var path = key.replace(/(_lte|_gte|_ne|_like)$/, '')
var elementValue = _.get(element, path)
if (isRange) {
var isLowerThan = key.indexOf('_gte') !== -1
if (isLowerThan) {
return value <= elementValue
} else {
return value >= elementValue
}
} else if (isDifferent) {
return value !== elementValue
} else if (isLike) {
return new RegExp(value, 'i').test(elementValue)
} else {
return _.matchesProperty(key, value)(element)
}
}).reduce(function (a, b) {
return a || b
})
})
}
})
// Sort
if (_sort) {
_order = _order || 'ASC'
chain = chain.sortBy(function (element) {
return _.get(element, _sort)
})
if (_order === 'DESC') {
chain = chain.reverse()
}
}
// Slice result
if (_end || _limit) {
res.setHeader('X-Total-Count', chain.size())
res.setHeader('Access-Control-Expose-Headers', 'X-Total-Count')
}
_start = parseInt(_start, 10) || 0
if (_end) {
_end = parseInt(_end, 10)
chain = chain.slice(_start, _end)
} else if (_limit) {
_limit = parseInt(_limit, 10)
chain = chain.slice(_start, _start + _limit)
}
// embed and expand
chain = chain
.cloneDeep()
.forEach(function (element) {
embed(element, _embed)
expand(element, _expand)
})
res.locals.data = chain.value()
next()
}
// GET /name/:id
// GET /name/:id?_embed=&_expand
function show (req, res, next) {
var _embed = req.query._embed
var _expand = req.query._expand
var id = utils.toNative(req.params.id)
var resource = db.get(name).getById(id).value()
if (resource) {
// Clone resource to avoid making changes to the underlying object
resource = _.cloneDeep(resource)
// Embed other resources based on resource id
// /posts/1?_embed=comments
embed(resource, _embed)
// Expand inner resources based on id
// /posts/1?_expand=user
expand(resource, _expand)
res.locals.data = resource
}
next()
}
// POST /name
function create (req, res, next) {
for (var key in req.body) {
req.body[key] = utils.toNative(req.body[key])
}
var resource = db.get(name)
.insert(req.body)
.value()
res.status(201)
res.locals.data = resource
next()
}
// PUT /name/:id
// PATCH /name/:id
function update (req, res, next) {
for (var key in req.body) {
req.body[key] = utils.toNative(req.body[key])
}
var id = utils.toNative(req.params.id)
var chain = db.get(name)
chain = req.method === 'PATCH'
? chain.updateById(id, req.body)
: chain.replaceById(id, req.body)
var resource = chain.value()
if (resource) {
res.locals.data = resource
}
next()
}
// DELETE /name/:id
function destroy (req, res, next) {
var resource = db.get(name).removeById(utils.toNative(req.params.id)).value()
// Remove dependents documents
var removable = db._.getRemovable(db.getState())
_.each(removable, function (item) {
db.get(item.name).removeById(item.id).value()
})
if (resource) {
res.locals.data = {}
}
next()
}
router.route('/')
.get(list)
.post(create)
router.route('/:id')
.get(show)
.put(update)
.patch(update)
.delete(destroy)
return router
}