import type { APIRoute } from 'astro'; import { env as workerEnv } from 'cloudflare:workers'; import { getEmailBinding, sendCloudflareEmail } from '../../lib/email'; export const prerender = false; type RuntimeEnv = Record; function getRuntimeEnv(): RuntimeEnv { return workerEnv as unknown as RuntimeEnv; } function readEnv(name: string, runtimeEnv: RuntimeEnv): string { const runtimeValue = runtimeEnv[name]; if (typeof runtimeValue === 'string' && runtimeValue.trim().length > 0) { return runtimeValue.trim(); } const staticValue = import.meta.env[name]; return typeof staticValue === 'string' ? staticValue.trim() : ''; } function sanitizeRedirect(candidate: string, requestUrl: URL): string { if (!candidate) return ''; try { const u = new URL(candidate, requestUrl); return u.toString(); } catch { return ''; } } function required(value: FormDataEntryValue | null): string { return typeof value === 'string' ? value.trim() : ''; } function wantsJson(request: Request): boolean { return (request.headers.get('Accept') || '').includes('application/json'); } function fail(request: Request, message: string, status: number): Response { if (wantsJson(request)) { return Response.json({ ok: false, error: message }, { status }); } return new Response(message, { status }); } function succeed(request: Request, redirectTarget: string): Response { if (wantsJson(request)) { return Response.json({ ok: true, redirect: redirectTarget }); } return Response.redirect(redirectTarget, 303); } function emailErrorMessage(error: unknown): string { const code = error && typeof error === 'object' && 'code' in error ? String((error as { code?: unknown }).code || '') : ''; const message = error instanceof Error ? error.message : error && typeof error === 'object' && 'message' in error ? String((error as { message?: unknown }).message || '') : ''; switch (code) { case 'E_SENDER_NOT_VERIFIED': case 'E_SENDER_DOMAIN_NOT_AVAILABLE': return 'Dominio mittente non verificato su Cloudflare Email Service.'; case 'E_RECIPIENT_NOT_ALLOWED': return 'Destinatario non autorizzato dal binding email.'; case 'E_RECIPIENT_SUPPRESSED': return 'Destinatario in lista di soppressione (bounce/spam).'; case 'E_RATE_LIMIT_EXCEEDED': case 'E_DAILY_LIMIT_EXCEEDED': return 'Limite di invio email raggiunto. Riprova più tardi.'; case 'E_VALIDATION_ERROR': case 'E_FIELD_MISSING': return message ? `Dati email non validi: ${message}` : 'Dati email non validi.'; default: return code ? `Invio email non riuscito (${code}). Riprova più tardi.` : 'Invio email non riuscito. Riprova più tardi.'; } } async function verifyTurnstile(token: string, secret: string, ip?: string | null): Promise { const body = new URLSearchParams(); body.set('secret', secret); body.set('response', token); if (ip) body.set('remoteip', ip); const response = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', { method: 'POST', body, }); if (!response.ok) return false; const payload = (await response.json()) as { success?: boolean }; return payload.success === true; } function resolveDepartmentRecipient(runtimeEnv: RuntimeEnv, defaultRecipient: string, department: string): string { const normalized = department.trim().toLowerCase(); const map: Record = { info: readEnv('CONTACT_FORM_TO_EMAIL_INFO', runtimeEnv) || defaultRecipient, privacy: readEnv('CONTACT_FORM_TO_EMAIL_PRIVACY', runtimeEnv), careers: readEnv('CONTACT_FORM_TO_EMAIL_CAREERS', runtimeEnv), }; return map[normalized] || defaultRecipient; } export const POST: APIRoute = async ({ request, url }) => { try { const runtimeEnv = getRuntimeEnv(); const mode = readEnv('CONTACT_FORM_MODE', runtimeEnv).toLowerCase() || 'dev'; const fromEmail = readEnv('CONTACT_FORM_FROM_EMAIL', runtimeEnv); const toEmail = readEnv('CONTACT_FORM_TO_EMAIL', runtimeEnv); const turnstileSecret = readEnv('CONTACT_TURNSTILE_SECRET_KEY', runtimeEnv); const form = await request.formData(); if (required(form.get('_gotcha'))) { const redirectTarget = sanitizeRedirect(required(form.get('_next')), url) || sanitizeRedirect(request.headers.get('referer') ?? '/', url) || new URL('/#contatti', url).toString(); return succeed(request, redirectTarget); } const redirectTarget = sanitizeRedirect(required(form.get('_next')), url) || sanitizeRedirect(readEnv('PUBLIC_CONTACT_FORM_SUCCESS_REDIRECT', runtimeEnv), url) || sanitizeRedirect(request.headers.get('referer') ?? '/', url) || new URL('/#contatti', url).toString(); const name = required(form.get('name')); const company = required(form.get('company')); const email = required(form.get('email')); const country = required(form.get('country')); const department = required(form.get('department')) || 'info'; const message = required(form.get('message')); if (!name || !email || !country || !message) { return fail(request, 'Missing required contact fields.', 400); } const turnstileToken = required(form.get('cf-turnstile-response')); if (turnstileSecret) { const isHuman = await verifyTurnstile( turnstileToken, turnstileSecret, request.headers.get('CF-Connecting-IP'), ); if (!isHuman) { return fail(request, 'Turnstile verification failed.', 400); } } const subject = required(form.get('_subject')) || 'Richiesta contatto - sito NexStudio'; const text = [ `Nome: ${name}`, company ? `Azienda: ${company}` : '', `Email: ${email}`, `Paese: ${country}`, `Reparto: ${department}`, '', message, ] .filter(Boolean) .join('\n'); if (mode === 'live') { const emailBinding = getEmailBinding(runtimeEnv); if (!emailBinding || !fromEmail || !toEmail) { return fail( request, 'Live mode requires EMAIL binding (Cloudflare Email Service), CONTACT_FORM_FROM_EMAIL and CONTACT_FORM_TO_EMAIL.', 500, ); } const destination = resolveDepartmentRecipient(runtimeEnv, toEmail, department); if (!destination) { return fail(request, 'Nessun destinatario configurato per il reparto selezionato.', 500); } try { await sendCloudflareEmail(emailBinding, { fromEmail, toEmail: destination, subject, text, replyTo: email, }); } catch (error) { console.error('[contact] email failed', error); // Retry without replyTo: some payloads reject Reply-To even when From/To are valid. try { await sendCloudflareEmail(emailBinding, { fromEmail, toEmail: destination, subject, text, }); } catch (retryError) { console.error('[contact] email retry failed', retryError); return fail(request, emailErrorMessage(retryError), 502); } } } else { console.info('[contact:dev] Contact payload received', { name, company, email, country, department, subject, }); } return succeed(request, redirectTarget); } catch (error) { console.error('[contact] unexpected error', error); return fail(request, 'Errore interno. Riprova più tardi.', 500); } };