Files
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

78 lines
5.0 KiB
Markdown

# 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).