src/route.js
import ErrorResponder from './responders/error_responder'
import JsonResponder from './responders/json_responder'
import XlsxResponder from './responders/xlsx_responder'
import CsvResponder from './responders/csv_responder'
import XmlResponder from './responders/xml_responder'
import Component from './component'
import Renderer from './renderer'
import _ from 'lodash'
class Route extends Component {
action = null
method = 'get'
processor = _.noop
constructor(config = {}) {
super(config)
if(config.action) this.setAction(config.action)
if(config.method) this.setMethod(config.method)
if(config.processor) this.setProcessor(config.processor)
if(config.responder) this.setResponder(config.responder)
if(config.serializer) this.setSerializer(config.serializer)
}
setAction(action) {
this.action = action
}
setMethod(method) {
this.method = method
}
setProcessor(processor) {
this.processor = processor
}
setSerializer(serializer) {
this._setOption('serializer', serializer)
}
setResponder(responder) {
this._setOption('responder', responder)
}
render(routePath = '', routeOptions = {}, routeHooks = {}) {
const path = this._mergePaths(routePath, this.path)
const options = this._mergeOptions(routeOptions, this.options)
const hooks = this._mergeHooks(routeHooks, this.hooks)
const handler = (req, res, next) => {
return options.knex.transaction(async trx => {
try {
req = await this._alterRequest(req, trx, options, hooks.alterRequest)
await this._runHooks(req, trx, null, options, hooks.beforeProcessor, false)
const result = await this.processor(req, trx, options) || null
await this._runHooks(req, trx, result, options, hooks.afterProcessor, true)
const renderer = new Renderer({ req, trx, result, options })
const rendered = await renderer.render()
const altered = await this._alterRecord(req, trx, rendered, options, hooks.alterRecord)
const responder = this._getResponder(req, res, altered, options)
await responder.render()
await this._runHooks(req, trx, result, options, hooks.beforeCommit, true)
await trx.commit(result)
await this._runHooks(req, trx, result, options, hooks.afterCommit, true)
} catch(error) {
await this._runHooks(req, trx, null, options, hooks.beforeRollback, false)
await trx.rollback(error)
if(process.env.NODE_ENV !== 'production') console.log(error)
const error_responder = new ErrorResponder({ res, error })
error_responder.render()
}
})
}
return {
method: this.method,
path: `${path}.:format?`,
options,
hooks,
handler
}
}
async _alterRequest(req, trx, options, hooks) {
return await Promise.reduce(hooks, async (req, hook) => {
return await hook(req, trx, options) || req
}, req)
}
async _runHooks(req, trx, result, options, hooks, includeResult) {
await Promise.mapSeries(hooks, async (hook) => {
if(includeResult) return await hook(req, trx, result, options)
await hook(req, trx, options)
})
}
async _alterRecord(req, trx, result, options, hooks) {
const alterRecord = (req, trx, record, options) => Promise.reduce(hooks, async (record, hook) => {
return await hook(req, trx, record, options) || record
}, record)
if(_.isPlainObject(result) && result.records) {
const records = await Promise.mapSeries(result.records, record => {
return alterRecord(req, trx, record, options)
})
return {
...result,
records
}
}
return alterRecord(req, trx, result, options)
}
_getResponder(req, res, result, options) {
const responderClass = this._getResponderClass(req, options)
return new responderClass({ req, res, result, options })
}
_getResponderClass(req, options) {
if(options.responder) return options.responder
const format = req.params && req.params.format ? req.params.format : options.defaultFormat
if(_.includes(['xml'], format)) return XmlResponder
if(_.includes(['xls','xlsx'], format)) return XlsxResponder
if(_.includes(['tsv','csv'], format)) return CsvResponder
return JsonResponder
}
}
export default Route