models/EntryCollection.js

const objectAssignDeep = require('object-assign-deep')
const DraftEntry = require('./DraftEntry')
const CollectionBase = require('./CollectionBase')
const { URLS } = require('../constants')
const { COLLECTION_TYPES } = require('../enums')
const { AuthError } = require('../exceptions')
const { getActualPath } = require('../utils')

/**
 * Entry collection.
 *
 * @augments CollectionBase
 */
class EntryCollection extends CollectionBase {
  /**
   * Title.
   *
   * @type {string}
   */
  title

  /**
   * Title ID.
   *
   * @type {number}
   */
  titleId

  /**
   * Title slug.
   *
   * @type {string}
   */
  titleSlug

  /**
   * Title URL.
   *
   * @type {string}
   */
  titleUrl

  /**
   * Draft entry.
   *
   * @type {(DraftEntry|null)}
   */
  draftEntry

  /**
   * Entry collection.
   *
   * @type {Array<(Entry|EntryForMember)>}
   */
  entries = []

  /**
   * Entry sorting or filtering type.
   *
   * @type {CollectionType}
   */
  collectionType

  /**
   * Searched user's username.
   *
   * @type {string}
   */
  searchAuthor

  /**
   * Searched text.
   *
   * @type {string}
   */
  searchText

  /**
   * Create an entry collection.
   *
   * @param {object}          request                 Axios client.
   * @param {string}          path                    Path or plain title.
   * @param {object}          options                 Parameters that user can specify.
   * @param {CollectionType}  [options.type=null]     Sorting or filtering type.
   * @param {string}          [options.author=null]   Username, if type is 'author' set this parameter.
   * @param {string}          [options.search=null]   Search text,  if type is 'find' set this parameter.
   * @param {number}          [options.page=1]        Page number.
   * @param {string}          [options.cookies=null]  Cookie string.
   */
  constructor (request, path, options) {
    super()
    // handle default options
    const _options = objectAssignDeep(
      {
        page: 1,
        cookies: null,
        type: null,
        author: null,
        search: null
      },
      options
    )

    const isPathPlainTitle = !path.includes('-')

    this.currPage = _options.page
    this.title = isPathPlainTitle ? path : null
    this._request = request
    this._path = isPathPlainTitle ? null : path
    this._cookies = _options.cookies
    this.collectionType = _options.type
    this.searchAuthor = _options.author
    this.searchText = _options.search
  }

  /**
   * Retrieve the entry collection.
   *
   * @returns {Promise} Promise.
   */
  retrieve () {
    // eslint-disable-next-line no-async-promise-executor
    return new Promise(async (resolve, reject) => {
      const isPathProvided = this._path
      const endpoint = isPathProvided
        ? this._path
        : `/${await getActualPath(this.title)}`
      const isEndpointCompatibleWithPageParam = !endpoint.includes('?')

      const isTypeLinks = this.collectionType === COLLECTION_TYPES.LINKS
      const isTypeAuthor = this.collectionType === COLLECTION_TYPES.AUTHOR
      const isTypeFind = this.collectionType === COLLECTION_TYPES.FIND
      const params = isTypeLinks
        ? { a: 'find', keywords: 'http://' }
        : isTypeAuthor
        ? { a: 'search', author: this.searchAuthor }
        : isTypeFind
        ? { a: 'find', keywords: this.searchText }
        : { a: this.collectionType }

      const requestOptions = {
        endpoint,
        cookie: this._cookies,
        params: isEndpointCompatibleWithPageParam
          ? { ...params, p: this.currPage }
          : { ...params },
        resourceName: 'Entries'
      }

      this._request(requestOptions, $ => {
        // dynamically import
        // @see circular dependency issue
        const Entry = require('./Entry')
        const EntryForMember = require('./EntryForMember')

        $('ul#entry-item-list li').each((i, elm) => {
          const entryId = $(elm).data('id')
          const entry = this._cookies
            ? new EntryForMember(this._request, entryId, this._cookies)
            : new Entry(this._request, entryId)
          entry.serialize($, elm)
          this.entries.push(entry)
        })

        const isTitleProvided = this.title

        if (!isTitleProvided) {
          this.title = $('#title').data('title')
        }

        this.titleId = $('#title').data('id')
        this.titleSlug = $('#title').data('slug')
        this.titleUrl = URLS.BASE + $('h1#title a').attr('href')
        this.currPage = $('div.pager').data('currentpage') || 1
        this.pageCount = $('div.pager').data('pagecount') || 1

        const isDraftEntryExist = $('#draft-content').text().length > 0
        const draftEntry = isDraftEntryExist
          ? new DraftEntry(this._request, this.title, this._cookies)
          : null

        if (draftEntry) {
          draftEntry.serialize($)
        }

        this.draftEntry = draftEntry

        resolve()
      })
    })
  }

  /**
   * Create entry.
   *
   * @param   {string}                                content                     Entry content.
   * @param   {object}                                options                     Parameters that user can specify.
   * @param   {boolean}                               [options.saveAsDraft=false] Save as draft.
   * @returns {Promise.<(EntryForMember|DraftEntry)>}                             Created entry.
   * @throws  {AuthError}                                                         User not authorized.
   */
  async createEntry (content, options) {
    const isAuth = this._cookies

    if (!isAuth) {
      throw new AuthError()
    }

    // dynamically import
    // @see circular dependency issue
    const { createEntry } = require('../lib')

    return await createEntry(
      this._request,
      this.title,
      content,
      options,
      this._cookies
    )
  }
}

module.exports = EntryCollection