Files
edr-platform/apps/edr-freight-api/src/modules/support-chat/support-chat.controller.ts
2026-07-18 08:52:06 +00:00

115 lines
3.3 KiB
TypeScript

import { CurrentUser } from "@edr/api-common";
import {
SUPPORT_ATTACHMENT_MAX_PER_MESSAGE,
SupportAuthorRole,
} from "@edr/types";
import {
Body,
Controller,
Get,
Post,
Query,
UploadedFiles,
UseInterceptors,
} from "@nestjs/common";
import { FilesInterceptor } from "@nestjs/platform-express";
import { ApiBody, ApiConsumes, ApiOperation, ApiTags } from "@nestjs/swagger";
import {
AuthUserPayload,
resolveAuthUserId,
} from "../../common/resolve-auth-user-id";
import {
SUPPORT_ATTACHMENT_FIELD,
supportAttachmentMulterOptions,
} from "./attachment-upload.options";
import { ListMessagesQueryDto } from "./dto/list-messages-query.dto";
import { SendMessageDto } from "./dto/send-message.dto";
import { SupportChatService } from "./support-chat.service";
/**
* Portal (customer) support-chat endpoints. The caller's company has exactly one
* thread, so these are addressed as a singleton — no conversation id on the wire,
* and nothing for a portal user to pick between.
*/
@ApiTags("support-chat")
@Controller("support")
export class SupportChatController {
constructor(private readonly service: SupportChatService) {}
@Get("conversation")
@ApiOperation({
summary: "My company's support thread (null until someone speaks)",
})
conversation(@CurrentUser() user: AuthUserPayload) {
return this.service.getCustomerConversation(resolveAuthUserId(user));
}
@Get("conversation/messages")
@ApiOperation({
summary: "Messages in my company's support thread (newest page first)",
description:
"Keyset-paginated backwards from the newest message. Omit `before` for " +
"the newest page, then pass the previous response's `nextCursor` to walk " +
"back through history. `nextCursor: null` means the thread's start.",
})
messages(
@CurrentUser() user: AuthUserPayload,
@Query() query: ListMessagesQueryDto,
) {
return this.service.getCustomerMessages(resolveAuthUserId(user), query);
}
@Post("conversation/messages")
@UseInterceptors(
FilesInterceptor(
SUPPORT_ATTACHMENT_FIELD,
SUPPORT_ATTACHMENT_MAX_PER_MESSAGE,
supportAttachmentMulterOptions,
),
)
@ApiConsumes("multipart/form-data", "application/json")
@ApiBody({
schema: {
type: "object",
properties: {
body: { type: "string" },
attachments: {
type: "array",
items: { type: "string", format: "binary" },
},
},
},
})
@ApiOperation({
summary:
"Send a message as the customer (optionally with attachments), opening the thread if needed",
})
send(
@CurrentUser() user: AuthUserPayload,
@Body() body: SendMessageDto,
@UploadedFiles() attachments?: Express.Multer.File[],
) {
return this.service.sendAsCustomer(
resolveAuthUserId(user),
body.body,
attachments ?? [],
);
}
@Post("conversation/read")
@ApiOperation({ summary: "Mark my company's thread read (customer side)" })
read(@CurrentUser() user: AuthUserPayload) {
return this.service.markCustomerRead(resolveAuthUserId(user));
}
@Get("unread-count")
@ApiOperation({ summary: "Count my unread support messages" })
unread(@CurrentUser() user: AuthUserPayload) {
return this.service.unreadCount(
SupportAuthorRole.CUSTOMER,
resolveAuthUserId(user),
);
}
}