OurBigBook
models/id.js
const ourbigbook = require('../index')

module.exports = (sequelize) => {
  const { DataTypes, Op } = sequelize.Sequelize
  const Id = sequelize.define(
    'Id',
    {
      // Don't use `id` because that is the default pk column.
      idid: {
        type: DataTypes.TEXT,
        allowNull: false,
        // Used to be unique, but not the case anymore, because when converting a directory
        // with duplicates, we have to do the duplicate check at the end to account e.g. if an
        // ID is moved between two files. Previousy, we were nuking the DB of files to be converted,
        // and just extracing IDs every time. But with timestamp skipping, we just don't know if the
        // ID was moved between files or not until everything is done.
        //
        // Once there are no conversion errors however and the DB is stable, then they should be unique.
        //unique: true,
      },
      // The ID of the toplevel header for this element. E.g. in:
      //
      // ``
      // = h1
      //
      // == h2
      // {toplevel}
      //
      // === h3
      //
      // == h2 2
      // ``
      //
      // both h2 and h3 had toplevel_id = h2.
      toplevel_id: {
        type: DataTypes.TEXT,
        // Can be NULL e.g. for an image before any header.
      },
      ast_json: {
        type: DataTypes.TEXT,
        allowNull: false,
      },
      // Needed for title to title checks. This does duplicate
      // ast_json.macro_name for the toplevel element, but for nested elements in the JSON
      // we have no choice, so just keeping it duplicated for the toplevel for simplicity.
      // We could use database JSON functions instead, but these will be slower,
      // and have less support/portability.
      macro_name: {
        type: DataTypes.TEXT,
        allowNull: false,
      },
    },
    {
      indexes: [
        { fields: ['idid'], },
        { fields: ['defined_at'], },
      ],
    }
  )

  Id.findDuplicates = async (paths, transaction) => {
    const where = {}
    if (paths.length) {
      where.path = paths
    }
    return sequelize.models.Id.findAll({
      include: [
        {
          model: sequelize.models.Id,
          as: 'duplicate',
          required: true,
          on: {
            '$Id.idid$': { [Op.col]: 'duplicate.idid' },
            '$Id.id$': { [Op.ne]: { [Op.col]: 'duplicate.id' } },
          },
        },
        {
          model: sequelize.models.File,
          as: 'idDefinedAt',
          required: true,
          where,
        },
      ],
      order: [
        ['idid', 'ASC'],
        [sequelize.col('idDefinedAt.path'), 'ASC'],
      ],
      transaction,
    })
  }

  Id.findInvalidTitleTitle = async (paths, transaction) => {
    let where
    if (paths === undefined) {
      where = { path: paths }
    }
    return sequelize.models.Id.findAll({
      include: [
        {
          model: sequelize.models.File,
          as: 'idDefinedAt',
          required: true,
          where,
        },
        {
          model: sequelize.models.Ref,
          as: 'from',
          required: true,
          where: {
            type: sequelize.models.Ref.Types[ourbigbook.REFS_TABLE_X_TITLE_TITLE],
          },
          include: [
            {
              model: sequelize.models.Id,
              as: 'to',
              required: true,
              where: {
                macro_name: { [Op.ne]: ourbigbook.Macro.HEADER_MACRO_NAME },
              }
            },
          ],
        },
      ],
      order: [
        [sequelize.col('idDefinedAt.path'), 'ASC'],
        ['idid', 'ASC'],
      ],
      transaction,
    })
  }

  return Id
}