Spaces:
Configuration error
Configuration error
| 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 } | |
| ) | |
| } | |
| } | |