import type { Sequelize } from "sequelize"; import { QueryTypes } from "sequelize"; /** Proyección estructural mínima; Resource conserva identidad, datos editables, estado e historial. */ export async function migrateDirectoryDomains(database: Sequelize) { for (const [table, fields, name] of [ ["resources", ["id", "companyId", "templateId"], "directory_resource_scope_template"], ["resource_definitions", ["id", "stockKey"], "directory_template_key"], ] as const) { const indexes = await database.query<{ name: string }>( "SELECT INDEX_NAME AS name FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=:table AND INDEX_NAME=:name", { replacements: { table, name }, type: QueryTypes.SELECT } ); if (!indexes.length) { await database.getQueryInterface().addIndex(table, [...fields], { name, unique: true }); } } await database.query(`CREATE TABLE IF NOT EXISTS resource_directory_domains ( resourceId CHAR(36) BINARY NOT NULL PRIMARY KEY, companyId CHAR(36) BINARY NOT NULL, templateId CHAR(36) BINARY NOT NULL, templateKey VARCHAR(100) NOT NULL DEFAULT 'template:windows_domain', normalizedDns VARCHAR(253) CHARACTER SET ascii COLLATE ascii_bin NOT NULL, environmentId CHAR(36) BINARY NOT NULL, associatedBy CHAR(36) BINARY NOT NULL, associatedAt VARCHAR(30) NOT NULL, UNIQUE KEY directory_dns_company (companyId,normalizedDns), UNIQUE KEY directory_environment_unique (environmentId), CONSTRAINT directory_template_semantics CHECK (templateKey='template:windows_domain'), CONSTRAINT directory_dns_not_empty CHECK (CHAR_LENGTH(normalizedDns)>0), CONSTRAINT directory_resource FOREIGN KEY (resourceId,companyId,templateId) REFERENCES resources(id,companyId,templateId), CONSTRAINT directory_template FOREIGN KEY (templateId,templateKey) REFERENCES resource_definitions(id,stockKey), CONSTRAINT directory_context FOREIGN KEY (environmentId,companyId) REFERENCES access_directory_environments(environmentId,companyId), CONSTRAINT directory_association_actor FOREIGN KEY (associatedBy) REFERENCES users(id) ) ENGINE=InnoDB`); const scopeIndex = await database.query<{ name: string }>( "SELECT INDEX_NAME AS name FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='resource_directory_domains' AND INDEX_NAME='directory_resource_company'", { type: QueryTypes.SELECT } ); if (!scopeIndex.length) { await database .getQueryInterface() .addIndex("resource_directory_domains", ["resourceId", "companyId"], { name: "directory_resource_company", unique: true, }); } const canonical = await database.query<{ name: string }>( "SELECT CONSTRAINT_NAME AS name FROM information_schema.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='resource_directory_domains' AND CONSTRAINT_NAME='directory_dns_canonical'", { type: QueryTypes.SELECT } ); if (!canonical.length) { await database.query(`ALTER TABLE resource_directory_domains ADD CONSTRAINT directory_dns_canonical CHECK (normalizedDns=LOWER(normalizedDns) AND LENGTH(normalizedDns)=LENGTH(TRIM(normalizedDns)) AND RIGHT(normalizedDns,1)<>'.')`); } }