From cef08aaf9fb55f7fd4788239ac524adbaff911d8 Mon Sep 17 00:00:00 2001 From: "Adriano (daitarn)" Date: Sun, 27 Sep 2026 22:23:03 +0200 Subject: [PATCH] longevity: API giornale di bordo per cliente (aggiungi/cancella) POST unico con campo azione, autore e ruolo sempre da locals.user, redirect 303 alla scheda cliente #giornale. Refs #16 Co-Authored-By: Claude Sonnet 5 --- .../api/longevity/gestionale/giornale.ts | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 apps/platforms/src/pages/api/longevity/gestionale/giornale.ts diff --git a/apps/platforms/src/pages/api/longevity/gestionale/giornale.ts b/apps/platforms/src/pages/api/longevity/gestionale/giornale.ts new file mode 100644 index 0000000..716118a --- /dev/null +++ b/apps/platforms/src/pages/api/longevity/gestionale/giornale.ts @@ -0,0 +1,67 @@ +import type { APIRoute } from 'astro'; +import { getLongevityDb } from '../../../../lib/longevity/db'; +import { aggiungiVoce, cancellaVoce } from '../../../../lib/longevity/giornale'; + +export const prerender = false; + +const REGEX_CODE = /^ISL-\d{4}$/; + +// Riservato a admin+trainer da src/lib/auth.ts (RULES, /^\/api\/longevity\/gestionale(\/|$)/). + +/** + * Aggiunge o cancella una voce del giornale di bordo (#16, solo staff): un unico endpoint + * per le due azioni, come verifica.ts fa per conferma/scarto — un campo `azione` invece di + * due rotte. Autore e ruolo SEMPRE da `locals.user`, mai dal form (stesso principio di + * checkup.ts e verifica.ts: chi scrive la richiesta non decide chi ne è l'autore). + */ +export const POST: APIRoute = async ({ request, locals }) => { + const form = await request.formData(); + const db = getLongevityDb(); + const autore = locals.user!.username; + const ruolo = locals.user!.role; + + const azione = form.get('azione'); + + if (azione === 'aggiungi') { + const clientCode = String(form.get('client_code') ?? '').trim(); + // Senza un codice valido non c'è una scheda a cui tornare — stesso trattamento di + // checkup.ts: l'unico ritorno sensato è l'indice del gestionale. + if (!REGEX_CODE.test(clientCode)) { + return redirect(`/longevity/gestionale?errore=${encodeURIComponent('Codice cliente non valido.')}`); + } + + const data = String(form.get('data') ?? '').trim(); + const testo = String(form.get('testo') ?? ''); + + const esito = aggiungiVoce(db, { clientCode, data, autore, testo }); + if (!esito.ok) { + return tornaA(clientCode, `?errore=${encodeURIComponent(esito.errore)}`); + } + return tornaA(clientCode, '?giornale=aggiunta'); + } + + if (azione === 'cancella') { + const idRaw = form.get('id'); + const id = typeof idRaw === 'string' ? Number(idRaw) : NaN; + if (!Number.isInteger(id)) { + return redirect(`/longevity/gestionale?errore=${encodeURIComponent('Voce non valida.')}`); + } + + const esito = cancellaVoce(db, { id, utente: autore, ruolo }); + if (!esito.ok) { + // Nessuna voce da cui leggere il client_code (id inesistente, o non c'era già): non + // c'è una scheda su cui tornare, come per un id di misura inesistente in verifica.ts. + return redirect(`/longevity/gestionale?errore=${encodeURIComponent(esito.errore)}`); + } + return tornaA(esito.clientCode, '?giornale=cancellata'); + } + + return redirect(`/longevity/gestionale?errore=${encodeURIComponent('Azione non valida.')}`); +}; + +const redirect = (location: string): Response => new Response(null, { status: 303, headers: { Location: location } }); + +// Il redirect torna sempre alla sezione #giornale della scheda, dove il form e l'elenco +// vivono — così chi aggiunge o cancella una nota si ritrova esattamente dove era. +const tornaA = (clientCode: string, query: string): Response => + redirect(`/longevity/gestionale/cliente/${clientCode}${query}#giornale`);