metis-desk/modules/companies/src/api/infrastructure/persistence/intervention-template-migration.ts

90 lines
3.6 KiB
TypeScript
Raw Normal View History

2026-10-07 11:03:01 +00:00
import { DataTypes, QueryTypes, type Sequelize } from "sequelize";
import { interventionTemplateSeeds } from "../../domain/intervention-template-seed";
import { InterventionTemplatePersistenceMapper } from "./intervention-template-persistence.mapper";
export async function migrateInterventionTemplates(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) => ({
type: DataTypes.UUID,
allowNull: true,
references: { model, key: "id" },
onDelete: "RESTRICT",
});
const definitions = [
["intervention_template_lock", { id: { type: DataTypes.INTEGER, primaryKey: true } }],
[
"intervention_templates",
{
id: { type: DataTypes.UUID, primaryKey: true },
companyId: fk("companies"),
requestKey: { type: DataTypes.UUID, allowNull: false, unique: true },
values: { type: DataTypes.JSON, allowNull: false },
},
],
[
"intervention_template_revisions",
{
id: { type: DataTypes.UUID, primaryKey: true },
templateId: {
type: DataTypes.UUID,
allowNull: false,
references: { model: "intervention_templates", key: "id" },
onDelete: "RESTRICT",
},
actorId: fk("users"),
values: { type: DataTypes.JSON, allowNull: false },
recordedAt: { type: DataTypes.STRING(30), allowNull: false },
position: { type: DataTypes.INTEGER, allowNull: false },
},
],
] as const;
for (const [table, columns] of definitions) {
if (!tables.some((item) => item.name === table)) {
await query.createTable(table, columns);
}
}
await database.query("INSERT IGNORE INTO intervention_template_lock (id) VALUES (1)");
const indexes = await database.query<{ name: string }>(
"SELECT DISTINCT INDEX_NAME AS name FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='intervention_template_revisions'",
{ type: QueryTypes.SELECT }
);
if (!indexes.some((item) => item.name === "intervention_template_revision_position")) {
await query.addIndex("intervention_template_revisions", ["templateId", "position"], {
name: "intervention_template_revision_position",
unique: true,
});
}
await database.transaction(async (transaction) => {
await database.query("SELECT id FROM intervention_template_lock WHERE id=1 FOR UPDATE", {
type: QueryTypes.SELECT,
transaction,
});
const existing = await database.query<{ requestKey: string }>(
"SELECT requestKey FROM intervention_templates",
{ type: QueryTypes.SELECT, transaction }
);
const mapper = new InterventionTemplatePersistenceMapper();
for (const seed of interventionTemplateSeeds()) {
if (existing.some((item) => item.requestKey === seed.requestKey.toPrimitive())) {
continue;
}
const persisted = mapper.toPersistence(seed);
await database.query(
"INSERT INTO intervention_templates (id,companyId,requestKey,`values`) VALUES (:id,:companyId,:requestKey,:values)",
{ replacements: persisted.root, transaction }
);
for (const revision of persisted.revisions) {
await database.query(
"INSERT INTO intervention_template_revisions (id,templateId,`values`,actorId,recordedAt,position) VALUES (:id,:templateId,:values,:actorId,:recordedAt,:position)",
{ replacements: revision, transaction }
);
}
}
});
}