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 <noreply@anthropic.com>
This commit is contained in:
@@ -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`);
|
||||
Reference in New Issue
Block a user