import { MigrationInterface, QueryRunner } from 'typeorm'; const newPermissions = [ 'assets.read_provenance', 'assets.manage_provenance', 'assets.verify_provenance', 'assets.read_temporal', ] as const; const rolePermissionValues = ` ('admin', 'assets.read_provenance'), ('admin', 'assets.manage_provenance'), ('admin', 'assets.verify_provenance'), ('admin', 'assets.read_temporal'), ('director', 'assets.read_provenance'), ('director', 'assets.verify_provenance'), ('director', 'assets.read_temporal'), ('supervisor', 'assets.read_provenance'), ('supervisor', 'assets.manage_provenance'), ('supervisor', 'assets.verify_provenance'), ('supervisor', 'assets.read_temporal'), ('inspector', 'assets.read_provenance'), ('inspector', 'assets.manage_provenance'), ('inspector', 'assets.read_temporal'), ('auditor', 'assets.read_provenance'), ('auditor', 'assets.read_temporal') `; export class PhaseB5ProvenanceTemporal1786723203000 implements MigrationInterface { name = 'PhaseB5ProvenanceTemporal1786723203000'; public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(` ALTER TABLE assets ADD COLUMN data_origin varchar(32) NOT NULL DEFAULT 'MANUAL', ADD COLUMN source_name varchar(160), ADD COLUMN source_reference varchar(255), ADD COLUMN source_observed_at timestamptz, ADD COLUMN source_notes text, ADD COLUMN provenance_verified_at timestamptz, ADD COLUMN provenance_verified_by uuid, ADD COLUMN provenance_updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, ADD COLUMN provenance_updated_by uuid, ADD CONSTRAINT chk_assets_data_origin CHECK (data_origin IN ( 'MANUAL', 'FIELD_SURVEY', 'PROVIDED_DOCUMENT', 'IMPORT', 'SYSTEM' )), ADD CONSTRAINT fk_assets_provenance_verified_by FOREIGN KEY (provenance_verified_by) REFERENCES users(id) ON DELETE SET NULL, ADD CONSTRAINT fk_assets_provenance_updated_by FOREIGN KEY (provenance_updated_by) REFERENCES users(id) ON DELETE SET NULL `); await queryRunner.query(` UPDATE assets SET provenance_updated_by = updated_by `); await queryRunner.query(`CREATE INDEX idx_assets_data_origin ON assets (data_origin)`); await queryRunner.query(` CREATE INDEX idx_assets_provenance_verified_at ON assets (provenance_verified_at) `); await queryRunner.query(` ALTER TABLE asset_versions DROP CONSTRAINT chk_asset_versions_change_type `); await queryRunner.query(` ALTER TABLE asset_versions ADD CONSTRAINT chk_asset_versions_change_type CHECK (change_type IN ( 'BASELINE', 'CREATED', 'UPDATED', 'STATUS_CHANGED', 'GEOMETRY_UPDATED', 'GEOMETRY_REMOVED', 'MEDIA_UPLOADED', 'MEDIA_UPDATED', 'MEDIA_REMOVED', 'PROVENANCE_BASELINE', 'PROVENANCE_UPDATED', 'PROVENANCE_VERIFIED' )) `); await queryRunner.query(` WITH bumped AS ( UPDATE assets SET current_version = current_version + 1 RETURNING * ) INSERT INTO asset_versions ( asset_id, version_number, change_type, changed_fields, snapshot, actor_user_id, actor_username, source, request_id ) SELECT asset.id, asset.current_version, 'PROVENANCE_BASELINE', ARRAY['provenance']::text[], COALESCE(previous.snapshot, '{}'::jsonb) || JSONB_BUILD_OBJECT( 'provenance', JSONB_BUILD_OBJECT( 'origin', asset.data_origin, 'sourceName', asset.source_name, 'sourceReference', asset.source_reference, 'observedAt', asset.source_observed_at, 'notes', asset.source_notes, 'verifiedAt', asset.provenance_verified_at, 'verifiedBy', asset.provenance_verified_by, 'updatedAt', asset.provenance_updated_at, 'updatedBy', asset.provenance_updated_by ), 'currentVersion', asset.current_version ), NULL, NULL, 'SYSTEM', 'migration:phase-b5-provenance-baseline' FROM bumped asset LEFT JOIN LATERAL ( SELECT version.snapshot FROM asset_versions version WHERE version.asset_id = asset.id ORDER BY version.version_number DESC LIMIT 1 ) previous ON true `); await queryRunner.query(` INSERT INTO permissions (code, description) VALUES ('assets.read_provenance', 'Consultar procedencia de datos de activos'), ('assets.manage_provenance', 'Actualizar procedencia de datos de activos'), ('assets.verify_provenance', 'Verificar procedencia de datos de activos'), ('assets.read_temporal', 'Reconstruir el maestro de activos a una fecha') 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 `); } 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(` UPDATE asset_versions SET change_type = 'UPDATED' WHERE change_type IN ( 'PROVENANCE_BASELINE', 'PROVENANCE_UPDATED', 'PROVENANCE_VERIFIED' ) `); await queryRunner.query(` ALTER TABLE asset_versions DROP CONSTRAINT chk_asset_versions_change_type `); await queryRunner.query(` ALTER TABLE asset_versions ADD CONSTRAINT chk_asset_versions_change_type CHECK (change_type IN ( 'BASELINE', 'CREATED', 'UPDATED', 'STATUS_CHANGED', 'GEOMETRY_UPDATED', 'GEOMETRY_REMOVED', 'MEDIA_UPLOADED', 'MEDIA_UPDATED', 'MEDIA_REMOVED' )) `); await queryRunner.query(` ALTER TABLE assets DROP CONSTRAINT fk_assets_provenance_verified_by, DROP CONSTRAINT fk_assets_provenance_updated_by, DROP CONSTRAINT chk_assets_data_origin, DROP COLUMN data_origin, DROP COLUMN source_name, DROP COLUMN source_reference, DROP COLUMN source_observed_at, DROP COLUMN source_notes, DROP COLUMN provenance_verified_at, DROP COLUMN provenance_verified_by, DROP COLUMN provenance_updated_at, DROP COLUMN provenance_updated_by `); } }