import { randomUUID } from 'node:crypto'; import { BadRequestException, Body, Controller, Get, Headers, HttpCode, Param, Post, } from '@nestjs/common'; import { ThreadsService } from './threads.service'; import { MessageService } from '../messaging/message.service'; import { SessionVerifier } from '../platform/session.verifier'; import { SendMessageDto } from './send-message.dto'; @Controller('v1/threads') export class ThreadsController { constructor( private readonly threads: ThreadsService, private readonly messages: MessageService, private readonly session: SessionVerifier, ) {} /** Generic "my threads" — every thread the caller participates in (last message + unread). */ @Get() async listThreads(@Headers('authorization') auth?: string) { return this.messages.listThreads(this.principal(auth)); } /** Create a thread; `membership`/`creatorRole` are opaque, app-supplied thread attributes the kernel stores but never interprets. */ @Post() @HttpCode(201) async createThread(@Body() body: { membership?: string; creatorRole?: string }, @Headers('authorization') auth?: string) { return this.messages.openThread(null, this.principal(auth), { membership: body?.membership, creatorRole: body?.creatorRole }); } /** Governed membership: add a user (by userId) to a thread — policy enforces DM cap / roles. */ @Post(':id/participants') @HttpCode(201) async addParticipant(@Param('id') id: string, @Body() body: { userId: string; role?: string }, @Headers('authorization') auth?: string) { return this.messages.addParticipant(id, this.principal(auth), body.userId, body.role); } @Get(':id/messages') async listMessages(@Param('id') id: string) { return this.threads.getMessages(id); } /** REST/polling fallback for native send (same write as the socket path). */ @Post(':id/messages') @HttpCode(201) async send( @Param('id') id: string, @Body() body: SendMessageDto, @Headers('authorization') authorization?: string, @Headers('idempotency-key') idempotencyKey?: string, ) { return this.messages.send( id, this.principal(authorization), { content: body.content, contentRef: body.contentRef }, idempotencyKey ?? randomUUID(), undefined, body.parentInteractionId, ); } private principal(authorization?: string) { const token = (authorization ?? '').replace(/^Bearer\s+/i, ''); if (!token) throw new BadRequestException('Authorization bearer token is required'); return this.session.verify(token); } }