import { NextRequest, NextResponse } from "next/server"; import { headers } from "next/headers"; import { auth } from "@/lib/auth"; import { prisma } from "@/lib/prisma"; import { sendQuestionCreatedEmail } from "@/lib/email"; import { isAllowedPublicUrl } from "@/lib/s3"; interface FileAttachment { name: string; url: string; size: number; } export async function GET() { const session = await auth.api.getSession({ headers: await headers() }); if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); const isStaff = session.user.role === "admin" || session.user.role === "curator"; const userSelect = isStaff ? { id: true as const, name: true as const, email: true as const } : { id: true as const, name: true as const }; const questions = await prisma.studentQuestion.findMany({ where: isStaff ? undefined : { userId: session.user.id }, include: { user: { select: userSelect }, course: { select: { id: true, title: true } }, _count: { select: { messages: { where: isStaff ? { isRead: false, author: { role: "student" } } : { isRead: false, NOT: { authorId: session.user.id } }, }, }, }, }, orderBy: { updatedAt: "desc" }, }); return NextResponse.json( questions.map((q) => ({ id: q.id, title: q.title, status: q.status, createdAt: q.createdAt, updatedAt: q.updatedAt, user: q.user, course: q.course, unreadCount: q._count.messages, })) ); } export async function POST(req: NextRequest) { const session = await auth.api.getSession({ headers: await headers() }); if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); if (session.user.role !== "student") { return NextResponse.json({ error: "Forbidden" }, { status: 403 }); } let body: unknown; try { body = await req.json(); } catch { return NextResponse.json({ error: "Invalid JSON" }, { status: 400 }); } const { title, text, courseId, files } = body as { title: string; text: string; courseId?: string; files?: FileAttachment[]; }; if (!title?.trim() || !text?.trim()) { return NextResponse.json({ error: "title and text are required" }, { status: 400 }); } const safeFiles = files ?.filter( (f) => typeof f.name === "string" && isAllowedPublicUrl(f.url) && typeof f.size === "number" ) .map((f) => ({ name: f.name.slice(0, 255), url: f.url, size: Math.max(0, f.size) })); const question = await prisma.studentQuestion.create({ data: { userId: session.user.id, courseId: courseId ?? null, title: title.trim(), messages: { create: { authorId: session.user.id, text: text.trim(), files: safeFiles?.length ? (safeFiles as object[]) : undefined, }, }, }, }); const staff = await prisma.user.findMany({ where: { role: { in: ["admin", "curator"] } }, select: { email: true, name: true }, }); void Promise.all( staff.map((s) => sendQuestionCreatedEmail(s.email, s.name, session.user.name, title.trim()) ) ); return NextResponse.json(question, { status: 201 }); }