metis-desk/modules/assets/src/api/infrastructure/persistence/asset-relationship.model.ts
2026-10-10 17:57:05 +02:00

90 lines
3.1 KiB
TypeScript

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<RelationshipModel>,
InferCreationAttributes<RelationshipModel>
> {
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,
});
}