diff --git a/apps/edr-freight-api/src/migrations/2320000000000-SupportChatAttachments.ts b/apps/edr-freight-api/src/migrations/2320000000000-SupportChatAttachments.ts new file mode 100644 index 000000000..1f383f1da --- /dev/null +++ b/apps/edr-freight-api/src/migrations/2320000000000-SupportChatAttachments.ts @@ -0,0 +1,50 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +/** + * Let a support message carry files instead of text. + * + * No new table: chat attachments reuse the polymorphic `freight.files` record + * with `resource = 'support_message'` and `resource_id = `, the same + * way bookings/contracts/companies already store theirs. + * + * The only schema change is dropping NOT NULL from `support_messages.body`, so + * an attachment-only message can say "there is no text" rather than smuggling + * that fact through an empty string. DROP NOT NULL is a catalog-only change in + * Postgres — no table rewrite, no long lock — so this is safe on a live table. + * + * The partial index on (resource, resource_id) is what makes hydrating a page of + * messages one indexed lookup instead of a scan of every file row in the system. + */ +export class SupportChatAttachments2320000000000 implements MigrationInterface { + name = "SupportChatAttachments2320000000000"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE freight.support_messages + ALTER COLUMN body DROP NOT NULL + `); + + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS "IDX_FILES_RESOURCE_LOOKUP" + ON freight.files (resource, resource_id) + WHERE deleted_at IS NULL + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + DROP INDEX IF EXISTS freight."IDX_FILES_RESOURCE_LOOKUP" + `); + + // Re-imposing NOT NULL would fail on any attachment-only message written + // while this migration was applied. Backfill those to '' first so the + // rollback is deterministic rather than dependent on production data. + await queryRunner.query(` + UPDATE freight.support_messages SET body = '' WHERE body IS NULL + `); + await queryRunner.query(` + ALTER TABLE freight.support_messages + ALTER COLUMN body SET NOT NULL + `); + } +} diff --git a/apps/edr-freight-api/src/modules/files/files.controller.ts b/apps/edr-freight-api/src/modules/files/files.controller.ts index 307e24985..6978ad446 100644 --- a/apps/edr-freight-api/src/modules/files/files.controller.ts +++ b/apps/edr-freight-api/src/modules/files/files.controller.ts @@ -1,12 +1,19 @@ +import { SUPPORT_ATTACHMENT_RESOURCE } from "@edr/types"; import { Controller, + ForbiddenException, Get, Param, ParseUUIDPipe, Query, Res, } from "@nestjs/common"; -import { ApiBearerAuth, ApiOperation, ApiQuery, ApiTags } from "@nestjs/swagger"; +import { + ApiBearerAuth, + ApiOperation, + ApiQuery, + ApiTags, +} from "@nestjs/swagger"; import { Response } from "express"; import { FilesService } from "./files.service"; @@ -23,13 +30,16 @@ export class FilesController { // Browser inline previews (/