Manual Reference Source Test

src/controllers/ContentService.js

// Import the necessary modules.
// @flow
import pMap from 'p-map'
/**
 * MongoDB object modeling designed to work in an asynchronous environment.
 * @external {MongooseModel} https://github.com/Automattic/mongoose
 */
import type { MongooseModel } from 'mongoose'

/**
 * ContentService class for the CRUD operations.
 * @type {ContentService}
 */
export default class ContentService {

  /**
   * The model of the service.
   * @type {MongooseModel}
   */
  Model: MongooseModel

  /**
   * The maximum items to display per page.
   * @type {number}
   */
  pageSize: number

  /**
   * Simple projection for showing multiple content items.
   * @type {Object}
   */
  projection: Object

  /**
   * The query of the service.
   * @type {Object}
   */
  query: Object

  /**
   * Create a new ContentService.
   * @param {!Object} options - The options for the content service.
   * @param {!MongooseModel} options.Model - The model of the service.
   * @param {!Object} options.projection - The projection of the service.
   * @param {!Object} options.query={} - The query of the service.
   * @param {!number} [options.pageSize=25] - The page size of the service.
   */
  constructor({
    Model,
    projection,
    query = {},
    pageSize = 25
  }: Object): void {
    /**
     * The item type of the service.
     * @type {MongooseModel}
     */
    this.Model = Model
    /**
     * The maximum items to display per page.
     * @type {number}
     */
    this.pageSize = pageSize
    /**
     * Simple projection for showing multiple content items.
     * @type {Object}
     */
    this.projection = projection
    /**
     * Query to only get the content items.
     * @type {Object}
     */
    this.query = query
  }

  /**
   * Get all the available pages.
   * @param {!string} [base=''] - The base of the url to display.
   * @returns {Promise<Array<string>, Error>} - A list of pages which are
   * available.
   */
  getContents(base: string = ''): Promise<Array<string>> {
    return this.Model.count(this.query).then(count => {
      const pages = Math.ceil(count / this.pageSize)
      const docs = []

      for (let i = 1; i < pages + 1; i++) {
        docs.push(`${base}/${i}`)
      }

      return docs
    })
  }

  /**
   * Get content from one page.
   * @param {?Object} sort - The sort object to sort and order content.
   * @param {!number} [p=1] - The page to get.
   * @param {!Object} [query=this.query] - A copy of the query object to
   * get the objects.
   * @returns {Promise<Array<MongooseModel>, Error>} - The content of one page.
   */
  getPage(
    sort?: Object | null,
    p?: number | string = 1,
    query?: Object = {
      ...this.query
    }
  ): Promise<Array<any>> {
    const page = !isNaN(p) ? Number(p) - 1 : 0
    const offset = page * this.pageSize

    let aggregateQuery = [{
      $match: query
    }, {
      $project: this.projection
    }]

    if (sort) {
      aggregateQuery = [...aggregateQuery, {
        $sort: sort
      }]
    }

    if (typeof p === 'string' && p.toLowerCase() === 'all') {
      return this.Model.aggregate(aggregateQuery)
    }

    aggregateQuery = [...aggregateQuery, {
      $skip: offset
    }, {
      $limit: this.pageSize
    }]

    return this.Model.aggregate(aggregateQuery)
  }

  /**
   * Get the content from the database with an id.
   * @param {!string} id - The id of the content to get.
   * @param {!Object} projection - The projection for the content.
   * @returns {Promise<MongooseModel, Error>} - The details of the content.
   */
  getContent(id: string, projection?: Object): Promise<any> {
    return this.Model.findOne({
      _id: id
    }, projection)
  }

  /**
   * Insert the content into the database.
   * @param {!Object} obj - The object to insert.
   * @returns {Promise<MongooseModel, Error>} - The created content.
   */
  createContent(obj: Object): Promise<any> {
    return new this.Model(obj).save()
  }

  /**
   * Insert multiple content models into the database.
   * @param {!Array<Object>} arr - The array of content to insert.
   * @returns {Promise<Array<MongooseModel>, Error>} - The inserted content.
   */
  createMany(arr: Array<Object>): Promise<Array<any>> {
    return pMap(arr, async obj => {
      const found = await this.Model.findOne({
        _id: obj.slug
      })

      return found
        ? this.updateContent(obj.slug, obj)
        : this.createContent(obj)
    }, {
      concurrency: 1
    })
  }

  /**
   * Update the content.
   * @param {!string} id - The id of the content to get.
   * @param {!Object} obj - The object to update.
   * @returns {Promise<MongooseModel, Error>} - The updated content.
   */
  updateContent(id: string, obj: Object): Promise<any> {
    return this.Model.findOneAndUpdate({
      _id: id
    }, new this.Model(obj), {
      upsert: true,
      new: true
    })
  }

  /**
   * Update multiple content models into the database.
   * @param {!Array<Object>} arr - The array of content to update.
   * @returns {Promise<Array<MongooseModel>, Error>} - The updated content.
   */
  updateMany(arr: Array<Object>): Promise<Array<any>> {
    return this.createMany(arr)
  }

  /**
   * Delete a content model.
   * @param {!string} id - The id of the content to delete.
   * @returns {Promise<MongooseModel, Error>} - The deleted content.
   */
  deleteContent(id: string): Promise<any> {
    return this.Model.findOneAndRemove({
      _id: id
    })
  }

  /**
   * Delete multiple content models from the database.
   * @param {!Array<Object>} arr - The array of content to delete.
   * @returns {Promise<Array<MongooseModel>, Error>} - The deleted content.
   */
  deleteMany(arr: Array<Object>): Promise<Array<any>> {
    return pMap(arr, obj => this.deleteContent(obj._id))
  }

  /**
   * Get random content.
   * @returns {Promise<MongooseModel, Error>} - Random content.
   */
  getRandomContent(): Promise<any> {
    return this.Model.aggregate([{
      $match: this.query
    }, {
      $project: this.projection
    }, {
      $sample: {
        size: 1
      }
    }, {
      $limit: 1
    }]).then(([ res ]) => res)
  }

}