import { BadRequestException, Body, Controller, Delete, ForbiddenException, Get, HttpCode, HttpStatus, Inject, NotFoundException, Param, Patch, Post, Query, UseGuards, } from '@nestjs/common'; import type { Brain } from '@mosaicstack/brain'; import { BRAIN } from '../brain/brain.tokens.js'; import { AuthGuard } from '../auth/auth.guard.js'; import { CurrentUser } from '../auth/current-user.decorator.js'; import { CreateConversationDto, UpdateConversationDto, SendMessageDto, SearchMessagesDto, } from './conversations.dto.js'; @Controller('api/conversations') @UseGuards(AuthGuard) export class ConversationsController { constructor(@Inject(BRAIN) private readonly brain: Brain) {} @Get() async list(@CurrentUser() user: { id: string }) { return this.brain.conversations.findAll(user.id); } @Get('search') async search(@Query() dto: SearchMessagesDto, @CurrentUser() user: { id: string }) { if (!dto.q || dto.q.trim().length === 0) { throw new BadRequestException('Query parameter "q" is required and must not be empty'); } const limit = dto.limit ?? 20; const offset = dto.offset ?? 0; return this.brain.conversations.searchMessages(user.id, dto.q.trim(), limit, offset); } @Get(':id') async findOne(@Param('id') id: string, @CurrentUser() user: { id: string }) { const conversation = await this.brain.conversations.findById(id, user.id); if (!conversation) throw new NotFoundException('Conversation not found'); return conversation; } @Post() async create(@CurrentUser() user: { id: string }, @Body() dto: CreateConversationDto) { return this.brain.conversations.create({ userId: user.id, title: dto.title, projectId: dto.projectId, }); } @Patch(':id') async update( @Param('id') id: string, @Body() dto: UpdateConversationDto, @CurrentUser() user: { id: string }, ) { const conversation = await this.brain.conversations.update(id, user.id, dto); if (!conversation) throw new NotFoundException('Conversation not found'); return conversation; } @Delete(':id') @HttpCode(HttpStatus.NO_CONTENT) async remove(@Param('id') id: string, @CurrentUser() user: { id: string }) { const deleted = await this.brain.conversations.remove(id, user.id); if (!deleted) throw new NotFoundException('Conversation not found'); } @Get(':id/messages') async listMessages(@Param('id') id: string, @CurrentUser() user: { id: string }) { // Verify ownership explicitly to return a clear 404 rather than an empty list. const conversation = await this.brain.conversations.findById(id, user.id); if (!conversation) throw new NotFoundException('Conversation not found'); return this.brain.conversations.findMessages(id, user.id); } @Post(':id/messages') async addMessage( @Param('id') id: string, @Body() dto: SendMessageDto, @CurrentUser() user: { id: string }, ) { const message = await this.brain.conversations.addMessage( { conversationId: id, role: dto.role, content: dto.content, metadata: dto.metadata, }, user.id, ); if (!message) throw new ForbiddenException('Conversation not found or access denied'); return message; } }