import { NextRequest, NextResponse } from "next/server" import prisma from "@/lib/prisma" import { getAuthUser } from "@/lib/auth" import { sendMessageSchema, parseBody } from "@/lib/validations" import { checkRateLimit, getClientIdentifier } from "@/lib/rate-limit" import { sanitizeText } from "@/lib/api-utils" // GET - Get conversations for current user (fixed N+1 with participant include) export async function GET(request: NextRequest) { try { const authUser = await getAuthUser() if (!authUser) { return NextResponse.json( { error: "Authentication required" }, { status: 401 } ) } const conversations = await prisma.conversation.findMany({ where: { OR: [ { participant1: authUser.id }, { participant2: authUser.id }, ], }, orderBy: { lastActivity: "desc" }, include: { messages: { orderBy: { createdAt: "desc" }, take: 1, }, _count: { select: { messages: { where: { isRead: false, senderId: { not: authUser.id }, }, }, }, }, }, }) // Get other participant info (batch query instead of N+1) const participantIds = conversations.map(c => c.participant1 === authUser.id ? c.participant2 : c.participant1 ) const users = await prisma.user.findMany({ where: { id: { in: participantIds } }, select: { id: true, name: true, username: true, image: true, }, }) const userMap = new Map(users.map(u => [u.id, u])) const conversationsWithUsers = conversations.map(conv => { const otherId = conv.participant1 === authUser.id ? conv.participant2 : conv.participant1 return { ...conv, otherUser: userMap.get(otherId) || null, unreadCount: conv._count.messages, lastMessage: conv.messages[0] || null, } }) return NextResponse.json({ conversations: conversationsWithUsers }) } catch (error) { console.error("Error fetching conversations:", error) return NextResponse.json( { error: "Failed to fetch conversations" }, { status: 500 } ) } } // POST - Start a new conversation or send message (rate limited, validated, sanitized) export async function POST(request: NextRequest) { try { const authUser = await getAuthUser() if (!authUser) { return NextResponse.json( { error: "Authentication required" }, { status: 401 } ) } // Rate limit const identifier = getClientIdentifier(request, authUser.id) const rateLimit = await checkRateLimit(`messages:${identifier}`, true) if (!rateLimit.success) { return NextResponse.json({ error: rateLimit.error }, { status: 429 }) } const body = await request.json() const parsed = parseBody(sendMessageSchema, body) if (!parsed.success) return parsed.response const { recipientId, content } = parsed.data if (recipientId === authUser.id) { return NextResponse.json( { error: "Cannot message yourself" }, { status: 400 } ) } // Sanitize content const sanitizedContent = sanitizeText(content, 5000) if (!sanitizedContent) { return NextResponse.json( { error: "Message content cannot be empty" }, { status: 400 } ) } // Find or create conversation — always sort IDs for consistent unique constraint const [p1, p2] = [authUser.id, recipientId].sort() let conversation = await prisma.conversation.findUnique({ where: { participant1_participant2: { participant1: p1, participant2: p2 }, }, }) if (!conversation) { conversation = await prisma.conversation.create({ data: { participant1: p1, participant2: p2, }, }) } // Create message const message = await prisma.message.create({ data: { content: sanitizedContent, senderId: authUser.id, conversationId: conversation.id, }, }) // Update conversation await prisma.conversation.update({ where: { id: conversation.id }, data: { lastMessage: sanitizedContent, lastActivity: new Date(), }, }) return NextResponse.json({ conversation, message }) } catch (error) { console.error("Error sending message:", error) return NextResponse.json( { error: "Failed to send message" }, { status: 500 } ) } }