chore: import DH V2 D5.6.4 production baseline
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
const permissionCode = 'assets.read_history';
|
||||
|
||||
const rolePermissionValues = `
|
||||
('admin', 'assets.read_history'),
|
||||
('director', 'assets.read_history'),
|
||||
('supervisor', 'assets.read_history'),
|
||||
('inspector', 'assets.read_history'),
|
||||
('auditor', 'assets.read_history')
|
||||
`;
|
||||
|
||||
function quoteIdentifier(identifier: string): string {
|
||||
return `"${identifier.replaceAll('"', '""')}"`;
|
||||
}
|
||||
|
||||
export class PhaseB3AssetVersions1786723201000 implements MigrationInterface {
|
||||
name = 'PhaseB3AssetVersions1786723201000';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE assets
|
||||
ADD COLUMN current_version integer NOT NULL DEFAULT 0,
|
||||
ADD CONSTRAINT chk_assets_current_version CHECK (current_version >= 0)
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE asset_versions (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
asset_id uuid NOT NULL,
|
||||
version_number integer NOT NULL,
|
||||
change_type varchar(32) NOT NULL,
|
||||
changed_fields text[] NOT NULL DEFAULT '{}',
|
||||
snapshot jsonb NOT NULL,
|
||||
occurred_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
actor_user_id uuid,
|
||||
actor_username varchar(80),
|
||||
source varchar(32) NOT NULL,
|
||||
request_id varchar(128),
|
||||
CONSTRAINT uq_asset_versions_asset_number
|
||||
UNIQUE (asset_id, version_number),
|
||||
CONSTRAINT chk_asset_versions_number CHECK (version_number >= 1),
|
||||
CONSTRAINT chk_asset_versions_change_type CHECK (change_type IN (
|
||||
'BASELINE', 'CREATED', 'UPDATED', 'STATUS_CHANGED',
|
||||
'GEOMETRY_UPDATED', 'GEOMETRY_REMOVED'
|
||||
)),
|
||||
CONSTRAINT chk_asset_versions_source CHECK (source IN (
|
||||
'WEB', 'ANDROID', 'SYSTEM', 'IMPORT'
|
||||
)),
|
||||
CONSTRAINT chk_asset_versions_snapshot_object
|
||||
CHECK (jsonb_typeof(snapshot) = 'object'),
|
||||
CONSTRAINT fk_asset_versions_asset FOREIGN KEY (asset_id)
|
||||
REFERENCES assets(id) ON DELETE RESTRICT,
|
||||
CONSTRAINT fk_asset_versions_actor FOREIGN KEY (actor_user_id)
|
||||
REFERENCES users(id) ON DELETE SET NULL
|
||||
)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX idx_asset_versions_asset_number
|
||||
ON asset_versions (asset_id, version_number DESC)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX idx_asset_versions_occurred_at
|
||||
ON asset_versions (occurred_at DESC)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX idx_asset_versions_change_type
|
||||
ON asset_versions (change_type)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX idx_asset_versions_snapshot_gin
|
||||
ON asset_versions USING GIN (snapshot jsonb_path_ops)
|
||||
`);
|
||||
|
||||
await queryRunner.query('UPDATE assets SET current_version = 1');
|
||||
await queryRunner.query(`
|
||||
INSERT INTO asset_versions (
|
||||
asset_id, version_number, change_type, changed_fields, snapshot,
|
||||
occurred_at, actor_user_id, actor_username, source
|
||||
)
|
||||
SELECT
|
||||
asset.id,
|
||||
1,
|
||||
'BASELINE',
|
||||
ARRAY['baseline']::text[],
|
||||
JSONB_BUILD_OBJECT(
|
||||
'id', asset.id,
|
||||
'code', asset.code,
|
||||
'name', asset.name,
|
||||
'description', asset.description,
|
||||
'type', JSONB_BUILD_OBJECT(
|
||||
'id', asset_type.id,
|
||||
'code', asset_type.code,
|
||||
'name', asset_type.name
|
||||
),
|
||||
'parent', CASE WHEN parent.id IS NULL THEN NULL ELSE JSONB_BUILD_OBJECT(
|
||||
'id', parent.id,
|
||||
'code', parent.code,
|
||||
'name', parent.name
|
||||
) END,
|
||||
'informationStatus', asset.information_status,
|
||||
'attributes', COALESCE((
|
||||
SELECT JSONB_AGG(JSONB_BUILD_OBJECT(
|
||||
'definitionId', definition.id,
|
||||
'code', definition.code,
|
||||
'name', definition.name,
|
||||
'dataType', definition.data_type,
|
||||
'isRequired', definition.is_required,
|
||||
'unit', definition.unit,
|
||||
'options', definition.options,
|
||||
'sortOrder', definition.sort_order,
|
||||
'value', value.value
|
||||
) ORDER BY definition.sort_order, definition.name)
|
||||
FROM asset_attribute_definitions definition
|
||||
LEFT JOIN asset_attribute_values value
|
||||
ON value.definition_id = definition.id
|
||||
AND value.asset_id = asset.id
|
||||
WHERE definition.asset_type_id = asset.asset_type_id
|
||||
AND definition.is_active = true
|
||||
), '[]'::jsonb),
|
||||
'geometry', CASE WHEN geometry.asset_id IS NULL THEN NULL ELSE JSONB_BUILD_OBJECT(
|
||||
'assetId', geometry.asset_id,
|
||||
'geometry', ST_AsGeoJSON(geometry.geometry)::jsonb,
|
||||
'geometryType', geometry.geometry_type,
|
||||
'source', geometry.source,
|
||||
'accuracyM', geometry.accuracy_m::double precision,
|
||||
'capturedAt', geometry.captured_at,
|
||||
'deviceLabel', geometry.device_label,
|
||||
'createdAt', geometry.created_at,
|
||||
'updatedAt', geometry.updated_at,
|
||||
'updatedBy', geometry.updated_by
|
||||
) END,
|
||||
'createdAt', asset.created_at,
|
||||
'updatedAt', asset.updated_at,
|
||||
'createdBy', asset.created_by,
|
||||
'updatedBy', asset.updated_by,
|
||||
'currentVersion', 1
|
||||
),
|
||||
asset.updated_at,
|
||||
asset.updated_by,
|
||||
actor.username,
|
||||
'SYSTEM'
|
||||
FROM assets asset
|
||||
INNER JOIN asset_types asset_type ON asset_type.id = asset.asset_type_id
|
||||
LEFT JOIN assets parent ON parent.id = asset.parent_id
|
||||
LEFT JOIN asset_geometries geometry ON geometry.asset_id = asset.id
|
||||
LEFT JOIN users actor ON actor.id = asset.updated_by
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
INSERT INTO permissions (code, description)
|
||||
VALUES ('assets.read_history', 'Consultar versiones históricas completas de activos')
|
||||
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 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');
|
||||
await queryRunner.query(`
|
||||
GRANT SELECT, INSERT ON TABLE asset_versions
|
||||
TO ${quoteIdentifier(appRole)}
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
REVOKE UPDATE, DELETE ON TABLE asset_versions
|
||||
FROM ${quoteIdentifier(appRole)}
|
||||
`);
|
||||
}
|
||||
|
||||
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 = $1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM role_permissions WHERE permission_id = permissions.id
|
||||
)`,
|
||||
[permissionCode],
|
||||
);
|
||||
await queryRunner.query('DROP TABLE asset_versions');
|
||||
await queryRunner.query('ALTER TABLE assets DROP CONSTRAINT chk_assets_current_version');
|
||||
await queryRunner.query('ALTER TABLE assets DROP COLUMN current_version');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user