fix: added chat to the freight api

This commit is contained in:
Nathnael
2026-07-17 10:59:25 +00:00
parent d0cdba6d29
commit 1fd46afaaa
17 changed files with 1131 additions and 1 deletions

View File

@@ -9,6 +9,7 @@ export * from "./contracts";
export * from "./clearance-files.catalog";
export * from "./notifications";
export * from "./booking-window-ws";
export * from "./support-chat";
export enum TradeDirection {
IMPORT = "IMPORT",

View File

@@ -0,0 +1,99 @@
/**
* Shared contracts for the freight in-app customer-support chat.
*
* There is exactly **one conversation per customer company** — any portal user
* of that company sees and continues the same thread, and every backoffice agent
* works the same shared inbox (no assignment). The thread has no lifecycle: it
* is created lazily by whichever side speaks first and stays open forever.
* Messages are text-only for the MVP.
*
* Because the thread is implied by the caller's company, the portal contract is
* addressed as a singleton (`/support/conversation`) and never passes an id.
* Agents address threads by id, since they see every company's.
*
* Mirrors the notification system's contract shape (`notifications.ts`): DTO
* interfaces with string dates for the wire, plus frozen WS event/namespace
* constants shared by the gateway (emitter) and both web apps (subscribers).
*/
/** Who authored a message — the customer side or a backoffice agent. */
export enum SupportAuthorRole {
CUSTOMER = "CUSTOMER",
AGENT = "AGENT",
}
/** A single chat message on the wire. */
export interface SupportMessageDto {
id: string;
conversationId: string;
authorUserId: string;
authorRole: SupportAuthorRole;
/** Display name of the author, resolved at send time (best-effort). */
authorName?: string | null;
body: string;
createdAt: string;
}
/** A company's conversation on the wire, with denormalized last-message fields. */
export interface SupportConversationDto {
id: string;
companyId: string;
companyName?: string | null;
/** Null when an agent opened the thread — no customer created it. */
createdByUserId?: string | null;
lastMessageAt?: string | null;
lastMessagePreview?: string | null;
lastMessageAuthorRole?: SupportAuthorRole | null;
/**
* Unread count *for the caller's side* (messages authored by the other role
* after the caller's read cursor). Populated on list/detail responses only —
* WS payloads carry an unauthoritative 0, so clients must refetch, not trust it.
*/
unreadCount: number;
createdAt: string;
updatedAt: string;
}
/** Post a message. The portal omits the id; the thread is implied by the company. */
export interface SendSupportMessageDto {
body: string;
}
/** Agent opens a thread with a company that has none yet. */
export interface StartSupportConversationDto {
companyId: string;
}
/**
* Result of a portal send: the thread (created on the fly if this was the first
* message) alongside the persisted message.
*/
export interface SendSupportMessageResult {
conversation: SupportConversationDto;
message: SupportMessageDto;
}
/** Paginated list envelope for the agent conversations list endpoint. */
export interface SupportConversationListResult {
items: SupportConversationDto[];
count: number;
/** Total unread conversations for the caller's side (badge source). */
unreadCount: number;
}
/** Socket.io event names pushed server → client on the `support-chat` namespace. */
export const SUPPORT_CHAT_WS_EVENTS = {
/** A new message was added to a conversation the socket can see. */
MESSAGE_NEW: "support:message-new",
/** A conversation's metadata changed (last message, or a thread was opened). */
CONVERSATION_UPDATED: "support:conversation-updated",
} as const;
/** Socket.io namespace the support-chat gateway listens on. */
export const SUPPORT_CHAT_WS_NAMESPACE = "support-chat";
/** Payload for {@link SUPPORT_CHAT_WS_EVENTS.MESSAGE_NEW}. */
export interface SupportMessageEvent {
conversation: SupportConversationDto;
message: SupportMessageDto;
}