import type { InferAttributes, InferCreationAttributes, Sequelize } from "sequelize"; import { DataTypes, Model, QueryTypes } from "sequelize"; import { relationshipColumns } from "./asset-relationship-schema"; export class RelationshipModel extends Model< InferAttributes, InferCreationAttributes > { declare id: string; declare companyId: string; declare sourceId: string; declare targetId: string; declare typeId: string; declare name: string; declare inverseName: string; declare symmetric: boolean; declare notes: string; declare createdBy: string; declare createdAt: string; declare endedBy: string | null; declare endedAt: string | null; declare activeKey: string | null; } export async function migrateRelationships(database: Sequelize) { const query = database.getQueryInterface(); const tables = await database.query<{ name: string }>( "SELECT TABLE_NAME AS name FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE()", { type: QueryTypes.SELECT } ); const fk = (model: string, nullable = false) => ({ type: DataTypes.UUID, allowNull: nullable, references: { model, key: "id" }, onDelete: "RESTRICT", }); const columns = { id: { type: DataTypes.UUID, primaryKey: true }, companyId: fk("companies"), sourceId: fk("assets"), targetId: fk("assets"), typeId: fk("asset_definitions"), ...relationshipColumns, createdBy: fk("users"), createdAt: { type: DataTypes.STRING(30), allowNull: false }, endedBy: fk("users", true), endedAt: { type: DataTypes.STRING(30), allowNull: true }, activeKey: { type: DataTypes.STRING(150), allowNull: true, unique: true }, }; if (!tables.some((table) => table.name === "asset_relationships")) { await query.createTable("asset_relationships", columns); } const indexes = await database.query<{ name: string }>( "SELECT DISTINCT INDEX_NAME AS name FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='assets'", { type: QueryTypes.SELECT } ); if (!indexes.some((index) => index.name === "assets_company_identity")) { await query.addIndex("assets", ["companyId", "id"], { unique: true, name: "assets_company_identity", }); } const constraints = await database.query<{ name: string }>( "SELECT CONSTRAINT_NAME AS name FROM information_schema.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='asset_relationships'", { type: QueryTypes.SELECT } ); // Las FK compuestas impiden vínculos cruzados también ante escrituras externas al dominio. for (const [column, name] of [ ["sourceId", "relationship_source_company"], ["targetId", "relationship_target_company"], ] as const) { if (!constraints.some((constraint) => constraint.name === name)) { await database.query( `ALTER TABLE asset_relationships ADD CONSTRAINT \`${name}\` FOREIGN KEY (companyId, \`${column}\`) REFERENCES assets(companyId,id)` ); } } RelationshipModel.init(columns, { sequelize: database, tableName: "asset_relationships", timestamps: false, }); }