import { NextRequest, NextResponse } from "next/server"; import { z } from "zod"; import { db, conversations, conversationReads, messages, users, eq, or, desc, and, gt, sql } from "@epicure/db"; import { requireSession } from "@/lib/api-auth"; import { applyRateLimit } from "@/lib/rate-limit"; import { findOrCreateConversation, otherParticipantId } from "@/lib/messaging"; import { isBlockedEitherWay } from "@/lib/blocks"; import { randomUUID } from "crypto"; const CreateSchema = z.object({ username: z.string().min(1), content: z.string().min(1).max(4000).optional(), }); export async function GET() { const { session, response } = await requireSession(); if (response) return response; const rows = await db .select() .from(conversations) .where(or(eq(conversations.userAId, session!.user.id), eq(conversations.userBId, session!.user.id))) .orderBy(desc(conversations.lastMessageAt)) .limit(50); const result = await Promise.all( rows.map(async (conv) => { const otherId = otherParticipantId(conv, session!.user.id); const [other, lastMessage, readRow] = await Promise.all([ db.query.users.findFirst({ where: eq(users.id, otherId), columns: { id: true, name: true, username: true, avatarUrl: true }, }), db.query.messages.findFirst({ where: eq(messages.conversationId, conv.id), orderBy: (m, { desc: d }) => [d(m.createdAt)], }), db.query.conversationReads.findFirst({ where: and(eq(conversationReads.conversationId, conv.id), eq(conversationReads.userId, session!.user.id)), }), ]); const unreadRow = await db .select({ total: sql`count(*)` }) .from(messages) .where( and( eq(messages.conversationId, conv.id), readRow ? gt(messages.createdAt, readRow.lastReadAt) : sql`true`, sql`${messages.senderId} != ${session!.user.id}` ) ); return { id: conv.id, otherUser: other, lastMessage: lastMessage?.content ?? null, lastMessageAt: conv.lastMessageAt, unreadCount: Number(unreadRow[0]?.total ?? 0), }; }) ); return NextResponse.json({ conversations: result }); } export async function POST(req: NextRequest) { const { session, response } = await requireSession(); if (response) return response; const limited = await applyRateLimit(`rl:message:${session!.user.id}`, 30, 60); if (limited) return limited; const body = await req.json() as unknown; const parsed = CreateSchema.safeParse(body); if (!parsed.success) return NextResponse.json({ error: "Validation error" }, { status: 400 }); const target = await db.query.users.findFirst({ where: eq(users.username, parsed.data.username) }); if (!target) return NextResponse.json({ error: "Not found" }, { status: 404 }); if (target.id === session!.user.id) return NextResponse.json({ error: "Cannot message yourself" }, { status: 400 }); if (await isBlockedEitherWay(session!.user.id, target.id)) { return NextResponse.json({ error: "Not found" }, { status: 404 }); } const conversation = await findOrCreateConversation(session!.user.id, target.id); if (parsed.data.content) { await db.insert(messages).values({ id: randomUUID(), conversationId: conversation.id, senderId: session!.user.id, content: parsed.data.content, }); await db.update(conversations).set({ lastMessageAt: new Date() }).where(eq(conversations.id, conversation.id)); } return NextResponse.json({ conversationId: conversation.id }, { status: 201 }); }