mahaplatform/backframe

View on GitHub
src/renderer.js

Summary

Maintainability
B
5 hrs
Test Coverage
import moment from 'moment'
import _ from 'lodash'

class Renderer {

  req = null

  result = null

  trx = null

  options = null

  constructor(config = {}) {
    this.req = config.req
    this.result = config.result
    this.trx = config.trx
    this.options = config.options
  }

  async render() {

    if(!this.result) return null

    const transforms = [
      this._renderRecord.bind(this),
      ...this.req.query.$select ? [this._selectFields(this.req.query.$select)] : []
    ]

    if(this.result.records) {

      return await this._applyToRecords(this.req, this.trx, this.result, transforms, this.options)

    }

    return await this._applyToRecord(this.req, this.trx, this.result, transforms, this.options)

  }

  async _applyToRecords(req, trx, result, operations, options) {

    if(!operations) return result

    const records = await Promise.mapSeries(result.records, async (record) => {

      return await this._applyToRecord(req, trx, record, operations, options)

    })

    return {
      ...result,
      records
    }

  }

  async _applyToRecord(req, trx, record, operations, options) {

    return await Promise.reduce(_.castArray(operations), async (record, operation) => {

      return await operation(req, trx, record, options)

    }, record)

  }

  _getCacheKey(req, record) {

    if(!record.get('updated_at')) return null

    const url = req.path.substr(1).replace(/\//g, '-')

    const id = record.get('id')

    const timestamp = moment(record.get('updated_at')).format('x')

    return [url, id, timestamp].filter(item => item !== null).join('-')

  }

  async _cache(key, options, fn) {

    if(!options.cache || !options.redis || !key) return await fn()

    const value = await new Promise((resolve, reject) => {

      options.redis.get(key, (err, data) => {

        if(err) reject(err)

        resolve(data)

      })

    })

    if(value) return JSON.parse(value)

    const rendered = await fn()

    await options.redis.set(key, JSON.stringify(rendered), 'EX', 24 * 60 * 60)

    return rendered

  }

  async _renderRecord(req, trx, record, options) {

    if(!record.toJSON) return record

    const key = this._getCacheKey(req, record)

    const fn = async () => {

      return options.serializer ? await options.serializer(req, trx, record, options) : record.toJSON()

    }

    return await this._cache(key, options, fn)

  }

  _selectFields(select) {

    return (req, trx, record) => {

      const fields = this._selectedKeys(select, record)

      return select ? _.pick(record, fields) : record

    }

  }

  _selectedKeys(select, record) {

    if(_.isPlainObject(select)) return Object.values(select)

    if(_.isNil(select)) return this._flattenKeys(record)

    return _.castArray(select)

  }

  _flattenKeys(hash, prefix = '') {

    return Object.keys(hash).reduce((keys, key) => [
      ...keys,
      ..._.isObject(hash[key]) ? this._flattenKeys(hash[key], `${prefix}${key}.`) : [`${prefix}${key}`]
    ], [])

  }

}

export default Renderer