import { MigrationInterface, QueryRunner } from 'typeorm'; const newPermissions = [ 'surveys.read', 'surveys.manage', 'surveys.assign', 'surveys.execute', ] as const; const rolePermissionValues = ` ('admin', 'surveys.read'), ('admin', 'surveys.manage'), ('admin', 'surveys.assign'), ('admin', 'surveys.execute'), ('director', 'surveys.read'), ('supervisor', 'surveys.read'), ('supervisor', 'surveys.manage'), ('supervisor', 'surveys.assign'), ('supervisor', 'surveys.execute'), ('inspector', 'surveys.read'), ('inspector', 'surveys.execute'), ('auditor', 'surveys.read') `; function quoteIdentifier(identifier: string): string { return `"${identifier.replaceAll('"', '""')}"`; } export class PhaseC1SurveyPlanning1786809600000 implements MigrationInterface { name = 'PhaseC1SurveyPlanning1786809600000'; public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(` CREATE TABLE survey_campaigns ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), code varchar(80) NOT NULL, name varchar(200) NOT NULL, description text, status varchar(24) NOT NULL DEFAULT 'DRAFT', planned_start_at timestamptz, planned_end_at timestamptz, scope_asset_id uuid, coordinator_user_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 uq_survey_campaigns_code UNIQUE (code), CONSTRAINT chk_survey_campaigns_status CHECK (status IN ( 'DRAFT', 'PLANNED', 'IN_PROGRESS', 'COMPLETED', 'CANCELLED' )), CONSTRAINT chk_survey_campaigns_dates CHECK ( planned_start_at IS NULL OR planned_end_at IS NULL OR planned_end_at >= planned_start_at ), CONSTRAINT fk_survey_campaigns_scope_asset FOREIGN KEY (scope_asset_id) REFERENCES assets(id) ON DELETE RESTRICT, CONSTRAINT fk_survey_campaigns_coordinator FOREIGN KEY (coordinator_user_id) REFERENCES users(id) ON DELETE SET NULL, CONSTRAINT fk_survey_campaigns_created_by FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL, CONSTRAINT fk_survey_campaigns_updated_by FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL ) `); await queryRunner.query(`CREATE INDEX idx_survey_campaigns_status ON survey_campaigns (status)`); await queryRunner.query(`CREATE INDEX idx_survey_campaigns_scope_asset_id ON survey_campaigns (scope_asset_id)`); await queryRunner.query(`CREATE INDEX idx_survey_campaigns_coordinator_user_id ON survey_campaigns (coordinator_user_id)`); await queryRunner.query(`CREATE INDEX idx_survey_campaigns_planned_dates ON survey_campaigns (planned_start_at, planned_end_at)`); await queryRunner.query(` CREATE TABLE survey_campaign_targets ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), campaign_id uuid NOT NULL, asset_id uuid NOT NULL, assigned_user_id uuid, status varchar(24) NOT NULL DEFAULT 'PENDING', due_at timestamptz, instructions text, created_by uuid, updated_by uuid, created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT uq_survey_campaign_targets_campaign_asset UNIQUE (campaign_id, asset_id), CONSTRAINT chk_survey_campaign_targets_status CHECK (status IN ( 'PENDING', 'IN_PROGRESS', 'COMPLETED', 'SKIPPED' )), CONSTRAINT fk_survey_campaign_targets_campaign FOREIGN KEY (campaign_id) REFERENCES survey_campaigns(id) ON DELETE RESTRICT, CONSTRAINT fk_survey_campaign_targets_asset FOREIGN KEY (asset_id) REFERENCES assets(id) ON DELETE RESTRICT, CONSTRAINT fk_survey_campaign_targets_assignee FOREIGN KEY (assigned_user_id) REFERENCES users(id) ON DELETE SET NULL, CONSTRAINT fk_survey_campaign_targets_created_by FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL, CONSTRAINT fk_survey_campaign_targets_updated_by FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL ) `); await queryRunner.query(`CREATE INDEX idx_survey_campaign_targets_campaign_status ON survey_campaign_targets (campaign_id, status)`); await queryRunner.query(`CREATE INDEX idx_survey_campaign_targets_asset_id ON survey_campaign_targets (asset_id)`); await queryRunner.query(`CREATE INDEX idx_survey_campaign_targets_assigned_user_id ON survey_campaign_targets (assigned_user_id)`); await queryRunner.query(` INSERT INTO permissions (code, description) VALUES ('surveys.read', 'Consultar planificación de relevamientos'), ('surveys.manage', 'Crear y actualizar campañas de relevamiento'), ('surveys.assign', 'Asignar objetivos de relevamiento'), ('surveys.execute', 'Actualizar el avance de objetivos asignados') ON CONFLICT (code) DO UPDATE SET description = EXCLUDED.description `); 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, UPDATE ON TABLE survey_campaigns, survey_campaign_targets TO ${applicationRole} `); await queryRunner.query(` REVOKE DELETE ON TABLE survey_campaigns, survey_campaign_targets 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 = ANY($1::varchar[]) AND NOT EXISTS ( SELECT 1 FROM role_permissions WHERE permission_id = permissions.id )`, [newPermissions], ); await queryRunner.query(`DROP TABLE survey_campaign_targets`); await queryRunner.query(`DROP TABLE survey_campaigns`); } }