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 { PortalCustomer } from "../../common/booking-guards"; 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") @PortalCustomer() 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), ); } }