39948-vm/backend/src/db/migrations/20260326000003-create-project-element-defaults.js

188 lines
4.8 KiB
JavaScript

'use strict';
/**
* Migration: Create project_element_defaults table
*
* This table stores project-specific element default settings that override
* the global element_type_defaults. Key design decisions:
*
* - element_type is TEXT (not ENUM) for flexibility
* - source_element_id is optional FK for audit trail (SET NULL on global delete)
* - snapshot_version tracks generations for "check for updates" feature
* - NO environment field - applies across all environments for consistent branding
* - Unique constraint on (projectId, element_type) ensures one override per type per project
*/
module.exports = {
async up(queryInterface, Sequelize) {
// Check if table already exists
const tableExists = await queryInterface.sequelize.query(
`SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = 'project_element_defaults'
);`,
{ type: Sequelize.QueryTypes.SELECT },
);
if (tableExists[0]?.exists) {
console.log(
'Table project_element_defaults already exists, skipping creation',
);
return;
}
await queryInterface.createTable('project_element_defaults', {
id: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4,
primaryKey: true,
},
element_type: {
type: Sequelize.TEXT,
allowNull: false,
},
name: {
type: Sequelize.TEXT,
allowNull: true,
},
sort_order: {
type: Sequelize.INTEGER,
allowNull: false,
defaultValue: 0,
},
settings_json: {
type: Sequelize.TEXT,
allowNull: true,
},
source_element_id: {
type: Sequelize.UUID,
allowNull: true,
references: {
model: 'element_type_defaults',
key: 'id',
},
onDelete: 'SET NULL',
onUpdate: 'CASCADE',
},
snapshot_version: {
type: Sequelize.INTEGER,
allowNull: false,
defaultValue: 1,
},
projectId: {
type: Sequelize.UUID,
allowNull: false,
references: {
model: 'projects',
key: 'id',
},
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
},
createdById: {
type: Sequelize.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id',
},
onDelete: 'SET NULL',
onUpdate: 'CASCADE',
},
updatedById: {
type: Sequelize.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id',
},
onDelete: 'SET NULL',
onUpdate: 'CASCADE',
},
importHash: {
type: Sequelize.STRING(255),
allowNull: true,
unique: true,
},
createdAt: {
type: Sequelize.DATE,
allowNull: false,
},
updatedAt: {
type: Sequelize.DATE,
allowNull: false,
},
deletedAt: {
type: Sequelize.DATE,
allowNull: true,
},
});
// Add indexes
await queryInterface.addIndex('project_element_defaults', ['projectId'], {
name: 'project_element_defaults_projectId',
});
await queryInterface.addIndex(
'project_element_defaults',
['projectId', 'element_type'],
{
name: 'project_element_defaults_projectId_element_type',
unique: true,
where: { deletedAt: null },
},
);
await queryInterface.addIndex(
'project_element_defaults',
['element_type'],
{
name: 'project_element_defaults_element_type',
},
);
await queryInterface.addIndex(
'project_element_defaults',
['source_element_id'],
{
name: 'project_element_defaults_source_element_id',
},
);
await queryInterface.addIndex('project_element_defaults', ['deletedAt'], {
name: 'project_element_defaults_deletedAt',
});
console.log('Successfully created project_element_defaults table');
},
async down(queryInterface, _Sequelize) {
// Drop indexes first
await queryInterface.removeIndex(
'project_element_defaults',
'project_element_defaults_projectId',
);
await queryInterface.removeIndex(
'project_element_defaults',
'project_element_defaults_projectId_element_type',
);
await queryInterface.removeIndex(
'project_element_defaults',
'project_element_defaults_element_type',
);
await queryInterface.removeIndex(
'project_element_defaults',
'project_element_defaults_source_element_id',
);
await queryInterface.removeIndex(
'project_element_defaults',
'project_element_defaults_deletedAt',
);
// Drop table
await queryInterface.dropTable('project_element_defaults');
console.log('Successfully dropped project_element_defaults table');
},
};