Collection.js

const yaml = require('js-yaml')
const { verifyRequired } = require('@bowtie/utils')

const Base = require('./Base')
const CollectionItem = require('./CollectionItem')

/**
 * Collection class
 */
class Collection extends Base {
  /**
   * Create a Collection object
   *
   * @constructor
   * @param {Object} options - Options for collection
   * @param {Object} options.jekyll - The Jekyll instance for this collection
   * @param {String} options.name - The name of this collection
   * @param {String} options.path - The path to this collection
   */
  constructor (options = {}) {
    super(options)

    verifyRequired(options, [ 'jekyll', 'name', 'path' ])

    this.github = this.jekyll.github
    this.repoPath = this.jekyll.repoPath
    this.repoParams = this.jekyll.repoParams
    this.defaultParams = this.jekyll.defaultParams
  }

  /**
   * Parse Jekyll file path
   *
   * @param {String} path - Path to be parsed (for Jekyll front matter + body content)
   * @param {Object} [params] - Additional params (sent to github)
   * @returns {Promise<Object>} - Returns promise with parsed data
   */
  parsePath (path, params = {}) {
    return new Promise(
      (resolve, reject) => {
        if (this._isCached(path)) {
          return resolve(this._cached(path))
        }

        this.logger.info(`Parsing collection file: ${path} (from: ${this.name})`)

        this.github.files(this._params(params, { path })).then(({ file }) => {
          const defaults = {
            fields: {},
            body: ''
          }

          try {
            const fileContent = Buffer.from(file.content, 'base64').toString()
            const fileParts = fileContent.split('---')

            if (fileParts.length > 1) {
              defaults['fields'] = yaml.safeLoad(fileParts[1])
            }

            if (fileParts.length > 2) {
              fileParts.shift()
              fileParts.shift()
              defaults['body'] = fileParts.join('---')
            }
          } catch (err) {
            this.logger.warn(`Invalid collection fields: ${this.path}`)
          }

          resolve(this._cache(path, defaults))
        }).catch(err => {
          this.logger.warn(`Error from collection: ${this.name} [${this.path}] - Attempting to load: ${path}`)
          this.logger.warn(err)

          resolve(this._cache(path, {}))
        })
      }
    )
  }

  /**
   * Get defaults for a collection (from "_fields.md" file in collection dir)
   *
   * @param {Object} [params] - Additional params (sent to github)
   * @returns {Promise<Object>} - Returns promise with parsed data
   */
  defaults (params = {}) {
    return this.parsePath(`${this.path}/_fields.md`, params)
  }

  /**
   * Load a single key from resolved defaults (fields or body)
   *
   * @param {String} key - Key to be loaded from defaults
   * @param {Object} [params] - Additional params (sent to github)
   * @returns {Promise<Object>} - Returns promise with parsed data for specified key
   */
  defaultsKey (key, params = {}) {
    return this.defaults(params).then(defaults => {
      return Promise.resolve(defaults[key])
    })
  }

  /**
   * Get body for this collection (using defaultsKey method)
   *
   * @param {Object} [params] - Additional params (sent to github)
   * @returns {Promise<String>} - Returns promise with parsed body
   */
  body (params = {}) {
    return this.defaultsKey('body', params)
  }

  /**
   * Get fields for this collection (using defaultsKey method)
   *
   * @param {Object} [params] - Additional params (sent to github)
   * @returns {Promise<Object>} - Returns promise with parsed fields
   */
  fields (params = {}) {
    return this.defaultsKey('fields', params)
  }

  /**
   * Load items for this collection
   *
   * @param {Object} [params] - Additional params (sent to github)
   * @returns {Promise<Array>} - Returns promise with array of CollectionItem objects
   */
  items (params = {}) {
    return new Promise(
      (resolve, reject) => {
        if (this._isCached('items')) {
          return resolve(this._cached('items'))
        }

        this.logger.info(`Loading items for collection: ${this.path}`)

        this.github.files(this._params(params, { path: this.path })).then(({ files }) => {
          let items = []

          if (Array.isArray(files)) {
            items = files.filter(item => {
              return (item.name.substr(0, 1) !== '_')
            }).map(item => {
              return new CollectionItem(Object.assign({}, item, {
                collection: this
              }))
            })
          } else {
            this.logger.warn(`Invalid collection items: ${this.path}`)
          }

          resolve(this._cache('items', items))
        }).catch(reject)
      }
    )
  }

  /**
   * Create a new item in this collection
   *
   * @param {Object} data - Data for new collection item
   * @param {String} data.name - Name for new collection item
   * @param {Object} [data.fields] - Fields for new collection item
   * @param {String} [data.body] - Content for new collection item
   * @param {Object} [params] - Additional params (sent to github)
   */
  createItem (data, params = {}) {
    verifyRequired(data, [ 'name' ])

    data['path'] = `${this.path}/${data['name']}`
    data['collection'] = this

    const item = new CollectionItem(data)

    return item.save(params)
  }
}

module.exports = Collection