import { MigrationInterface, QueryRunner } from 'typeorm'; function quoteIdentifier(identifier: string): string { return `"${identifier.replaceAll('"', '""')}"`; } export class PhaseD5372RelationalTerritoryPlan1787767200000 implements MigrationInterface { name = 'PhaseD5372RelationalTerritoryPlan1787767200000'; public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(` ALTER TABLE asset_import_plan_items DROP CONSTRAINT chk_asset_import_plan_item_kind `); await queryRunner.query(` ALTER TABLE asset_import_plan_items ADD CONSTRAINT chk_asset_import_plan_item_kind CHECK ( entity_kind IN ( 'DEPARTMENT','ORGANIZATION','AREA','AREA_DEPARTMENT_RELATION','FIELD','OPERATOR_RELATION', 'LEGAL_RIGHT','LEGAL_RIGHT_ORGANIZATION','INSTALLATION','TECHNICAL_ASSET' ) ) `); await queryRunner.query(` CREATE TABLE administrative_departments ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), province_code varchar(40) NOT NULL DEFAULT 'MENDOZA', code varchar(80) NOT NULL, name varchar(160) NOT NULL, normalized_name varchar(180) NOT NULL, is_active boolean NOT NULL DEFAULT true, source_document_id uuid, created_by uuid, updated_by uuid, created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT chk_administrative_department_code CHECK (code ~ '^[A-Z0-9][A-Z0-9._/-]{1,79}$'), CONSTRAINT chk_administrative_department_name CHECK (length(btrim(name)) >= 2), CONSTRAINT chk_administrative_department_normalized CHECK (length(btrim(normalized_name)) >= 2), CONSTRAINT fk_administrative_department_source FOREIGN KEY (source_document_id) REFERENCES source_documents(id) ON DELETE SET NULL, CONSTRAINT fk_administrative_department_created_by FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL, CONSTRAINT fk_administrative_department_updated_by FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL, CONSTRAINT uq_administrative_department_code UNIQUE (province_code, code), CONSTRAINT uq_administrative_department_name UNIQUE (province_code, normalized_name) ) `); await queryRunner.query(`CREATE INDEX idx_administrative_departments_active_name ON administrative_departments (is_active, normalized_name)`); await queryRunner.query(` CREATE TABLE area_department_relations ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), area_id uuid NOT NULL, department_id uuid NOT NULL, valid_from date NOT NULL DEFAULT CURRENT_DATE, valid_until date, source_document_id uuid, notes text, created_by uuid, ended_by uuid, created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT chk_area_department_relation_dates CHECK (valid_until IS NULL OR valid_until >= valid_from), CONSTRAINT fk_area_department_relation_area FOREIGN KEY (area_id) REFERENCES assets(id) ON DELETE RESTRICT, CONSTRAINT fk_area_department_relation_department FOREIGN KEY (department_id) REFERENCES administrative_departments(id) ON DELETE RESTRICT, CONSTRAINT fk_area_department_relation_source FOREIGN KEY (source_document_id) REFERENCES source_documents(id) ON DELETE SET NULL, CONSTRAINT fk_area_department_relation_created_by FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL, CONSTRAINT fk_area_department_relation_ended_by FOREIGN KEY (ended_by) REFERENCES users(id) ON DELETE SET NULL ) `); await queryRunner.query(`CREATE UNIQUE INDEX uq_area_department_relation_active_pair ON area_department_relations (area_id, department_id) WHERE valid_until IS NULL`); await queryRunner.query(`CREATE INDEX idx_area_department_relations_area ON area_department_relations (area_id) WHERE valid_until IS NULL`); await queryRunner.query(`CREATE INDEX idx_area_department_relations_department ON area_department_relations (department_id) WHERE valid_until IS NULL`); const appRole = process.env.DB_APP_USER; if (!appRole) throw new Error('Missing required environment variable: DB_APP_USER'); const roleRows = (await queryRunner.query('SELECT 1 FROM pg_roles WHERE rolname=$1', [appRole])) as unknown[]; if (roleRows.length !== 1) throw new Error('Configured DB_APP_USER does not exist'); const applicationRole = quoteIdentifier(appRole); await queryRunner.query(`GRANT SELECT, INSERT, UPDATE ON TABLE administrative_departments, area_department_relations TO ${applicationRole}`); await queryRunner.query(`REVOKE DELETE ON TABLE administrative_departments, area_department_relations FROM ${applicationRole}`); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`DROP TABLE area_department_relations`); await queryRunner.query(`DROP TABLE administrative_departments`); await queryRunner.query(`ALTER TABLE asset_import_plan_items DROP CONSTRAINT chk_asset_import_plan_item_kind`); await queryRunner.query(` ALTER TABLE asset_import_plan_items ADD CONSTRAINT chk_asset_import_plan_item_kind CHECK ( entity_kind IN ('ORGANIZATION','AREA','FIELD','OPERATOR_RELATION','INSTALLATION','TECHNICAL_ASSET') ) `); } }