b6b345f245
- Refactor email verification and password reset utilities to use new email service. - Introduce email delivery queue and notification delivery model for better tracking. - Enhance file validation and storage services for improved security and ownership management. - Add cron job for cleaning inactive notifications with retention policy. - Update document worker to handle document generation and storage more efficiently. - Implement logging improvements in activity and log workers. - Create comprehensive documentation for new API endpoints and services. - Add unit tests for file validation and notification policies to ensure robustness.
44 lines
5.0 KiB
JavaScript
44 lines
5.0 KiB
JavaScript
"use strict";
|
|
|
|
const add = (q, table, column, definition, transaction) => q.addColumn(table, column, definition, { transaction });
|
|
module.exports = {
|
|
async up(q, Sequelize) {
|
|
await q.sequelize.transaction(async (transaction) => {
|
|
await add(q, "uploads", "storage_provider", { type: Sequelize.STRING, allowNull: false, defaultValue: "S3" }, transaction);
|
|
await add(q, "uploads", "safe_name", { type: Sequelize.STRING, allowNull: true }, transaction);
|
|
await add(q, "uploads", "checksum", { type: Sequelize.STRING(64), allowNull: true }, transaction);
|
|
await add(q, "uploads", "owner_type", { type: Sequelize.STRING, allowNull: false, defaultValue: "USER" }, transaction);
|
|
await add(q, "uploads", "owner_id", { type: Sequelize.STRING, allowNull: true }, transaction);
|
|
await add(q, "uploads", "visibility", { type: Sequelize.ENUM("PRIVATE", "PUBLIC"), allowNull: false, defaultValue: "PRIVATE" }, transaction);
|
|
await add(q, "uploads", "status", { type: Sequelize.ENUM("PENDING", "AVAILABLE", "FAILED", "DELETED"), allowNull: false, defaultValue: "AVAILABLE" }, transaction);
|
|
await add(q, "uploads", "deletedAt", { type: Sequelize.DATE, allowNull: true }, transaction);
|
|
await q.sequelize.query("UPDATE uploads SET owner_id = uploaded_by WHERE owner_id IS NULL", { transaction });
|
|
await q.changeColumn("uploads", "owner_id", { type: Sequelize.STRING, allowNull: false }, { transaction });
|
|
await q.addIndex("uploads", ["owner_id", "status"], { name: "uploads_owner_status_idx", transaction });
|
|
|
|
await q.addIndex("user_notification", ["user_id", "notification_id"], { unique: true, name: "user_notification_user_notification_unique", transaction });
|
|
await q.addIndex("user_notification", ["user_id", "isRead", "createdAt"], { name: "user_notification_inbox_idx", transaction });
|
|
await q.addIndex("notification", ["isActive", "createdAt"], { name: "notification_retention_idx", transaction });
|
|
await q.createTable("notification_deliveries", {
|
|
id: { type: Sequelize.STRING, primaryKey: true }, notificationId: { type: Sequelize.STRING, allowNull: true }, userId: { type: Sequelize.STRING, allowNull: true },
|
|
channel: { type: Sequelize.ENUM("EMAIL", "PUSH"), allowNull: false }, recipient: { type: Sequelize.STRING, allowNull: false }, templateKey: { type: Sequelize.STRING, allowNull: false },
|
|
status: { type: Sequelize.ENUM("QUEUED", "PROCESSING", "SENT", "FAILED"), allowNull: false, defaultValue: "QUEUED" }, attemptCount: { type: Sequelize.INTEGER, allowNull: false, defaultValue: 0 },
|
|
lastAttemptAt: Sequelize.DATE, sentAt: Sequelize.DATE, failedAt: Sequelize.DATE, failureCode: Sequelize.STRING, createdAt: { type: Sequelize.DATE, allowNull: false }, updatedAt: { type: Sequelize.DATE, allowNull: false },
|
|
}, { transaction });
|
|
await q.addIndex("notification_deliveries", ["status", "createdAt"], { name: "notification_delivery_status_idx", transaction });
|
|
|
|
for (const [name, type, nullable] of [["event_id", Sequelize.STRING, true],["target_type",Sequelize.STRING,true],["target_id",Sequelize.STRING,true],["ip_address",Sequelize.STRING,true],["user_agent",Sequelize.STRING,true],["request_id",Sequelize.STRING,true],["metadata",Sequelize.JSON,true],["occurred_at",Sequelize.DATE,true]]) await add(q, "UserActivity", name, { type, allowNull: nullable }, transaction);
|
|
await q.addIndex("UserActivity", ["event_id"], { unique: true, name: "activity_event_unique", transaction });
|
|
await q.addIndex("UserActivity", ["user_id", "occurred_at"], { name: "activity_actor_time_idx", transaction });
|
|
await q.changeColumn("UserActivity", "user_id", { type: Sequelize.STRING, allowNull: true }, { transaction });
|
|
|
|
await add(q, "Document", "created_by", { type: Sequelize.STRING, allowNull: true }, transaction); await add(q, "Document", "owner_type", { type: Sequelize.STRING, allowNull: false, defaultValue: "USER" }, transaction); await add(q, "Document", "owner_id", { type: Sequelize.STRING, allowNull: true }, transaction);
|
|
await add(q, "Document", "storage_upload_id", { type: Sequelize.INTEGER, allowNull: true }, transaction); await add(q, "Document", "job_id", { type: Sequelize.STRING, allowNull: true }, transaction);
|
|
await add(q, "Document", "generated_at", { type: Sequelize.DATE, allowNull: true }, transaction); await add(q, "Document", "failed_at", { type: Sequelize.DATE, allowNull: true }, transaction); await add(q, "Document", "failure_code", { type: Sequelize.STRING, allowNull: true }, transaction); await add(q, "Document", "version", { type: Sequelize.INTEGER, allowNull: false, defaultValue: 1 }, transaction);
|
|
await q.addIndex("Document", ["job_id"], { unique: true, name: "document_job_unique", transaction }); await q.addIndex("Document", ["owner_id", "status"], { name: "document_owner_status_idx", transaction });
|
|
await q.addIndex("DocumentType", ["doc_type_name"], { unique: true, name: "document_type_name_unique", transaction });
|
|
});
|
|
},
|
|
async down() { throw new Error("Phase 2 migration is forward-only; restore from backup for rollback"); },
|
|
};
|