Pulisce a cascata i dati collegati quando un evento viene cancellato (DD-029)
Un audit del modulo Obiettivi ha verificato che gli obiettivi non hanno bisogno di pulizia: sono ricalcolati a runtime sull'elenco eventi corrente. Il problema reale era un livello sotto: cancellare un evento lasciava orfane le righe collegate in 8 tabelle (risposte_presenze, cacche_partita, mvp_voti, pagelle_voti, badge_social_voti, turni_palloni, scout_sessioni, scout_live, scout_partite), nessuna con una foreign key verso eventi_app. Aggiunge un trigger AFTER DELETE su eventi_app (migration m14_pulizia_dati_evento_cancellato) invece di una FK con CASCADE, non applicabile per dati storici disallineati (match_id che a volte punta a id Scout/CSI). Verificato con un nuovo test di integrazione contro il database locale; documentato in DESIGN_DECISIONS.md, DATABASE.md, obiettivi-squadra.md, test/README.md e CHANGELOG.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -80,6 +80,10 @@ Prima versione, pre-release.
|
|||||||
- L'MVP di una partita richiede ora un quorum minimo di 2 voti totali (`VOTI_MINIMI_MVP`)
|
- L'MVP di una partita richiede ora un quorum minimo di 2 voti totali (`VOTI_MINIMI_MVP`)
|
||||||
oltre al margine netto già richiesto: un solo voto non assegna più la vittoria (DD-028).
|
oltre al margine netto già richiesto: un solo voto non assegna più la vittoria (DD-028).
|
||||||
Alcuni conteggi `mvp` già mostrati possono scendere per effetto della nuova regola.
|
Alcuni conteggi `mvp` già mostrati possono scendere per effetto della nuova regola.
|
||||||
|
- Cancellare un evento pulisce ora a cascata, tramite trigger database, tutte le tabelle
|
||||||
|
collegate (presenze, pagelle, MVP, badge social, turni palloni, scout) invece di lasciarle
|
||||||
|
come righe orfane (migration `m14_pulizia_dati_evento_cancellato`, DD-029). Le righe orfane
|
||||||
|
generate da cancellazioni precedenti a questa migration non vengono bonificate.
|
||||||
|
|
||||||
### Sicurezza
|
### Sicurezza
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -46,7 +46,7 @@ La tabella è verificata da `test/integration/permessi.test.ts` contro il databa
|
|||||||
|
|
||||||
| Tabella | Scopo | Note |
|
| Tabella | Scopo | Note |
|
||||||
| ------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `eventi_app` | Eventi gestionali utilizzati dall'app. | Modello in uso dal codice attuale. |
|
| `eventi_app` | Eventi gestionali utilizzati dall'app. | Modello in uso dal codice attuale. Cancellare un evento pulisce a cascata, tramite trigger, tutte le tabelle collegate elencate in questa pagina (`risposte_presenze`, `cacche_partita`, `mvp_voti`, `pagelle_voti`, `badge_social_voti`, `turni_palloni`, `scout_sessioni`, `scout_live`, `scout_partite`) — migration `m14_pulizia_dati_evento_cancellato`, DD-029. |
|
||||||
| `risposte_presenze` | Risposte dei giocatori agli eventi. | Modello in uso dal codice attuale. `risposto_il` è l'istante della **prima** risposta (migration `m9_risposte_presenze_risposto_il`): confrontato con `eventi_app.creato_il` dà la serie "Conferme 24h". Un trigger lo rende immutabile, così un ripensamento non fa risultare rapida una risposta lenta — `aggiornato_il` resta l'ultima modifica. |
|
| `risposte_presenze` | Risposte dei giocatori agli eventi. | Modello in uso dal codice attuale. `risposto_il` è l'istante della **prima** risposta (migration `m9_risposte_presenze_risposto_il`): confrontato con `eventi_app.creato_il` dà la serie "Conferme 24h". Un trigger lo rende immutabile, così un ripensamento non fa risultare rapida una risposta lenta — `aggiornato_il` resta l'ultima modifica. |
|
||||||
| `eventi` | Calendario generale: allenamenti, partite, eventi della squadra. | Modello "nuovo" con autenticazione e vincoli, non ancora adottato (DD-014). |
|
| `eventi` | Calendario generale: allenamenti, partite, eventi della squadra. | Modello "nuovo" con autenticazione e vincoli, non ancora adottato (DD-014). |
|
||||||
| `presenze` | Presenze agli eventi. | Come sopra (DD-014). |
|
| `presenze` | Presenze agli eventi. | Come sopra (DD-014). |
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ Serve a rispondere a domande del tipo:
|
|||||||
| [DD-026](#dd-026--il-testo-della-notifica-viaggia-dentro-la-push) | Payload push cifrato |
|
| [DD-026](#dd-026--il-testo-della-notifica-viaggia-dentro-la-push) | Payload push cifrato |
|
||||||
| [DD-027](#dd-027--chi-vota-deve-essere-convocato-non-solo-autenticato-come-sé-stesso) | Voto limitato ai convocati |
|
| [DD-027](#dd-027--chi-vota-deve-essere-convocato-non-solo-autenticato-come-sé-stesso) | Voto limitato ai convocati |
|
||||||
| [DD-028](#dd-028--soglia-minima-di-campione-per-media-voto-e-mvp-in-home) | Soglia minima Media voto e MVP |
|
| [DD-028](#dd-028--soglia-minima-di-campione-per-media-voto-e-mvp-in-home) | Soglia minima Media voto e MVP |
|
||||||
|
| [DD-029](#dd-029--cancellare-un-evento-pulisce-a-cascata-i-dati-collegati) | Pulizia a cascata evento cancellato |
|
||||||
|
|
||||||
**In valutazione**
|
**In valutazione**
|
||||||
|
|
||||||
@@ -1076,3 +1077,60 @@ votasse perché il votato "vincesse" nettamente, senza nessun quorum di partecip
|
|||||||
**Riesame**
|
**Riesame**
|
||||||
Se la squadra segnala che il quorum di 2 voti per l'MVP è troppo permissivo o troppo severo, o
|
Se la squadra segnala che il quorum di 2 voti per l'MVP è troppo permissivo o troppo severo, o
|
||||||
se si vuole applicare la stessa soglia di Media voto anche alle StatTile di profilo e squadra.
|
se si vuole applicare la stessa soglia di Media voto anche alle StatTile di profilo e squadra.
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
**Riesame**
|
||||||
|
Se in futuro si vuole bonificare anche lo storico di righe orfane già esistenti, o 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).
|
||||||
|
|||||||
@@ -21,6 +21,14 @@ Nessuna tabella dedicata: ogni obiettivo è una funzione pura in `obiettivi.ts`
|
|||||||
`pagelle_voti`, i risultati ufficiali CSI, le serie di presenza). `obiettiviOrdinati()` li
|
`pagelle_voti`, i risultati ufficiali CSI, le serie di presenza). `obiettiviOrdinati()` li
|
||||||
ordina mettendo i completati in coda e gli altri per progresso decrescente.
|
ordina mettendo i completati in coda e gli altri per progresso decrescente.
|
||||||
|
|
||||||
|
Non c'è nessuno stato da tenere sincronizzato quando un evento viene cancellato: gli obiettivi
|
||||||
|
sono ricalcolati da zero a ogni render partendo dall'elenco eventi corrente, quindi un evento
|
||||||
|
sparito da `eventi_app` smette semplicemente di contare, senza bisogno di nessuna pulizia
|
||||||
|
esplicita. Il problema che *sembrava* riguardare gli obiettivi era in realtà nelle tabelle
|
||||||
|
collegate a un evento (presenze, pagelle, MVP, ecc.), che restavano orfane a database dopo la
|
||||||
|
cancellazione: risolto a livello database con un trigger (migration
|
||||||
|
`m14_pulizia_dati_evento_cancellato`, DD-029), non nel modulo Obiettivi.
|
||||||
|
|
||||||
`obiettiviSquadra(rosa, ctx, oggi)` accetta un terzo parametro opzionale `oggi: Date` (default
|
`obiettiviSquadra(rosa, ctx, oggi)` accetta un terzo parametro opzionale `oggi: Date` (default
|
||||||
`new Date()`) per iniettare una data deterministica nei test — usato dai due obiettivi con mese
|
`new Date()`) per iniettare una data deterministica nei test — usato dai due obiettivi con mese
|
||||||
corrente dinamico (vedi sotto).
|
corrente dinamico (vedi sotto).
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
-- M14 — Pulizia a cascata dei dati collegati quando un evento viene cancellato
|
||||||
|
--
|
||||||
|
-- Un audit del modulo Obiettivi ha verificato che gli obiettivi in sé non hanno bisogno di
|
||||||
|
-- nessuna pulizia: sono ricalcolati a runtime sull'elenco eventi corrente (`obiettivi.ts`), e
|
||||||
|
-- un evento cancellato semplicemente sparisce da quell'elenco. Il problema è un livello sotto:
|
||||||
|
-- `useEliminaEvento()` (`src/lib/eventi.ts`) cancella solo la riga in `eventi_app`, lasciando
|
||||||
|
-- orfane le righe collegate in `risposte_presenze`, `cacche_partita`, `mvp_voti`,
|
||||||
|
-- `pagelle_voti`, `badge_social_voti`, `turni_palloni`, `scout_sessioni`, `scout_live` e
|
||||||
|
-- `scout_partite` — nessuna di queste ha mai avuto una foreign key verso `eventi_app(id)`.
|
||||||
|
--
|
||||||
|
-- Una FK con ON DELETE CASCADE non è applicabile oggi: `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 rifiuterebbe la
|
||||||
|
-- migration alla prima riga storica disallineata. Un trigger non valida i dati esistenti,
|
||||||
|
-- solo le cancellazioni da qui in avanti, quindi è applicabile senza bonificare prima lo
|
||||||
|
-- storico (bonifica che resta un lavoro separato, se mai servirà).
|
||||||
|
--
|
||||||
|
-- SECURITY DEFINER: le policy DELETE di alcune tabelle collegate potrebbero in futuro
|
||||||
|
-- restringersi (oggi sono tutte aperte, DD-023); il trigger deve continuare a pulire a
|
||||||
|
-- prescindere da chi ha eseguito la DELETE su eventi_app.
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.pulisci_dati_evento_cancellato()
|
||||||
|
RETURNS trigger
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
SECURITY DEFINER
|
||||||
|
SET search_path = public
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
DELETE FROM public.risposte_presenze WHERE evento_id = OLD.id;
|
||||||
|
DELETE FROM public.cacche_partita WHERE evento_id = OLD.id;
|
||||||
|
DELETE FROM public.mvp_voti WHERE match_id = OLD.id;
|
||||||
|
DELETE FROM public.pagelle_voti WHERE match_id = OLD.id;
|
||||||
|
DELETE FROM public.badge_social_voti WHERE match_id = OLD.id;
|
||||||
|
DELETE FROM public.turni_palloni WHERE evento_id = OLD.id;
|
||||||
|
DELETE FROM public.scout_sessioni WHERE evento_id = OLD.id;
|
||||||
|
DELETE FROM public.scout_live WHERE evento_id = OLD.id;
|
||||||
|
DELETE FROM public.scout_partite WHERE evento_id = OLD.id;
|
||||||
|
RETURN OLD;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CREATE TRIGGER eventi_app_pulisci_dati_collegati
|
||||||
|
AFTER DELETE ON public.eventi_app
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION public.pulisci_dati_evento_cancellato();
|
||||||
+2
-1
@@ -26,7 +26,7 @@ la consegna effettiva a schermo bloccato richiede un telefono e il servizio push
|
|||||||
| Cartella | Cosa verifica | Serve rete? |
|
| Cartella | Cosa verifica | Serve rete? |
|
||||||
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
|
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
|
||||||
| `unit/` | Logica di dominio pura: badge, serie, palloni, pagelle, MVP, cacche, scout, obiettivi, notifiche, parsing CSI, dati della rosa. Più le funzioni pure isolabili nei moduli con hook/rete (validazione upload, guardie push, JWT VAPID, cattura errori, avatar, guardia admin delle route) | No |
|
| `unit/` | Logica di dominio pura: badge, serie, palloni, pagelle, MVP, cacche, scout, obiettivi, notifiche, parsing CSI, dati della rosa. Più le funzioni pure isolabili nei moduli con hook/rete (validazione upload, guardie push, JWT VAPID, cattura errori, avatar, guardia admin delle route) | No |
|
||||||
| `integration/` | Le route `/api/public/*` sul server di sviluppo: risposte, cache, validazione degli input. Più schema e permessi del Profilo Giocatore (`schema-profili`) contro il database configurato; permessi per ruolo, incluse le policy di M11, le deroghe dell'amministratore e le tabelle lasciate aperte di proposito (`permessi`), accesso alle route di notifica (`permessi-route`), semantica degli upsert e vincoli (`scritture`) e le letture lato server delle route push (`lettori-server`) sul database locale | Sì |
|
| `integration/` | Le route `/api/public/*` sul server di sviluppo: risposte, cache, validazione degli input. Più schema e permessi del Profilo Giocatore (`schema-profili`) contro il database configurato; permessi per ruolo, incluse le policy di M11, le deroghe dell'amministratore e le tabelle lasciate aperte di proposito (`permessi`), accesso alle route di notifica (`permessi-route`), semantica degli upsert e vincoli (`scritture`), le letture lato server delle route push (`lettori-server`) e la pulizia a cascata dei dati collegati alla cancellazione di un evento (`pulizia-evento`, M14) sul database locale | Sì |
|
||||||
| `e2e/` | Percorsi completi sull'app servita: schermate, dati CSI fino alla pagina, file PWA, 404 | Sì |
|
| `e2e/` | Percorsi completi sull'app servita: schermate, dati CSI fino alla pagina, file PWA, 404 | Sì |
|
||||||
| `helpers/` | Avvio del server di test e mini-harness condiviso | — |
|
| `helpers/` | Avvio del server di test e mini-harness condiviso | — |
|
||||||
|
|
||||||
@@ -46,6 +46,7 @@ bun test/integration/permessi.test.ts # permessi per ruolo sulle tabelle
|
|||||||
bun test/integration/permessi-route.test.ts # chi può far partire le notifiche
|
bun test/integration/permessi-route.test.ts # chi può far partire le notifiche
|
||||||
bun test/integration/scritture.test.ts # semantica degli upsert e vincoli
|
bun test/integration/scritture.test.ts # semantica degli upsert e vincoli
|
||||||
bun test/integration/lettori-server.test.ts # le letture server delle route push
|
bun test/integration/lettori-server.test.ts # le letture server delle route push
|
||||||
|
bun test/integration/pulizia-evento.test.ts # cascata alla cancellazione di un evento (M14)
|
||||||
```
|
```
|
||||||
|
|
||||||
`permessi-route` avvia il server di sviluppo **puntato al database locale** invece che al
|
`permessi-route` avvia il server di sviluppo **puntato al database locale** invece che al
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
/**
|
||||||
|
* Pulizia a cascata dei dati collegati alla cancellazione di un evento (M14):
|
||||||
|
* `bun test/integration/pulizia-evento.test.ts`.
|
||||||
|
*
|
||||||
|
* Scrive una riga per ciascuna delle 9 tabelle collegate a un evento (`risposte_presenze`,
|
||||||
|
* `cacche_partita`, `mvp_voti`, `pagelle_voti`, `badge_social_voti`, `turni_palloni`,
|
||||||
|
* `scout_sessioni`, `scout_live`, `scout_partite`), cancella l'evento e verifica che il
|
||||||
|
* trigger `eventi_app_pulisci_dati_collegati` le abbia rimosse tutte. Prima di M14 queste
|
||||||
|
* righe restavano orfane a database (`docs/DESIGN_DECISIONS.md`, DD-029).
|
||||||
|
*
|
||||||
|
* Gira solo sullo stack locale (`npx supabase start`): usa id con il prefisso
|
||||||
|
* `test-pulizia-evento`, che nessun dato vero può avere.
|
||||||
|
*/
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { statoLocale } from "../helpers/locale";
|
||||||
|
import { prova, riepilogo, salta } from "../helpers/prova";
|
||||||
|
|
||||||
|
const locale = statoLocale();
|
||||||
|
|
||||||
|
if (!locale) {
|
||||||
|
salta("pulizia dati evento cancellato", "stack locale non attivo (npx supabase start)");
|
||||||
|
riepilogo("pulizia-evento");
|
||||||
|
} else {
|
||||||
|
const { url: URL_BASE, servizio: SERVIZIO } = locale;
|
||||||
|
console.log(`pulizia dati evento cancellato su ${URL_BASE}`);
|
||||||
|
|
||||||
|
const ID_EVENTO = "test-pulizia-evento-e1";
|
||||||
|
|
||||||
|
const rest = (percorso: string, init?: RequestInit) =>
|
||||||
|
fetch(`${URL_BASE}/rest/v1/${percorso}`, {
|
||||||
|
...init,
|
||||||
|
headers: {
|
||||||
|
apikey: SERVIZIO,
|
||||||
|
Authorization: `Bearer ${SERVIZIO}`,
|
||||||
|
"content-type": "application/json",
|
||||||
|
Prefer: "return=minimal",
|
||||||
|
...(init?.headers ?? {}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
async function inserisci(tabella: string, riga: Record<string, unknown>) {
|
||||||
|
const res = await rest(tabella, { method: "POST", body: JSON.stringify(riga) });
|
||||||
|
if (!res.ok) throw new Error(`insert su ${tabella}: ${res.status} ${await res.text()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function conta(tabella: string, filtro: string): Promise<number> {
|
||||||
|
const res = await rest(`${tabella}?${filtro}&select=*`, {
|
||||||
|
headers: { Prefer: "count=exact" },
|
||||||
|
});
|
||||||
|
return Number(res.headers.get("content-range")?.split("/")[1] ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const TABELLE_EVENTO_ID = [
|
||||||
|
"risposte_presenze",
|
||||||
|
"cacche_partita",
|
||||||
|
"turni_palloni",
|
||||||
|
"scout_sessioni",
|
||||||
|
"scout_live",
|
||||||
|
"scout_partite",
|
||||||
|
];
|
||||||
|
const TABELLE_MATCH_ID = ["mvp_voti", "pagelle_voti", "badge_social_voti"];
|
||||||
|
|
||||||
|
async function pulisciTutto() {
|
||||||
|
await rest(`eventi_app?id=eq.${ID_EVENTO}`, { method: "DELETE" });
|
||||||
|
for (const t of TABELLE_EVENTO_ID) {
|
||||||
|
await rest(`${t}?evento_id=eq.${ID_EVENTO}`, { method: "DELETE" });
|
||||||
|
}
|
||||||
|
for (const t of TABELLE_MATCH_ID) {
|
||||||
|
await rest(`${t}?match_id=eq.${ID_EVENTO}`, { method: "DELETE" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await prova("cancellare un evento pulisce a cascata tutte le tabelle collegate", async () => {
|
||||||
|
await inserisci("eventi_app", {
|
||||||
|
id: ID_EVENTO,
|
||||||
|
tipo: "allenamento",
|
||||||
|
titolo: "Test pulizia",
|
||||||
|
data: "2026-09-01",
|
||||||
|
});
|
||||||
|
|
||||||
|
await inserisci("risposte_presenze", {
|
||||||
|
evento_id: ID_EVENTO,
|
||||||
|
giocatore_id: "test-g1",
|
||||||
|
stato: "presente",
|
||||||
|
});
|
||||||
|
await inserisci("cacche_partita", {
|
||||||
|
evento_id: ID_EVENTO,
|
||||||
|
giocatore_id: "test-g1",
|
||||||
|
quantita: 2,
|
||||||
|
});
|
||||||
|
await inserisci("turni_palloni", { evento_id: ID_EVENTO, giocatore_id: "test-g1" });
|
||||||
|
await inserisci("scout_sessioni", {
|
||||||
|
evento_id: ID_EVENTO,
|
||||||
|
giocatore_id: "test-g1",
|
||||||
|
giocatore_nome: "Uno",
|
||||||
|
});
|
||||||
|
await inserisci("scout_live", { evento_id: ID_EVENTO, stato: {} });
|
||||||
|
await inserisci("scout_partite", {
|
||||||
|
id: `${ID_EVENTO}-scout`,
|
||||||
|
evento_id: ID_EVENTO,
|
||||||
|
data: "2026-09-01",
|
||||||
|
avversario: "Test",
|
||||||
|
set_nostri: 3,
|
||||||
|
set_loro: 0,
|
||||||
|
});
|
||||||
|
await inserisci("mvp_voti", {
|
||||||
|
match_id: ID_EVENTO,
|
||||||
|
votante_id: "test-g1",
|
||||||
|
votato_id: "test-g2",
|
||||||
|
votato_nome: "Due",
|
||||||
|
});
|
||||||
|
await inserisci("pagelle_voti", {
|
||||||
|
match_id: ID_EVENTO,
|
||||||
|
votante_id: "test-g1",
|
||||||
|
votato_id: "test-g2",
|
||||||
|
voto: 7,
|
||||||
|
});
|
||||||
|
await inserisci("badge_social_voti", {
|
||||||
|
match_id: ID_EVENTO,
|
||||||
|
categoria: "top",
|
||||||
|
votante_id: "test-g1",
|
||||||
|
votato_id: "test-g2",
|
||||||
|
votato_nome: "Due",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tutte le righe esistono prima della cancellazione.
|
||||||
|
for (const t of TABELLE_EVENTO_ID) {
|
||||||
|
assert.equal(await conta(t, `evento_id=eq.${ID_EVENTO}`), 1, `${t}: riga presente`);
|
||||||
|
}
|
||||||
|
for (const t of TABELLE_MATCH_ID) {
|
||||||
|
assert.equal(await conta(t, `match_id=eq.${ID_EVENTO}`), 1, `${t}: riga presente`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await rest(`eventi_app?id=eq.${ID_EVENTO}`, { method: "DELETE" });
|
||||||
|
if (!res.ok) throw new Error(`delete evento: ${res.status} ${await res.text()}`);
|
||||||
|
|
||||||
|
// Il trigger deve aver ripulito tutte le righe collegate.
|
||||||
|
for (const t of TABELLE_EVENTO_ID) {
|
||||||
|
assert.equal(await conta(t, `evento_id=eq.${ID_EVENTO}`), 0, `${t}: pulita a cascata`);
|
||||||
|
}
|
||||||
|
for (const t of TABELLE_MATCH_ID) {
|
||||||
|
assert.equal(await conta(t, `match_id=eq.${ID_EVENTO}`), 0, `${t}: pulita a cascata`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
await pulisciTutto();
|
||||||
|
}
|
||||||
|
|
||||||
|
riepilogo("pulizia-evento");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user