holidayextras/jsonapi-server

View on GitHub
lib/jsonApi.js

Summary

Maintainability
B
5 hrs
Test Coverage
'use strict'
const jsonApi = module.exports = { }
jsonApi._version = require(require('path').join(__dirname, '../package.json')).version
jsonApi._resources = { }
jsonApi._apiConfig = { }

const _ = {
  assign: require('lodash.assign'),
  pick: require('lodash.pick')
}
const ourJoi = require('./ourJoi.js')
const router = require('./router.js')
const responseHelper = require('./responseHelper.js')
const handlerEnforcer = require('./handlerEnforcer.js')
const pagination = require('./pagination.js')
const routes = require('./routes')
const url = require('url')
const metrics = require('./metrics.js')
const schemaValidator = require('./schemaValidator.js')

jsonApi.Joi = ourJoi.Joi
jsonApi.metrics = metrics.emitter
jsonApi.MemoryHandler = require('./MemoryHandler')
jsonApi.ChainHandler = require('./ChainHandler')

jsonApi.setConfig = apiConfig => {
  jsonApi._apiConfig = apiConfig
  jsonApi._apiConfig.base = jsonApi._cleanBaseUrl(jsonApi._apiConfig.base)
  jsonApi._apiConfig.pathPrefix = apiConfig.urlPrefixAlias || jsonApi._concatenateUrlPrefix(jsonApi._apiConfig)
  responseHelper.setBaseUrl(jsonApi._apiConfig.pathPrefix)
  responseHelper.setMetadata(jsonApi._apiConfig.meta)
}

jsonApi.authenticate = router.authenticateWith

jsonApi._concatenateUrlPrefix = config => url.format({
  protocol: config.protocol,
  hostname: config.hostname,
  port: config.port,
  pathname: config.base
})

jsonApi._cleanBaseUrl = base => {
  if (!base) {
    base = ''
  }
  if (base[0] !== '/') {
    base = `/${base}`
  }
  if (base[base.length - 1] !== '/') {
    base += '/'
  }
  return base
}

jsonApi.define = resourceConfig => {
  if (!resourceConfig.resource.match(/^[A-Za-z0-9_]*$/)) {
    throw new Error(`Resource '${resourceConfig.resource}' contains illegal characters!`)
  }
  resourceConfig.namespace = resourceConfig.namespace || 'default'
  resourceConfig.searchParams = resourceConfig.searchParams || { }
  jsonApi._resources[resourceConfig.resource] = resourceConfig

  handlerEnforcer.wrap(resourceConfig.handlers)

  resourceConfig.handlers.initialise = resourceConfig.handlers.initialise || resourceConfig.handlers.initialize
  if (resourceConfig.handlers.initialise) {
    resourceConfig.handlers.initialise(resourceConfig)
  }

  Object.keys(resourceConfig.attributes).forEach(attribute => {
    if (!attribute.match(/^[A-Za-z0-9_]*$/)) {
      throw new Error(`Attribute '${attribute}' on ${resourceConfig.resource} contains illegal characters!`)
    }
  })

  resourceConfig.searchParams = _.assign({
    type: ourJoi.Joi.any().required().valid(resourceConfig.resource)
      .description(`Always "${resourceConfig.resource}"`)
      .example(resourceConfig.resource),
    sort: ourJoi.Joi.any()
      .description('An attribute to sort by')
      .example('title'),
    filter: ourJoi.Joi.any()
      .description('An attribute+value to filter by')
      .example('title'),
    fields: ourJoi.Joi.any()
      .description('An attribute+value to filter by')
      .example('title'),
    include: ourJoi.Joi.any()
      .description('An attribute to include')
      .example('title')
  }, resourceConfig.searchParams, pagination.joiPageDefinition)

  resourceConfig.attributes = _.assign({
    id: ourJoi.Joi.string().required()
      .description('Unique resource identifier')
      .example('1234'),
    type: ourJoi.Joi.string().required().valid(resourceConfig.resource)
      .description(`Always "${resourceConfig.resource}"`)
      .example(resourceConfig.resource),
    meta: ourJoi.Joi.object().optional()
  }, resourceConfig.attributes)

  resourceConfig.onCreate = _.pick.apply(_, [].concat(resourceConfig.attributes, Object.keys(resourceConfig.attributes).filter(i => (resourceConfig.attributes[i]._meta.indexOf('readonly') === -1) && (!(resourceConfig.attributes[i]._settings || { }).__as))))
}

jsonApi.onUncaughtException = errHandler => {
  jsonApi._errHandler = errHandler
}

jsonApi.getExpressServer = () => router.getExpressServer()

jsonApi.start = () => {
  schemaValidator.validate(jsonApi._resources)
  router.applyMiddleware()
  routes.register()
  if (!jsonApi._apiConfig.router) {
    router.listen(jsonApi._apiConfig.port)
  }
}

jsonApi.close = () => {
  router.close()
  metrics.emitter.removeAllListeners('data')
  for (const i in jsonApi._resources) {
    const resourceConfig = jsonApi._resources[i]
    if (resourceConfig.handlers.close) resourceConfig.handlers.close()
  }
}