import { MigrationInterface, QueryRunner } from 'typeorm'; const permissionCode = 'inspection_verifications.plan'; const rolePermissionValues = ` ('admin', 'inspection_verifications.plan'), ('director', 'inspection_verifications.plan'), ('supervisor', 'inspection_verifications.plan') `; function quoteIdentifier(identifier: string): string { return `"${identifier.replaceAll('"', '""')}"`; } export class PhaseD5316VerificationPlanning1788112800000 implements MigrationInterface { name = 'PhaseD5316VerificationPlanning1788112800000'; public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(` CREATE TABLE inspection_finding_verification_visits ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), finding_id uuid NOT NULL, visit_id uuid NOT NULL, linked_by uuid NULL, created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT uq_inspection_finding_verification_visit UNIQUE (finding_id, visit_id), CONSTRAINT fk_inspection_finding_verification_finding FOREIGN KEY (finding_id) REFERENCES inspection_findings(id) ON DELETE RESTRICT, CONSTRAINT fk_inspection_finding_verification_visit FOREIGN KEY (visit_id) REFERENCES inspection_visits(id) ON DELETE RESTRICT, CONSTRAINT fk_inspection_finding_verification_linked_by FOREIGN KEY (linked_by) REFERENCES users(id) ON DELETE SET NULL ) `); await queryRunner.query('CREATE INDEX idx_inspection_finding_verification_finding ON inspection_finding_verification_visits (finding_id, created_at DESC)'); await queryRunner.query('CREATE INDEX idx_inspection_finding_verification_visit ON inspection_finding_verification_visits (visit_id)'); await queryRunner.query(` INSERT INTO permissions (code, description) VALUES ($1, 'Planificar visitas de verificación a partir de hallazgos con fecha de control') ON CONFLICT (code) DO UPDATE SET description = EXCLUDED.description `, [permissionCode]); await queryRunner.query(` WITH mapping(role_code, permission_code) AS (VALUES ${rolePermissionValues}) INSERT INTO role_permissions (role_id, permission_id) SELECT role.id, permission.id FROM mapping INNER JOIN roles role ON role.code = mapping.role_code INNER JOIN permissions permission ON permission.code = mapping.permission_code ON CONFLICT (role_id, permission_id) DO NOTHING `); const appRole = process.env.DB_APP_USER; if (!appRole) throw new Error('Missing required environment variable: DB_APP_USER'); const applicationRole = quoteIdentifier(appRole); await queryRunner.query(`GRANT SELECT, INSERT ON TABLE inspection_finding_verification_visits TO ${applicationRole}`); await queryRunner.query(`REVOKE UPDATE, DELETE ON TABLE inspection_finding_verification_visits FROM ${applicationRole}`); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(` WITH mapping(role_code, permission_code) AS (VALUES ${rolePermissionValues}) DELETE FROM role_permissions role_permission USING roles role, permissions permission, mapping WHERE role_permission.role_id = role.id AND role_permission.permission_id = permission.id AND role.code = mapping.role_code AND permission.code = mapping.permission_code `); await queryRunner.query(` DELETE FROM permissions WHERE code = $1 AND NOT EXISTS ( SELECT 1 FROM role_permissions WHERE permission_id = permissions.id ) `, [permissionCode]); await queryRunner.query('DROP TABLE inspection_finding_verification_visits'); } }