import { MigrationInterface, QueryRunner } from 'typeorm'; function quoteIdentifier(identifier: string): string { return `"${identifier.replaceAll('\"', '\"\"')}"`; } export class PhaseD5323FieldDiscoveries1788717600000 implements MigrationInterface { name = 'PhaseD5323FieldDiscoveries1788717600000'; public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(` CREATE TABLE asset_field_discoveries ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), asset_id uuid NOT NULL UNIQUE, visit_id uuid NOT NULL, status varchar(24) NOT NULL DEFAULT 'PENDING', discovery_notes text, observed_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by uuid NOT NULL, reviewed_at timestamptz, reviewed_by uuid, review_notes text, matched_asset_id uuid, created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_asset_field_discoveries_asset FOREIGN KEY (asset_id) REFERENCES assets(id) ON DELETE RESTRICT, CONSTRAINT fk_asset_field_discoveries_visit FOREIGN KEY (visit_id) REFERENCES inspection_visits(id) ON DELETE RESTRICT, CONSTRAINT fk_asset_field_discoveries_created_by FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE RESTRICT, CONSTRAINT fk_asset_field_discoveries_reviewed_by FOREIGN KEY (reviewed_by) REFERENCES users(id) ON DELETE SET NULL, CONSTRAINT fk_asset_field_discoveries_matched_asset FOREIGN KEY (matched_asset_id) REFERENCES assets(id) ON DELETE RESTRICT, CONSTRAINT chk_asset_field_discoveries_status CHECK (status IN ('PENDING','APPROVED','MATCHED','REJECTED')), CONSTRAINT chk_asset_field_discoveries_match CHECK ( (status = 'MATCHED' AND matched_asset_id IS NOT NULL) OR (status <> 'MATCHED' AND matched_asset_id IS NULL) ), CONSTRAINT chk_asset_field_discoveries_not_self_match CHECK (matched_asset_id IS NULL OR matched_asset_id <> asset_id) ) `); await queryRunner.query(`CREATE INDEX idx_asset_field_discoveries_status_created ON asset_field_discoveries (status, created_at DESC)`); await queryRunner.query(`CREATE INDEX idx_asset_field_discoveries_visit ON asset_field_discoveries (visit_id, created_at DESC)`); await queryRunner.query(`CREATE INDEX idx_asset_field_discoveries_matched ON asset_field_discoveries (matched_asset_id) WHERE matched_asset_id IS NOT NULL`); 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 asset_field_discoveries TO ${applicationRole}`); await queryRunner.query(`REVOKE DELETE ON TABLE asset_field_discoveries FROM ${applicationRole}`); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`DROP TABLE IF EXISTS asset_field_discoveries`); } }