"use server"; import { revalidatePath } from "next/cache"; import { z } from "zod"; import { auth } from "@/lib/auth"; import { prisma } from "@/lib/prisma"; import { clientIp, rateLimit } from "@/lib/rate-limit"; import { moderateOrThrow } from "@/lib/services/moderation"; const ticketSchema = z.object({ title: z.string().min(1, "Title is required").max(255), content: z.string().min(1, "Content is required").max(5000), }); export async function createTicket(formData: FormData): Promise { // Re-read the session user id server-side; never trust a form-supplied id. const session = await auth(); const userId = Number(session?.user?.id); if (!Number.isInteger(userId) || userId <= 0) return; await clientIp(); if (!(await rateLimit(`ticket:${userId}`, 3, 60_000)).ok) return; const raw = { title: String(formData.get("title") ?? "") .normalize("NFC") .trim() .slice(0, 255), content: String(formData.get("content") ?? "") .normalize("NFC") .trim() .slice(0, 5000), }; const parsed = ticketSchema.safeParse(raw); if (!parsed.success) return; const { title, content } = parsed.data; // Moderation check try { await moderateOrThrow(`${title} ${content}`); } catch { return; } const now = new Date(); await prisma.websiteHelpCenterTickets.create({ data: { userId, title, content, open: true, createdAt: now, updatedAt: now, }, }); revalidatePath("/help/tickets"); }