import { timingSafeEqual } from "node:crypto"; import { NextResponse } from "next/server"; import { getPayload } from "payload"; import config from "@payload-config"; import { createMeetingToken } from "@/lib/meeting/token"; import { canJoinMeeting, combineDateAndTime, getMeetingStatus } from "@/lib/meeting/status"; export const dynamic = "force-dynamic"; function safeEqual(a: string, b: string): boolean { const bufA = Buffer.from(a); const bufB = Buffer.from(b); if (bufA.length !== bufB.length) return false; return timingSafeEqual(bufA, bufB); } type Args = { params: Promise<{ slug: string }> }; export async function POST(request: Request, { params }: Args) { const { slug } = await params; let body: { name?: unknown; password?: 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 password = typeof body.password === "string" ? body.password.trim() : ""; if (!name) { return NextResponse.json({ error: "Bitte gib deinen Namen ein." }, { 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.isOnline) { return NextResponse.json({ error: "Dieser Online-Termin wurde nicht gefunden." }, { status: 404 }); } const { user } = await payload.auth({ headers: request.headers }); const isHost = Boolean(user); if (!isHost) { if (!password) { return NextResponse.json({ error: "Bitte gib das Meeting-Passwort ein." }, { status: 400 }); } if (!event.meetingPassword || !safeEqual(password, event.meetingPassword)) { return NextResponse.json({ error: "Das Meeting-Passwort ist nicht korrekt." }, { status: 401 }); } } const settings = await payload.findGlobal({ slug: "meeting-settings" }); const start = combineDateAndTime(event.date, event.startTime); const end = event.endTime ? combineDateAndTime(event.date, event.endTime) : new Date(start.getTime() + 60 * 60_000); const status = getMeetingStatus({ start, end, joinWindowMinutes: isHost ? settings.hostJoinWindowMinutes : settings.participantJoinWindowMinutes, closeAfterMinutes: settings.meetingCloseAfterMinutes, }); if (!canJoinMeeting(status)) { const message = status === "scheduled" ? "Dieser Termin ist noch nicht offen. Bitte versuche es näher am Beginn erneut." : "Dieser Termin ist bereits beendet."; return NextResponse.json({ error: message }, { status: 403 }); } const { token, participantId } = createMeetingToken({ eventSlug: slug, name, role: isHost ? "host" : "participant", }); return NextResponse.json({ token, participantId, role: isHost ? "host" : "participant", eventTitle: event.title, }); } catch (error) { console.error("meeting join failed", error); return NextResponse.json({ error: "Der Beitritt ist gerade nicht möglich. Bitte versuche es später erneut." }, { status: 500 }); } }