Source

backend/database/models/tag.js

/* This file is part of Ezra Bible App.

   Copyright (C) 2019 - 2023 Ezra Bible App Development Team <contact@ezrabibleapp.net>

   Ezra Bible App is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 2 of the License, or
   (at your option) any later version.

   Ezra Bible App is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with Ezra Bible App. See the file LICENSE.
   If not, see <http://www.gnu.org/licenses/>. */

'use strict';

/**
 * The Tag model is used to manage tags.
 * @typedef Tag
 * @category Model
 */
module.exports = (sequelize, DataTypes) => {
  const Tag = sequelize.define('Tag', {
    title: DataTypes.STRING,
    bibleBookId: DataTypes.INTEGER,
    tagGroupList: DataTypes.VIRTUAL,
    globalAssignmentCount: DataTypes.VIRTUAL,
    bookAssignmentCount: DataTypes.VIRTUAL,
    lastUsed: DataTypes.VIRTUAL
  }, {});

  Tag.associate = function(models) {
    Tag.belongsToMany(models.VerseReference, {through: 'VerseTags'});
    Tag.belongsToMany(models.TagGroup, {through: 'TagGroupMembers'});
  };

  Tag.create_new_tag = async function(new_tag_title) {
    try {
      var newTag = await global.models.Tag.create({
        title: new_tag_title,
        bibleBookId: null
      });

      await global.models.MetaRecord.updateLastModified();

      return {
        success: true,
        dbObject: newTag.dataValues,
      };

    } catch (error) {
      console.error('An error occurred while trying to save the new tag: ' + error);

      return global.getDatabaseException(error);
    }
  };

  Tag.destroy_tag = async function(id) {
    try {
      await global.models.VerseTag.destroy({ where: { tagId: id } });
      await global.models.TagGroupMember.destroy({ where: { tagId: id }});
      await global.models.Tag.destroy({ where: { id: id } });
      await global.models.MetaRecord.updateLastModified();

      return {
        success: true
      };

    } catch (error) {
      console.error('An error occurred while trying to delete the tag with id ' + id + ': ' + error);

      return global.getDatabaseException(error);
    }
  };

  Tag.update_tag = async function(id, newTitle, addTagGroups, removeTagGroups) {
    try {
      let tag = await global.models.Tag.findByPk(id);
      let titleChanged = tag.title != newTitle;

      if (titleChanged) {
        await global.models.Tag.update({ title: newTitle }, { where: { id: id }});
      }

      addTagGroups.forEach(async (tagGroupId) => {
        let tagGroup = await global.models.TagGroup.findByPk(tagGroupId);
        await tagGroup.addTag(id);
      });

      removeTagGroups.forEach(async (tagGroupId) => {
        let tagGroup = await global.models.TagGroup.findByPk(tagGroupId);
        await tagGroup.removeTag(id);
      });

      await global.models.MetaRecord.updateLastModified();

      return {
        success: true
      };

    } catch (error) {
      console.error("An error occurred while trying to update the tag with id " + id + ": " + error);

      return global.getDatabaseException(error);
    }
  };

  Tag.update_tags_on_verses = async function(tagId, verseObjects, versification, action) {
    try {
      var tag = await global.models.Tag.findByPk(tagId);

      for (var verseObject of verseObjects) {
        var verseReference = await global.models.VerseReference.findOrCreateFromVerseObject(verseObject, versification);
        
        if (action == "add") {
          await verseReference.addTag(tag.id);
        } else if (action == "remove") {
          await verseReference.removeTag(tag.id);
        }
      }

      await global.models.MetaRecord.updateLastModified();
      return {
        success: true
      };
      
    } catch (error) {
      console.error("An error occurred while trying to update tags on selected verses: " + error);

      return global.getDatabaseException(error);
    }
  };

  Tag.getAllTags = function(bibleBookId = 0, lastUsed=false, onlyStats=false) {
    var query = "SELECT t.*," +
                 " SUM(CASE WHEN vt.tagId IS NULL THEN 0 ELSE 1 END) AS globalAssignmentCount," +
                 " SUM(CASE WHEN vr.bibleBookId=" + bibleBookId + " THEN 1 ELSE 0 END) AS bookAssignmentCount";

    if (!onlyStats) {
      query += ", GROUP_CONCAT(DISTINCT tg.id) AS tagGroupList";
    }

    query += ", strftime('%s', MAX(vt.updatedAt)) AS lastUsed";

    query += " FROM Tags t" +
             " LEFT JOIN VerseTags vt ON vt.tagId = t.id" +
             " LEFT JOIN VerseReferences vr ON vt.verseReferenceId = vr.id";

    if (!onlyStats) {
      query += " LEFT JOIN TagGroupMembers tgm ON tgm.tagId = t.id" +
               " LEFT JOIN TagGroups tg ON tg.id = tgm.tagGroupId";
    }

    query += " GROUP BY t.id";
    
    if (lastUsed) {
      query += " ORDER BY lastUsed DESC limit 5";
    } else if (!onlyStats) {
      query += " ORDER BY t.title ASC";
    }

    return sequelize.query(query, { model: global.models.Tag });
  };

  Tag.getTagCount = async function(bibleBookId=0) {
    var bibleBookQuery = '';

    if (bibleBookId != 0) {
      bibleBookQuery = `
        LEFT JOIN VerseTags vt ON vt.tagId = t.id
        LEFT JOIN VerseReferences vr ON vt.verseReferenceId = vr.id
        WHERE vr.bibleBookId = ${bibleBookId}
      `;
    }

    var query = `SELECT t.id FROM Tags t ${bibleBookQuery}`;
    var records = await sequelize.query(query, { model: global.models.Tag });
    return records.length;
  };

  return Tag;
};