import { MigrationInterface, QueryRunner } from "typeorm"; /** * Business-license files used to live inline as a jsonb array on * `company_profiles.business_license_files`. They now belong to the FileRecord * model (`freight.files`, resource `company_profiles`, code `business_license`) * so they get stable ids and stream through `GET /api/files/:id` — the same * proxy path regular documents use — instead of broken direct-MinIO URLs. * * This copies each existing inline entry into `freight.files` by reference * (keeping the stored object URL — no bytes are re-uploaded). The original jsonb * column is left intact for rollback safety. */ export class MigrateLicenseFilesToFileRecords2040000000000 implements MigrationInterface { name = "MigrateLicenseFilesToFileRecords2040000000000"; public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(` INSERT INTO freight.files (id, resource_id, resource, code, name, url, size, mime_type, created_at, updated_at) SELECT gen_random_uuid(), cp.id, 'company_profiles', 'business_license', COALESCE(elem->>'name', 'license'), elem->>'url', COALESCE(NULLIF(elem->>'size', '')::int, 0), COALESCE(NULLIF(elem->>'mimeType', ''), 'application/octet-stream'), now(), now() FROM freight.company_profiles cp CROSS JOIN LATERAL jsonb_array_elements(cp.business_license_files) AS elem WHERE cp.business_license_files IS NOT NULL AND jsonb_typeof(cp.business_license_files) = 'array' AND elem->>'url' IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM freight.files f WHERE f.resource_id = cp.id AND f.resource = 'company_profiles' AND f.code = 'business_license' AND f.url = elem->>'url' AND f.deleted_at IS NULL ); `); } public async down(queryRunner: QueryRunner): Promise { // Reverse the model migration by dropping the license FileRecords. The // original jsonb column was never cleared, so the data still exists there. await queryRunner.query(` DELETE FROM freight.files WHERE resource = 'company_profiles' AND code = 'business_license'; `); } }