apeman-app-rest
Version:
apeman app to handle restful endpoint.
162 lines (146 loc) • 4.1 kB
JavaScript
/**
* Endpoint for list
* @augments Endpoint
* @class ListEndpoint
*/
const Endpoint = require('./endpoint')
const { toData, toOffset, toLimit, toOrder, toWhere, defineList } = require('apemanmodel')
const validateInclude = require('../validating/validate_include')
const qs = require('qs')
const co = require('co')
/** @lends ListEndpoint */
class ListEndpoint extends Endpoint {
/**
* @override
*/
getDefaultConfig () {
return {
name: 'list'
}
}
/**
* @override
*/
handle (ctx, next) {
const s = this
let succeed = s.bindSucceed(ctx)
let fail = s.bindFail(ctx)
let { state } = ctx
let { sort, page, include, model } = state
let { associations } = model
return co(function * () {
let error = s.validate(null, state)
if (error) {
return fail(error)
}
let offset = toOffset(model, page)
let limit = toLimit(model, page)
let order = toOrder(model, sort)
let { total, found } = yield s.list(offset, limit, order, state)
let data = toData(model, found).map((data, i) => {
Object.assign(data, {
links: {
self: `./${data.id}`
},
relationships: ListEndpoint.relationships(found[ i ], associations)
})
if (include) {
include.forEach((include) => {
delete data.attributes[ include ]
})
}
return data
})
return succeed(
{ total, offset, limit },
data,
ListEndpoint.links(total, offset, limit),
ListEndpoint.included(found, associations, include)
)
})
}
/**
* @override
*/
validate (data, state) {
let { model, include } = state
let includeError = validateInclude(model, include)
if (includeError) {
return includeError
}
return null
}
list (offset, limit, order, state) {
let { model, transaction, filter, include } = state
return co(function * () {
let list = defineList(model, transaction)
let result = yield list({
offset, limit, order, include,
where: toWhere(model, filter)
})
return {
total: result.count,
found: result.rows
}
})
}
}
Object.assign(ListEndpoint, {
links (total, offset, limit) {
let size = Number(limit)
let first = 1
let last = parseInt(total / size) + 1
let self = parseInt(offset / size) + 1
let prev = self - 1
let next = self + 1
let _link = (number) => '.?' + qs.stringify({
page: { number, size }
})
return {
self: _link(self),
first: first < self ? _link(first) : null,
last: self < last ? _link(last) : null,
prev: first <= prev ? _link(prev) : null,
next: next <= last ? _link(last) : null
}
},
included (found, associations, include) {
let hasInclude = include && (include.length > 0)
if (!hasInclude) {
return undefined
}
return include.map((name) => {
let association = associations[ name ]
let relatedModel = association.target
let hash = {}
found.forEach((found) => {
let data = toData(relatedModel, found[ name ] || [])
for (let included of [].concat(data || [])) {
hash[ included.id ] = included
}
})
return Object.keys(hash).map((id) => hash[ id ])
}).reduce((a, b) => [].concat(a, b), [])
},
relationships (found, associations) {
let names = associations && Object.keys(associations)
let hasRelationships = names.length > 0
if (!hasRelationships) {
return undefined
}
return names.reduce((result, name) => {
let association = associations[ name ]
let relatedModel = association.target
let links = {
self: `./${found.id}/relationships/${name}/`,
related: `./${found.id}/${name}`
}
let data = toData.withoutAttributes(relatedModel.$name, found[ name ])
return Object.assign(result, {
[name]: { data, links }
})
}, {})
}
})
module.exports = ListEndpoint