Files
dh-inspeccion-v2/api-v3/src/database/migrations/1786723203000-phase-b5-provenance-temporal.ts
T

192 lines
7.2 KiB
TypeScript

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<void> {
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<void> {
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
`);
}
}