Files
CRAPP/docs/decisions/DD-029.md
T
davideandClaude Sonnet 5.5 8f25143aa5 Riorganizza la documentazione per l'uso con assistenti AI
- Spezza il registro delle decisioni in docs/decisions/DD-XXX.md, con indice
- Ripulisce DATABASE.md dalla storia delle migration e aggiunge la sezione Lettura
- Riduce PROJECT_STATE.md a una pagina; la procedura di allestimento passa in OPERATIONS.md
- Aggiunge OPERATIONS.md, GLOSSARIO.md, trappole note e checklist in ARCHITECTURE.md
- Sintetizza la copertura test di badge.md e documenta docs/archive
- Allinea AGENTS.md, README.md e la regola Cursor (tolto l'obbligo di develop, in contrasto con DD-019)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 08:53:38 +02:00

5.0 KiB

DD-029 — Cancellare un evento pulisce a cascata i dati collegati

Data: 9 settembre 2026
Stato: Accettata

Contesto
Un audit del modulo Obiettivi ha verificato che gli obiettivi in sé non hanno bisogno di nessuna pulizia quando un evento viene cancellato: sono ricalcolati a runtime sull'elenco eventi corrente (obiettivi.ts), quindi un evento sparito da eventi_app semplicemente smette di contare. Il problema è un livello sotto: useEliminaEvento() (src/lib/eventi.ts) cancella solo la riga in eventi_app. Nessuna delle otto tabelle collegate (risposte_presenze, cacche_partita, mvp_voti, pagelle_voti, badge_social_voti, turni_palloni, scout_sessioni, scout_live, scout_partite) ha mai avuto una foreign key verso eventi_app(id): le loro righe restavano orfane a database.

Oggi è innocuo per le statistiche, perché nessun calcolo legge quelle tabelle se non partendo dall'elenco eventi corrente. Ma è un rischio latente: un id evento futuro identico a uno passato (generato come "e" + timestamp, collisione improbabile ma non impossibile) erediterebbe dati vecchi non suoi; e un calcolo futuro che iterasse direttamente una di quelle tabelle, invece di partire da eventi_app, conterebbe anche le righe orfane.

Decisione
Migration m14_pulizia_dati_evento_cancellato: un trigger AFTER DELETE ON eventi_app cancella a cascata le righe corrispondenti (evento_id/match_id = id evento cancellato) nelle otto tabelle collegate, tramite una funzione SECURITY DEFINER. Agisce a database, non in useEliminaEvento(): protegge anche chi cancella un evento scrivendo direttamente su PostgREST, non solo chi passa dal bottone dell'app.

Alternative scartate

  • Foreign key con ON DELETE CASCADE verso eventi_app(id) → non applicabile subito: mvp.md documenta che storicamente match_id in mvp_voti/pagelle_voti/ badge_social_voti a volte conteneva l'id di una sessione Scout o di una partita CSI, non l'id evento CrAPP. Un vincolo FK avrebbe rifiutato la migration alla prima riga storica disallineata; il trigger non valida i dati esistenti, solo le cancellazioni da qui in avanti.
  • Cancellazione manuale nelle otto tabelle dentro useEliminaEvento() → fragile: va tenuta aggiornata a mano ogni volta che un nuovo modulo aggiunge una tabella con evento_id, e non protegge chi scrive/cancella direttamente su PostgREST.
  • Bonificare anche le righe orfane già esistenti nella stessa migration → rimandato: tocca dati reali già scritti, è un intervento più delicato che merita una migration a sé, non urgente perché quelle righe sono già invisibili a ogni calcolo attuale.

Conseguenze

  • Da questa migration in poi, cancellare un evento (da qualunque punto, app o REST diretto) ripulisce automaticamente tutte le tabelle collegate.
  • Le righe orfane generate da cancellazioni precedenti a questa migration restano nel database: il trigger previene il problema da qui in avanti, non ripulisce lo storico.
  • test/integration/pulizia-evento.test.ts è la definizione eseguibile del comportamento: scrive una riga in ciascuna delle otto tabelle, cancella l'evento e verifica che spariscano tutte.

Aggiornamento (9 settembre 2026, stesso giorno) — la bonifica dello storico prevista sopra come "riesame" è stata fatta subito dopo, migration m15_bonifica_dati_evento_orfani: righe orfane in risposte_presenze, cacche_partita, turni_palloni, scout_sessioni, scout_live, scout_partite identificate confrontando evento_id con eventi_app. Per mvp_voti/pagelle_voti/badge_social_voti il confronto si applica solo ai match_id nel formato id evento CrAPP (^e[0-9a-z]+$, quello di nuovoIdEvento()): i vecchi voti su id Scout (prefisso s + timestamp decimale) o CSI (numerico o data-squadra-squadra) non sono orfani, sono dati storici legittimi mai collegati a un evento CrAPP (vedi contesto sopra), e la migration non li tocca. Verificato manualmente sul database locale prima di applicarla: un voto di test su id Scout è sopravvissuto alla bonifica, un voto di test su id evento CrAPP orfano è stato rimosso.

Aggiornamento (9 settembre 2026) — la verifica manuale di M15 non lasciava nessuna rete di sicurezza automatica per il futuro, a differenza del resto del progetto (DD-020). Migration m16_funzione_bonifica_dati_evento_orfani rende lo stesso corpo una funzione bonifica_dati_evento_orfani() (riservata al service_role, non richiamabile dall'app), coperta da test/integration/bonifica-evento.test.ts: inserisce una riga orfana e una storica su id Scout/CSI, richiama la funzione via RPC e verifica che tocchi solo la prima. Non serve richiamarla di nuovo ora (M15 ha già ripulito lo storico): resta pronta come intervento di manutenzione se in futuro il trigger di M14 smettesse di funzionare o emergesse un altro batch di orfani.

Riesame
Se una nuova tabella collegata a un evento non viene aggiunta al trigger quando creata (va aggiornata a mano, non c'è un meccanismo che lo forzi).