/** * POST /api/inquiries: the Inquiries form (multipart, works without script). * Checks the post, the rate limit and Turnstile, saves a copy through the * content service, emails Greg, and redirects (303) to /inquiries/sent with * the gk-inquiry cookie. A post that is turned down goes back to /inquiries * with the reason and the visitor's answers. */ import type { APIRoute } from "astro"; import { clientIp, encodeDraft, formValues, inquiryEmail, inquiryFields, inquirySent, isBot, saveAndSend, TURNSTILE_FIELD, validateInquiry, type ErrorCode, type FieldErrors, } from "../../lib/forms.ts"; import { contactTo, flashCookie, inquiryLimiter, maxAttachmentBytes, reportDelivery, saveSubmission, sendMail, verifyTurnstile, } from "../../lib/forms-server.ts"; export const prerender = false; export const POST: APIRoute = async ({ request, cookies, redirect, url, clientAddress }) => { let form: FormData; try { form = await request.formData(); } catch { return redirect("/inquiries?error=invalid", 303); } const type = String(form.get("type") ?? ""); const back = (code: ErrorCode, errors: FieldErrors = {}) => { cookies.set("gk-inquiry-draft", encodeDraft({ code, errors, values: formValues(form) }), flashCookie(url)); const q = new URLSearchParams({ error: code }); if (/^[a-z]{1,20}$/.test(type)) q.set("type", type); return redirect(`/inquiries?${q}`, 303); }; // A bot filled the hidden field: answer as if it worked, keep nothing. if (isBot(form)) return redirect("/inquiries/sent", 303); const checked = validateInquiry(form, { maxBytes: maxAttachmentBytes() }); if (!checked.ok) return back(checked.code, checked.errors); const q = checked.value; let ip = "unknown"; try { ip = clientIp(request.headers, clientAddress); } catch { ip = clientIp(request.headers); } if (inquiryLimiter.hit(ip)) return back("rate"); if (!(await verifyTurnstile(String(form.get(TURNSTILE_FIELD) ?? ""), ip))) return back("bot"); const result = await saveAndSend({ save: () => saveSubmission({ kind: "inquiry", type: q.type, name: q.name, email: q.email, fields: inquiryFields(q), attachment: q.attachment }), email: (savedId) => sendMail({ to: contactTo(), replyTo: { name: q.name, address: q.email }, ...inquiryEmail(q, savedId), attachment: q.attachment }), report: reportDelivery, }); if (!result.ok) return back("failed"); cookies.set("gk-inquiry", JSON.stringify(inquirySent(q)), flashCookie(url)); return redirect("/inquiries/sent", 303); };