import { NextRequest, NextResponse } from "next/server"; import { getPayload } from "payload"; import config from "@payload-config"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; interface ContactBody { name?: unknown; email?: unknown; message?: unknown; turnstileToken?: unknown; } interface TurnstileResponse { success: boolean; "error-codes"?: string[]; } async function verifyTurnstile(token: string, ip: string): Promise { const secret = process.env.TURNSTILE_SECRET_KEY; if (!secret) return true; const body = new URLSearchParams({ secret, response: token, remoteip: ip, }); const res = await fetch( "https://challenges.cloudflare.com/turnstile/v0/siteverify", { method: "POST", body }, ); const data = (await res.json()) as TurnstileResponse; return data.success === true; } export async function POST(req: NextRequest) { const raw = (await req.json().catch(() => null)) as ContactBody | null; if (!raw) { return NextResponse.json({ ok: false, error: "Invalid request body." }, { status: 400 }); } const { name, email, message, turnstileToken } = raw; if (typeof name !== "string" || !name.trim()) { return NextResponse.json({ ok: false, error: "Name is required." }, { status: 400 }); } if (typeof email !== "string" || !email.trim()) { return NextResponse.json({ ok: false, error: "Email is required." }, { status: 400 }); } if (typeof message !== "string" || !message.trim()) { return NextResponse.json({ ok: false, error: "Message is required." }, { status: 400 }); } const forwardedFor = req.headers.get("x-forwarded-for") ?? ""; const ip = forwardedFor.split(",")[0]?.trim() ?? ""; if (process.env.TURNSTILE_SECRET_KEY && typeof turnstileToken === "string") { const valid = await verifyTurnstile(turnstileToken, ip); if (!valid) { return NextResponse.json( { ok: false, error: "Spam protection check failed. Please try again." }, { status: 400 }, ); } } try { const payload = await getPayload({ config }); await payload.create({ collection: "contactSubmissions", data: { name: name.trim(), email: email.trim(), message: message.trim(), turnstileVerified: Boolean( process.env.TURNSTILE_SECRET_KEY && typeof turnstileToken === "string", ), submittedAt: new Date().toISOString(), ip: ip || undefined, }, }); return NextResponse.json({ ok: true }, { status: 200 }); } catch (err) { const message = err instanceof Error ? err.message : "Unknown error"; return NextResponse.json({ ok: false, error: message }, { status: 500 }); } }