import { NextResponse } from "next/server"; import { getPayload } from "payload"; import config from "@payload-config"; import { checkRateLimit, getClientIp } from "@/lib/auth/rateLimit"; export const dynamic = "force-dynamic"; // Registration capacity is checked before the insert. Serialize registrations // for the same event so two simultaneous requests cannot both observe the // same free seat in this single-process deployment. const registrationLocks = new Map>(); async function withEventRegistrationLock(eventId: string, action: () => Promise): Promise { const previous = registrationLocks.get(eventId) ?? Promise.resolve(); let release!: () => void; const current = new Promise((resolve) => { release = resolve; }); registrationLocks.set(eventId, current); await previous; try { return await action(); } finally { release(); if (registrationLocks.get(eventId) === current) registrationLocks.delete(eventId); } } type Args = { params: Promise<{ slug: string }> }; export async function POST(request: Request, { params }: Args) { const { slug } = await params; if (!checkRateLimit(`event-registration:ip:${getClientIp(request)}`, { max: 20, windowMs: 60 * 60 * 1000 })) { return NextResponse.json({ error: "Zu viele Anmeldungen. Bitte versuche es später erneut." }, { status: 429 }); } let body: { name?: unknown; email?: unknown }; try { body = await request.json(); } catch { return NextResponse.json({ error: "Ungültige Anfrage." }, { status: 400 }); } const name = typeof body.name === "string" ? body.name.trim() : ""; const email = typeof body.email === "string" ? body.email.trim().toLowerCase() : ""; if (!name || name.length > 120 || !email || email.length > 254 || !email.includes("@")) { return NextResponse.json({ error: "Bitte gib deinen Namen und eine gültige E-Mail-Adresse an." }, { status: 400 }); } try { const payload = await getPayload({ config }); const { docs } = await payload.find({ collection: "events", where: { slug: { equals: slug } }, limit: 1, }); const event = docs[0]; if (!event || event._status !== "published" || !event.registrationRequired) { return NextResponse.json({ error: "Dieser Termin wurde nicht gefunden." }, { status: 404 }); } if (!checkRateLimit(`event-registration:email:${event.id}:${email}`, { max: 3, windowMs: 60 * 60 * 1000 })) { return NextResponse.json({ error: "Zu viele Anmeldeversuche für diese Adresse. Bitte versuche es später erneut." }, { status: 429 }); } return await withEventRegistrationLock(String(event.id), async () => { const { docs: existingForEmail } = await payload.find({ collection: "event-registrations", where: { and: [ { event: { equals: event.id } }, { email: { equals: email } }, { status: { equals: "registered" } }, ], }, limit: 1, }); if (existingForEmail[0]) { return NextResponse.json({ ok: true, alreadyRegistered: true }); } if (event.maxParticipants) { const { totalDocs } = await payload.count({ collection: "event-registrations", where: { and: [{ event: { equals: event.id } }, { status: { equals: "registered" } }] }, }); if (totalDocs >= event.maxParticipants) { return NextResponse.json({ error: "Dieser Termin ist bereits ausgebucht." }, { status: 409 }); } } await payload.create({ collection: "event-registrations", data: { event: event.id, name, email, status: "registered" }, }); return NextResponse.json({ ok: true }); }); } catch (error) { console.error("event registration failed", error); return NextResponse.json({ error: "Die Anmeldung ist gerade nicht möglich. Bitte versuche es später erneut." }, { status: 500 }); } }