import { NextRequest, NextResponse } from "next/server" import prisma from "@/lib/prisma" import { getAuthUser } from "@/lib/auth" import { createForumPostSchema, parseBody } from "@/lib/validations" import { checkRateLimit, getClientIdentifier } from "@/lib/rate-limit" import { sanitizeText } from "@/lib/api-utils" const FORUM_CATEGORIES = [ "general", "help", "showcase", "feature-request", "tips", ] // GET - List forum posts (uses Prisma include instead of separate user query) export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url) const category = searchParams.get("category") const search = searchParams.get("search") const sort = searchParams.get("sort") || "recent" const limit = Math.min(parseInt(searchParams.get("limit") || "20"), 100) const offset = Math.max(parseInt(searchParams.get("offset") || "0"), 0) // Build where clause const whereClause: Record = {} if (category && category !== "all") { whereClause.category = category } if (search) { whereClause.OR = [ { title: { contains: search, mode: "insensitive" } }, { content: { contains: search, mode: "insensitive" } }, ] } // Build order by let orderBy: Record[] = [{ createdAt: "desc" }] if (sort === "popular") { orderBy = [{ views: "desc" }, { likes: "desc" }] } else if (sort === "unanswered") { whereClause.replies = { none: {} } } // Use Prisma include to get user data in single query (fixes N+1) const [posts, total] = await Promise.all([ prisma.forumPost.findMany({ where: whereClause, orderBy, take: limit, skip: offset, include: { user: { select: { id: true, name: true, username: true, image: true, }, }, _count: { select: { replies: true } } } }), prisma.forumPost.count({ where: whereClause }), ]) const postsWithCounts = posts.map(post => ({ ...post, replyCount: post._count.replies, })) return NextResponse.json({ posts: postsWithCounts, total, categories: FORUM_CATEGORIES, hasMore: offset + posts.length < total, }, { headers: { 'Cache-Control': 'public, s-maxage=30, stale-while-revalidate=120', }, }) } catch (error) { console.error("Error fetching forum posts:", error) return NextResponse.json( { error: "Failed to fetch posts" }, { status: 500 } ) } } // POST - Create a new forum post — requires auth, 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(`forum:${identifier}`, true) if (!rateLimit.success) { return NextResponse.json({ error: rateLimit.error }, { status: 429 }) } const body = await request.json() const parsed = parseBody(createForumPostSchema, body) if (!parsed.success) return parsed.response const { title, content, category } = parsed.data // Sanitize content (strip HTML) const sanitizedContent = sanitizeText(content, 20000) const sanitizedTitle = sanitizeText(title, 300) const post = await prisma.forumPost.create({ data: { title: sanitizedTitle, content: sanitizedContent, category, userId: authUser.id, }, }) return NextResponse.json(post) } catch (error) { console.error("Error creating forum post:", error) return NextResponse.json( { error: "Failed to create post" }, { status: 500 } ) } }