UNPKG

@replyke/express

Version:

Replyke: Build interactive apps with social features like comments, votes, feeds, user lists, notifications, and more.

278 lines (277 loc) 10.6 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const sequelize_1 = require("sequelize"); const short_uuid_1 = __importDefault(require("short-uuid")); const Comment_1 = __importDefault(require("./Comment")); const User_1 = __importDefault(require("./User")); const Report_1 = __importDefault(require("./Report")); class Entity extends sequelize_1.Model { static initModel(sequelize) { Entity.init({ id: { type: sequelize_1.DataTypes.UUID, defaultValue: sequelize_1.DataTypes.UUIDV4, primaryKey: true, allowNull: false, }, projectId: { type: sequelize_1.DataTypes.UUID, allowNull: false, }, userId: { type: sequelize_1.DataTypes.UUID, allowNull: true, }, shortId: { type: sequelize_1.DataTypes.STRING, allowNull: false, }, referenceId: { type: sequelize_1.DataTypes.STRING, allowNull: true, }, foreignId: { type: sequelize_1.DataTypes.STRING, allowNull: true, }, sourceId: { type: sequelize_1.DataTypes.STRING, allowNull: true, }, resourceId: { type: sequelize_1.DataTypes.STRING, allowNull: true, }, title: { type: sequelize_1.DataTypes.TEXT, allowNull: true, validate: { len: [0, 300] }, set(value) { const newValue = value && value.trim() !== "" ? value : null; this.setDataValue("title", newValue); }, }, content: { type: sequelize_1.DataTypes.TEXT, allowNull: true, validate: { len: [0, 100000] }, set(value) { const newValue = value && value.trim() !== "" ? value : null; this.setDataValue("content", newValue); }, }, media: { type: sequelize_1.DataTypes.ARRAY(sequelize_1.DataTypes.JSONB), allowNull: false, defaultValue: [], set(value) { const sanitized = Array.isArray(value) ? value.filter((item) => item && typeof item === "object" && !Array.isArray(item)) : []; this.setDataValue("media", sanitized); }, validate: { isArrayOfObjects(value) { if (!Array.isArray(value)) { throw new Error("Value must be an array."); } for (const item of value) { if (!item || typeof item !== "object" || Array.isArray(item)) { throw new Error("Each item must be a non-null object."); } } }, }, }, attachments: { type: sequelize_1.DataTypes.ARRAY(sequelize_1.DataTypes.JSONB), allowNull: false, defaultValue: [], set(value) { const sanitized = Array.isArray(value) ? value.filter((item) => item && typeof item === "object" && !Array.isArray(item)) : []; this.setDataValue("attachments", sanitized); }, validate: { isArrayOfObjects(value) { if (!Array.isArray(value)) { throw new Error("Value must be an array."); } for (const item of value) { if (!item || typeof item !== "object" || Array.isArray(item)) { throw new Error("Each item must be a non-null object."); } } }, }, }, mentions: { type: sequelize_1.DataTypes.ARRAY(sequelize_1.DataTypes.JSONB), allowNull: false, defaultValue: [], }, upvotes: { type: sequelize_1.DataTypes.ARRAY(sequelize_1.DataTypes.UUID), allowNull: false, defaultValue: [], }, downvotes: { type: sequelize_1.DataTypes.ARRAY(sequelize_1.DataTypes.UUID), allowNull: false, defaultValue: [], }, sharesCount: { type: sequelize_1.DataTypes.INTEGER, allowNull: false, defaultValue: 0, }, views: { type: sequelize_1.DataTypes.INTEGER, allowNull: false, defaultValue: 1, }, location: { type: sequelize_1.DataTypes.GEOGRAPHY("POINT"), allowNull: true, }, keywords: { type: sequelize_1.DataTypes.ARRAY(sequelize_1.DataTypes.TEXT), allowNull: false, defaultValue: [], set(value) { const filtered = value.filter((k) => k.trim() !== ""); this.setDataValue("keywords", filtered); }, }, score: { type: sequelize_1.DataTypes.FLOAT, allowNull: false, defaultValue: 2, }, scoreUpdatedAt: { type: sequelize_1.DataTypes.DATE, allowNull: false, defaultValue: sequelize_1.DataTypes.NOW, }, metadata: { type: sequelize_1.DataTypes.JSONB, allowNull: false, defaultValue: {}, validate: { notTooLarge(value) { const MAX_SIZE = 1024 * 1024; // 1 MB if (value !== null) { const size = Buffer.byteLength(JSON.stringify(value), "utf8"); if (size > MAX_SIZE) { throw new Error(`Metadata exceeds the size limit of ${MAX_SIZE} bytes.`); } } }, }, }, createdAt: { type: sequelize_1.DataTypes.DATE, allowNull: false, defaultValue: sequelize_1.DataTypes.NOW, }, updatedAt: { type: sequelize_1.DataTypes.DATE, allowNull: false, defaultValue: sequelize_1.DataTypes.NOW, }, deletedAt: { type: sequelize_1.DataTypes.DATE, allowNull: true, }, }, { sequelize, modelName: "Entity", tableName: "Entities", timestamps: false, paranoid: true, indexes: [ { unique: true, fields: ["projectId", "referenceId"] }, { name: "idx_entities_by_foreignId", fields: ["projectId", "foreignId"], where: { deletedAt: null, }, }, { unique: true, fields: ["projectId", "shortId"] }, { fields: ["location"], using: "gist" }, { fields: ["score"] }, { fields: ["title"], using: "gin", operator: "gin_trgm_ops", }, { fields: ["content"], using: "gin", operator: "gin_trgm_ops", }, { fields: ["keywords"], using: "gin", }, { name: "idx_entities_feed", fields: [ { name: "projectId" }, { name: "sourceId" }, { name: "createdAt", order: "DESC" }, ], where: { deletedAt: null, }, }, { name: "idx_entities_by_shortid", fields: [{ name: "projectId" }, { name: "shortId" }], where: { deletedAt: null, }, }, ], }); Entity.beforeValidate((instance) => { // only generate if it's not already set (e.g. on updates) if (!instance.shortId) { // id will have its defaultValue (UUIDV4) applied already instance.shortId = (0, short_uuid_1.default)().fromUUID(instance.id); } }); // On update: only overwrite updatedAt *if* the user didn’t explicitly set it. Entity.beforeUpdate((inst) => { if (!inst.changed("updatedAt")) { inst.setDataValue("updatedAt", new Date()); } }); } /** * Define associations to other models */ static associate() { Entity.hasMany(Comment_1.default, { foreignKey: "entityId", onDelete: "CASCADE", // If an Entity is deleted, all its Comments are deleted }); // We don't cascade delete comments when users are remove to keep conversation integrity. We will just not show the user if there is no one Entity.belongsTo(User_1.default, { foreignKey: "userId", as: "user", // Define the alias here }); Entity.hasMany(Report_1.default, { foreignKey: "targetId", constraints: false, // Disable FK constraints for polymorphic relation onDelete: "CASCADE", scope: { targetType: "Entity" }, // Ensures this association only applies to Reports for Entities }); } } exports.default = Entity;