open-prompt / src /app /api /messages /route.ts
GitHub Action
Automated sync to Hugging Face
bcce530
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 }
)
}
}