39948-vm/backend/src/db/migrations/20260319000001-add-foreign-key-constraints.js
2026-03-19 20:13:16 +04:00

151 lines
6.3 KiB
JavaScript

'use strict';
/**
* Migration to add foreign key constraints to all model associations.
* This enforces referential integrity at the database level.
*/
module.exports = {
async up(queryInterface) {
const transaction = await queryInterface.sequelize.transaction();
try {
// Helper to add FK constraint safely (checks if exists first)
const addForeignKey = async (tableName, columnName, references, onDelete = 'CASCADE', onUpdate = 'CASCADE') => {
const constraintName = `${tableName}_${columnName}_fkey`;
// Check if constraint already exists
const [results] = await queryInterface.sequelize.query(
`SELECT constraint_name FROM information_schema.table_constraints
WHERE table_name = '${tableName}' AND constraint_name = '${constraintName}'`,
{ transaction }
);
if (results.length === 0) {
await queryInterface.addConstraint(tableName, {
fields: [columnName],
type: 'foreign key',
name: constraintName,
references: {
table: references.table,
field: references.field,
},
onDelete,
onUpdate,
transaction,
});
console.log(`Added FK constraint: ${constraintName}`);
} else {
console.log(`FK constraint already exists: ${constraintName}`);
}
};
// asset_variants -> assets
await addForeignKey('asset_variants', 'assetId', { table: 'assets', field: 'id' }, 'CASCADE', 'CASCADE');
// page_elements -> tour_pages
await addForeignKey('page_elements', 'pageId', { table: 'tour_pages', field: 'id' }, 'CASCADE', 'CASCADE');
// page_links -> tour_pages (from_page)
await addForeignKey('page_links', 'from_pageId', { table: 'tour_pages', field: 'id' }, 'CASCADE', 'CASCADE');
// page_links -> tour_pages (to_page)
await addForeignKey('page_links', 'to_pageId', { table: 'tour_pages', field: 'id' }, 'SET NULL', 'CASCADE');
// page_links -> transitions
await addForeignKey('page_links', 'transitionId', { table: 'transitions', field: 'id' }, 'SET NULL', 'CASCADE');
// assets -> projects
await addForeignKey('assets', 'projectId', { table: 'projects', field: 'id' }, 'CASCADE', 'CASCADE');
// tour_pages -> projects
await addForeignKey('tour_pages', 'projectId', { table: 'projects', field: 'id' }, 'CASCADE', 'CASCADE');
// transitions -> projects
await addForeignKey('transitions', 'projectId', { table: 'projects', field: 'id' }, 'CASCADE', 'CASCADE');
// project_memberships -> projects
await addForeignKey('project_memberships', 'projectId', { table: 'projects', field: 'id' }, 'CASCADE', 'CASCADE');
// project_memberships -> users
await addForeignKey('project_memberships', 'userId', { table: 'users', field: 'id' }, 'CASCADE', 'CASCADE');
// presigned_url_requests -> projects
await addForeignKey('presigned_url_requests', 'projectId', { table: 'projects', field: 'id' }, 'CASCADE', 'CASCADE');
// presigned_url_requests -> users
await addForeignKey('presigned_url_requests', 'userId', { table: 'users', field: 'id' }, 'CASCADE', 'CASCADE');
// project_audio_tracks -> projects
await addForeignKey('project_audio_tracks', 'projectId', { table: 'projects', field: 'id' }, 'CASCADE', 'CASCADE');
// publish_events -> projects
await addForeignKey('publish_events', 'projectId', { table: 'projects', field: 'id' }, 'CASCADE', 'CASCADE');
// publish_events -> users (SET NULL to preserve audit trail)
await addForeignKey('publish_events', 'userId', { table: 'users', field: 'id' }, 'SET NULL', 'CASCADE');
// pwa_caches -> projects
await addForeignKey('pwa_caches', 'projectId', { table: 'projects', field: 'id' }, 'CASCADE', 'CASCADE');
// access_logs -> projects
await addForeignKey('access_logs', 'projectId', { table: 'projects', field: 'id' }, 'CASCADE', 'CASCADE');
// access_logs -> users (SET NULL to preserve audit trail)
await addForeignKey('access_logs', 'userId', { table: 'users', field: 'id' }, 'SET NULL', 'CASCADE');
// users -> roles (SET NULL so deleting role doesn't delete users)
await addForeignKey('users', 'app_roleId', { table: 'roles', field: 'id' }, 'SET NULL', 'CASCADE');
await transaction.commit();
console.log('All FK constraints added successfully');
} catch (error) {
await transaction.rollback();
throw error;
}
},
async down(queryInterface) {
const transaction = await queryInterface.sequelize.transaction();
try {
const dropForeignKey = async (tableName, columnName) => {
const constraintName = `${tableName}_${columnName}_fkey`;
try {
await queryInterface.removeConstraint(tableName, constraintName, { transaction });
console.log(`Removed FK constraint: ${constraintName}`);
} catch (error) {
console.log(`FK constraint not found (may not exist): ${constraintName}`);
}
};
// Remove all FK constraints in reverse order
await dropForeignKey('users', 'app_roleId');
await dropForeignKey('access_logs', 'userId');
await dropForeignKey('access_logs', 'projectId');
await dropForeignKey('pwa_caches', 'projectId');
await dropForeignKey('publish_events', 'userId');
await dropForeignKey('publish_events', 'projectId');
await dropForeignKey('project_audio_tracks', 'projectId');
await dropForeignKey('presigned_url_requests', 'userId');
await dropForeignKey('presigned_url_requests', 'projectId');
await dropForeignKey('project_memberships', 'userId');
await dropForeignKey('project_memberships', 'projectId');
await dropForeignKey('transitions', 'projectId');
await dropForeignKey('tour_pages', 'projectId');
await dropForeignKey('assets', 'projectId');
await dropForeignKey('page_links', 'transitionId');
await dropForeignKey('page_links', 'to_pageId');
await dropForeignKey('page_links', 'from_pageId');
await dropForeignKey('page_elements', 'pageId');
await dropForeignKey('asset_variants', 'assetId');
await transaction.commit();
console.log('All FK constraints removed successfully');
} catch (error) {
await transaction.rollback();
throw error;
}
}
};