import type { APIRoute } from 'astro'; import { getLongevityDb } from '../../../lib/longevity/db'; import { codicePerUtenteLoggato } from '../../../lib/longevity/anagrafica'; import { salvaDalForm } from '../../../lib/longevity/vista'; import { CAMPI_LIBERI } from '../../../lib/longevity/questionario'; export const prerender = false; const json = (status: number, body: object) => new Response(JSON.stringify(body), { status, headers: { 'Content-Type': 'application/json' } }); /** * Il corpo legittimo della richiesta: SOLO i campi che un cliente può davvero mandare. * `client_code` non è un campo di questo tipo — non per una lista di nomi vietati (una * lista così invecchia appena il file rinomina la variabile, vedi il commento nel test), * ma perché il tipo non lo prevede: chi scrivesse domani `corpo.client_code` non * supererebbe `tsc --noEmit`, il campo non esiste. Il codice cliente resta SEMPRE quello * della sessione (codicePerUtenteLoggato, spec §8), mai un campo letto da qui. */ type CorpoQuestionario = { risposte: Record; liberi: Record; consensoSanitario: string; eta?: number; }; /** Estrae SOLO i campi di CorpoQuestionario da un JSON grezzo; `null` se il corpo non è * nemmeno un oggetto, o se manca `risposte`. Ogni altra chiave del corpo (incluso un * eventuale `client_code`) viene semplicemente ignorata: non esiste modo di farla arrivare * al chiamante, perché il tipo di ritorno non la contiene. */ function corpoDaJson(raw: unknown): CorpoQuestionario | null { if (typeof raw !== 'object' || raw === null) return null; const grezzo = raw as Record; const risposteIn = grezzo.risposte; if (typeof risposteIn !== 'object' || risposteIn === null) return null; const risposte: Record = {}; for (const [id, v] of Object.entries(risposteIn as Record)) { if (typeof v === 'number' && Number.isFinite(v)) risposte[id] = v; } // Solo i campi liberi dichiarati: un corpo con chiavi arbitrarie non ne inventa di nuove. const liberiIn = grezzo.liberi; const liberi: Record = {}; if (typeof liberiIn === 'object' && liberiIn !== null) { for (const campo of CAMPI_LIBERI) { const v = (liberiIn as Record)[campo]; if (typeof v === 'string') liberi[campo] = v; } } const consensoSanitario = typeof grezzo.consensoSanitario === 'string' ? grezzo.consensoSanitario : ''; const eta = typeof grezzo.eta === 'number' && Number.isFinite(grezzo.eta) ? grezzo.eta : undefined; return { risposte, liberi, consensoSanitario, eta }; } export const POST: APIRoute = async ({ request, locals }) => { // Il codice cliente si ricava SEMPRE dalla sessione dell'utente loggato (spec §8), mai dal // corpo della richiesta: la risoluzione passa da anagrafica.ts (l'unico modulo autorizzato // a toccare quella connessione). `corpo`, qui sotto, non ha nemmeno un campo da cui // prenderlo per errore. const codice = codicePerUtenteLoggato(locals.user!.id); if (!codice) return json(403, { error: 'Nessun fascicolo cliente associato a questo utente.' }); let raw: unknown; try { raw = await request.json(); } catch { return json(400, { error: 'Dati non validi.' }); } if (typeof raw !== 'object' || raw === null) return json(400, { error: 'Dati non validi.' }); const corpo = corpoDaJson(raw); if (!corpo) return json(400, { error: 'Risposte mancanti.' }); const { risposte, liberi, consensoSanitario, eta } = corpo; // La data della compilazione è quella del server, non quella (manomettibile) del client. const oggi = new Date().toISOString().slice(0, 10); // salvaDalForm delega a salvaCompilazione, che lancia se una risposta porta un test_id // sconosciuto al registro (client disallineato, non serve malizia): un'eccezione qui // non deve diventare un 500 generico, come in src/pages/api/contact.ts per l'invio email. let esito; try { esito = salvaDalForm(getLongevityDb(), { client_code: codice, data: oggi, eta, risposte, consensoSanitario, liberi, }); } catch (err) { console.error('Salvataggio questionario fallito:', err); return json(400, { error: 'Impossibile salvare: alcune risposte non sono riconosciute. Ricarica la pagina e riprova.', }); } if (!esito.ok) return json(422, { error: esito.errore }); return json(201, { sessioneId: esito.sessioneId }); };