From 6126b5578405e7a691ce93da80b6145aa8b61bd8 Mon Sep 17 00:00:00 2001 From: Nathnael Date: Sat, 18 Jul 2026 08:52:06 +0000 Subject: [PATCH 1/5] feat: setup attachment to the freight chat --- .../2320000000000-SupportChatAttachments.ts | 50 +++ .../src/modules/files/files.controller.ts | 30 +- .../src/modules/files/files.repository.ts | 17 +- .../src/modules/files/files.service.ts | 47 ++- .../support-chat/attachment-upload.options.ts | 25 ++ .../dto/list-messages-query.dto.ts | 30 ++ .../support-chat/dto/send-message.dto.ts | 14 +- .../entities/support-message.entity.ts | 8 +- .../modules/support-chat/message-cursor.ts | 54 +++ .../support-attachment.controller.ts | 100 +++++ .../support-chat-agent.controller.ts | 63 ++- .../support-chat/support-chat.controller.ts | 75 +++- .../support-chat/support-chat.module.ts | 13 +- .../support-chat/support-chat.service.ts | 278 +++++++++++-- .../support-message.repository.ts | 54 ++- .../features/support/AttachmentDraftBar.tsx | 78 ++++ .../features/support/MessageAttachments.tsx | 122 ++++++ .../src/features/support/supportApi.ts | 57 ++- .../features/support/useAttachmentDraft.ts | 130 ++++++ .../support/useAttachmentObjectUrl.ts | 80 ++++ .../src/features/support/useSupport.ts | 165 +++++++- .../src/features/support/useSupportSocket.ts | 28 +- .../src/pages/support/SupportInboxPage.tsx | 374 +++++++++++++++--- .../features/support/AttachmentDraftBar.tsx | 80 ++++ .../features/support/MessageAttachments.tsx | 123 ++++++ .../src/features/support/SupportPanel.tsx | 254 ++++++++++-- .../src/features/support/SupportWidget.tsx | 20 +- .../portal/src/features/support/supportApi.ts | 64 ++- .../features/support/useAttachmentDraft.ts | 130 ++++++ .../support/useAttachmentObjectUrl.ts | 80 ++++ .../portal/src/features/support/useSupport.ts | 104 ++++- .../src/features/support/useSupportSocket.ts | 30 +- 32 files changed, 2582 insertions(+), 195 deletions(-) create mode 100644 apps/edr-freight-api/src/migrations/2320000000000-SupportChatAttachments.ts create mode 100644 apps/edr-freight-api/src/modules/support-chat/attachment-upload.options.ts create mode 100644 apps/edr-freight-api/src/modules/support-chat/dto/list-messages-query.dto.ts create mode 100644 apps/edr-freight-api/src/modules/support-chat/message-cursor.ts create mode 100644 apps/edr-freight-api/src/modules/support-chat/support-attachment.controller.ts create mode 100644 apps/edr-freight-web/backoffice/src/features/support/AttachmentDraftBar.tsx create mode 100644 apps/edr-freight-web/backoffice/src/features/support/MessageAttachments.tsx create mode 100644 apps/edr-freight-web/backoffice/src/features/support/useAttachmentDraft.ts create mode 100644 apps/edr-freight-web/backoffice/src/features/support/useAttachmentObjectUrl.ts create mode 100644 apps/edr-freight-web/portal/src/features/support/AttachmentDraftBar.tsx create mode 100644 apps/edr-freight-web/portal/src/features/support/MessageAttachments.tsx create mode 100644 apps/edr-freight-web/portal/src/features/support/useAttachmentDraft.ts create mode 100644 apps/edr-freight-web/portal/src/features/support/useAttachmentObjectUrl.ts 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 (/