OurBigBook logoOurBigBook Docs OurBigBook logoOurBigBook.comSite Source code
web/models/comment.js
const { DataTypes } = require('sequelize')

const config = require('../front/config')
const { getCommentSlug } = require('../front/js')
const convert = require('../convert')

module.exports = (sequelize) => {
  const Comment = sequelize.define(
    'Comment',
    {
      // OurBigBook Markup source of the comment.
      source: DataTypes.TEXT,
      // Rendered comment.
      render: DataTypes.TEXT,
      // User-visible numeric identifier for the issue. 1-based.
      number: DataTypes.INTEGER,
      // Upvote count.
      score: {
        type: DataTypes.INTEGER,
        allowNull: false,
        defaultValue: 0,
      },
    },
    {
      indexes: [
        {
          fields: ['issueId', 'number'],
          unique: true,
        },

        // Foreign key indexes https://docs.ourbigbook.com/database-guidelines
        { fields: ['issueId'], },
        { fields: ['authorId'], },

        // Efficient global listings.
        { fields: ['createdAt'], },
        { fields: ['updatedAt'], },

        // Efficient listing of issues by a given user.
        { fields: ['authorId', 'createdAt'], },
        { fields: ['authorId', 'updatedAt'], },
      ],
    },
  )

  Comment.createSideEffects = async function(author, issue, fields, opts={}) {
    return sequelize.transaction({ transaction: opts.transaction }, async (transaction) => {
      const [comment, newIssue] = await Promise.all([
        sequelize.models.Comment.create(
          Object.assign({ authorId: author.id, issueId: issue.id }, fields),
          { transaction }
        ),
        await author.addIssueFollowSideEffects(issue, { transaction }),
      ])
      return comment
    })
  }

  Comment.getComments = async function({
    authorId,
    articleId,
    issueId,
    limit,
    offset,
    order,
    transaction,
  }) {
    const where = {}
    if (authorId !== undefined) {
      where.authorId = authorId
    }
    if (order === undefined) {
      order = [['createdAt', 'DESC']]
    }
    const articleInclude = {
      model: sequelize.models.Article,
      as: 'article',
      required: true,
      subQuery: false,
    }
    if (articleId) {
      articleInclude.where = { id: articleId }
    }
    let issueIncludeWhere
    if (issueId) {
      issueIncludeWhere = { id: issueId }
    }
    return sequelize.models.Comment.findAndCountAll({
      include: [
        {
          model: sequelize.models.User,
          as: 'author',
        },
        {
          model: sequelize.models.Issue,
          as: 'issue',
          required: true,
          subQuery: false,
          where: issueIncludeWhere,
          include: [
            articleInclude
          ],
        },
      ],
      limit,
      offset,
      order,
      transaction,
      where,
    })
  }

  Comment.prototype.destroySideEffects = async function(fields, opts={}) {
    return this.destroy({ transaction: opts.transaction })
  }

  Comment.prototype.toJson = async function(loggedInUser) {
    const ret = {
      id: this.id,
      number: this.number,
      source: this.source,
      render: this.render,
      createdAt: this.createdAt.toISOString(),
      updatedAt: this.updatedAt.toISOString(),
      score: this.score,
    }
    const author = this.author
    if (author) {
      ret.author = await author.toJson(loggedInUser)
    }
    const issue = this.issue
    if (issue) {
      ret.issue = await issue.toJson(loggedInUser)
    }
    return ret
  }

  Comment.prototype.rerender = async function({ convertOptionsExtra, ignoreErrors, transaction }={}) {
    if (ignoreErrors === undefined)
      ignoreErrors = false
    await sequelize.transaction({ transaction }, async (transaction) => {
      try {
        await convert.convertComment({
          comment: this,
          sequelize,
          transaction,
          user: this.author,
        })
      } catch(e) {
        if (ignoreErrors) {
          console.log(e)
        } else {
          throw e
        }
      }
    })
  }

  Comment.rerender = async ({ convertOptionsExtra, ignoreErrors, log }={}) => {
    if (log === undefined)
      log = false
    let offset = 0
    while (true) {
      const comments = await sequelize.models.Comment.findAll({
        include: [
          {
            model: sequelize.models.Issue,
            as: 'issue',
            include: [{
              model: sequelize.models.Article,
              as: 'article',
            }]
          },
          {
            model: sequelize.models.User,
            as: 'author',
          }
        ],
        offset,
        limit: config.maxArticlesInMemory,
        order: [
          [{ model: sequelize.models.Issue, as: 'issue' }, { model: sequelize.models.Article, as: 'article' }, 'slug', 'ASC'],
          [{ model: sequelize.models.Issue, as: 'issue' }, 'number', 'ASC'],
          ['number', 'ASC']
        ],
      })
      if (comments.length === 0)
        break
      for (const comment of comments) {
        if (log)
          console.log(getCommentSlug(comment))
        await comment.rerender({ convertOptionsExtra, ignoreErrors })
      }
      offset += config.maxArticlesInMemory
    }
  }

  Comment.ALLOWED_SORTS_EXTRA = {}

  return Comment
}