Rende pubblica a tutta la squadra la data di nascita inserita dal Profilo

Prima veniva mostrata solo per i 17 giocatori del seed storico: chi la
inseriva da sé restava con "Invalid Date" in Squadra, perché il dato
finiva solo in profili_giocatore (privato). Un trigger ora lo sincronizza
in giocatori_squadra.nascita, colonna pubblica a tutta la rosa.

Include il backfill dei profili già compilati prima di M18 (M19),
mancante nel push iniziale.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 5 committed 2026-09-15 12:18:01 +02:00
1 parent 4d036f532d
commit 3f2ca30775
14 files changed
+471 -35

No files matched your search

@@ -0,0 +1,124 @@
-- M18 — Data di nascita pubblica a tutta la squadra (DD-031)
--
-- In "Squadra" la data di nascita mostrava "Invalid Date" per chi l'aveva inserita da solo
-- dal proprio Profilo: quel valore finisce in `profili_giocatore.data_nascita`, tabella con
-- RLS "solo il proprio profilo o admin" (M2), mai letta da `useRosa()`/`useAnagraficaRosa()`
-- (src/lib/rosa.ts). Quelle funzioni leggevano solo la mappa statica `nascitaPerId` del seed
-- storico dei 17 giocatori originali (src/lib/crapp-data.ts) — pubblica perché bundlata nel
-- client, ma senza voce per chi si è aggiunto dopo.
--
-- Questa migration aggiunge la colonna pubblica prevista fin da DD-015 ("giocatori_squadra
-- non ha ancora questa colonna, DD-015 follow-up") e la tiene sincronizzata con
-- `profili_giocatore.data_nascita`: un solo input nel form Profilo, due destinazioni — quella
-- amministrativa esistente (RLS invariata) e questo specchio pubblico, leggibile da tutta la
-- rosa attiva con la stessa policy di sempre.
ALTER TABLE public.giocatori_squadra ADD COLUMN nascita date;
-- Backfill una tantum dei 17 giocatori storici (stessi valori di `rosaCSI` in
-- src/lib/crapp-data.ts, stesso ordine g1..g17 del seed di M1). Il trigger
-- `enforce_giocatori_squadra_update` (attivo dalla migration M1) rifiuterebbe questi UPDATE:
-- gira in un contesto senza `auth.uid()`, quindi non passa né il ramo admin né quello di
-- reclamazione. Va disabilitato solo per il backfill e riattivato subito dopo.
ALTER TABLE public.giocatori_squadra DISABLE TRIGGER enforce_giocatori_squadra_update;
UPDATE public.giocatori_squadra SET nascita = '1997-08-30' WHERE id = 'g1';
UPDATE public.giocatori_squadra SET nascita = '1996-12-07' WHERE id = 'g2';
UPDATE public.giocatori_squadra SET nascita = '2000-04-28' WHERE id = 'g3';
UPDATE public.giocatori_squadra SET nascita = '1995-05-01' WHERE id = 'g4';
UPDATE public.giocatori_squadra SET nascita = '1995-08-13' WHERE id = 'g5';
UPDATE public.giocatori_squadra SET nascita = '1996-02-01' WHERE id = 'g6';
UPDATE public.giocatori_squadra SET nascita = '1997-07-04' WHERE id = 'g7';
UPDATE public.giocatori_squadra SET nascita = '1994-05-28' WHERE id = 'g8';
UPDATE public.giocatori_squadra SET nascita = '2003-04-04' WHERE id = 'g9';
UPDATE public.giocatori_squadra SET nascita = '1998-11-30' WHERE id = 'g10';
UPDATE public.giocatori_squadra SET nascita = '2000-02-02' WHERE id = 'g11';
UPDATE public.giocatori_squadra SET nascita = '1999-10-03' WHERE id = 'g12';
UPDATE public.giocatori_squadra SET nascita = '2000-09-16' WHERE id = 'g13';
UPDATE public.giocatori_squadra SET nascita = '1996-12-13' WHERE id = 'g14';
UPDATE public.giocatori_squadra SET nascita = '1993-03-24' WHERE id = 'g15';
UPDATE public.giocatori_squadra SET nascita = '2001-04-18' WHERE id = 'g16';
UPDATE public.giocatori_squadra SET nascita = '1994-05-20' WHERE id = 'g17';
ALTER TABLE public.giocatori_squadra ENABLE TRIGGER enforce_giocatori_squadra_update;
-- Il trigger di sync (sotto) è SECURITY DEFINER e bypassa la RLS in scrittura su
-- giocatori_squadra (di proprietà dell'owner della tabella), ma `enforce_giocatori_squadra_update`
-- resta un trigger BEFORE UPDATE che scatta comunque: senza questo ramo, l'update annidato
-- fallirebbe con l'eccezione già in vigore per chi non è admin.
--
-- Non apre una scrittura diretta dal client: la policy di update per un non-admin resta
-- "Users can claim unassigned roster slot" (USING auth_user_id IS NULL) — una riga già
-- collegata non la supera nemmeno, quindi l'unica strada che raggiunge questo ramo è la
-- funzione di sync qui sotto, che gira con privilegi propri e ignora la RLS.
CREATE OR REPLACE FUNCTION public.enforce_giocatori_squadra_update()
RETURNS TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
BEGIN
IF public.has_role(auth.uid(), 'admin'::public.app_role) THEN
RETURN NEW;
END IF;
IF OLD.auth_user_id IS NULL
AND NEW.auth_user_id = auth.uid()
AND OLD.email IS NOT NULL
AND lower(OLD.email) = lower(coalesce(auth.jwt() ->> 'email', ''))
AND NEW.id IS NOT DISTINCT FROM OLD.id
AND NEW.nome IS NOT DISTINCT FROM OLD.nome
AND NEW.cognome IS NOT DISTINCT FROM OLD.cognome
AND NEW.numero IS NOT DISTINCT FROM OLD.numero
AND NEW.ruolo IS NOT DISTINCT FROM OLD.ruolo
AND NEW.attivo IS NOT DISTINCT FROM OLD.attivo
AND NEW.email IS NOT DISTINCT FROM OLD.email
AND NEW.numero_tessera IS NOT DISTINCT FROM OLD.numero_tessera
AND NEW.data_tessera IS NOT DISTINCT FROM OLD.data_tessera
AND NEW.creato_il IS NOT DISTINCT FROM OLD.creato_il THEN
RETURN NEW;
END IF;
-- Slot già collegato al chiamante: può cambiare solo `nascita` (M18). È la via percorsa
-- dal trigger di sync qui sotto, mai da un client (vedi commento sopra).
IF OLD.auth_user_id IS NOT NULL
AND OLD.auth_user_id = auth.uid()
AND NEW.auth_user_id IS NOT DISTINCT FROM OLD.auth_user_id
AND NEW.id IS NOT DISTINCT FROM OLD.id
AND NEW.nome IS NOT DISTINCT FROM OLD.nome
AND NEW.cognome IS NOT DISTINCT FROM OLD.cognome
AND NEW.numero IS NOT DISTINCT FROM OLD.numero
AND NEW.ruolo IS NOT DISTINCT FROM OLD.ruolo
AND NEW.attivo IS NOT DISTINCT FROM OLD.attivo
AND NEW.email IS NOT DISTINCT FROM OLD.email
AND NEW.numero_tessera IS NOT DISTINCT FROM OLD.numero_tessera
AND NEW.data_tessera IS NOT DISTINCT FROM OLD.data_tessera
AND NEW.creato_il IS NOT DISTINCT FROM OLD.creato_il THEN
RETURN NEW;
END IF;
RAISE EXCEPTION 'Aggiornamento non autorizzato su giocatori_squadra';
END;
$$;
CREATE OR REPLACE FUNCTION public.sincronizza_nascita_pubblica()
RETURNS TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
BEGIN
IF TG_OP = 'DELETE' THEN
UPDATE public.giocatori_squadra SET nascita = NULL WHERE id = OLD.giocatore_id;
RETURN OLD;
END IF;
UPDATE public.giocatori_squadra SET nascita = NEW.data_nascita WHERE id = NEW.giocatore_id;
RETURN NEW;
END;
$$;
CREATE TRIGGER sincronizza_nascita_pubblica
AFTER INSERT OR UPDATE OR DELETE ON public.profili_giocatore
FOR EACH ROW
EXECUTE FUNCTION public.sincronizza_nascita_pubblica();
@@ -0,0 +1,22 @@
-- M19 — Backfill della nascita pubblica per i profili già esistenti (fix di M18)
--
-- M18 ha aggiunto `giocatori_squadra.nascita` e un trigger che la sincronizza da
-- `profili_giocatore.data_nascita`, ma quel trigger scatta solo sulle scritture *future*:
-- chi aveva già compilato il proprio profilo (data_nascita) prima che M18 fosse applicata è
-- rimasto con `nascita` vuota, mentre M18 aveva backfillato a mano solo i 17 giocatori del
-- seed storico. Effetto visibile: in Squadra la data di nascita compariva solo per quei 17,
-- non per chi l'aveva inserita dal Profilo prima di oggi.
--
-- Stesso motivo di M18 per disabilitare temporaneamente `enforce_giocatori_squadra_update`:
-- gira in un contesto senza `auth.uid()`, quindi non passerebbe nessun ramo del trigger.
ALTER TABLE public.giocatori_squadra DISABLE TRIGGER enforce_giocatori_squadra_update;
-- Ha la precedenza sui valori hardcoded di M18 per chi, tra i 17 storici, ha anche corretto
-- la propria data dal Profilo nel frattempo: `profili_giocatore` è la fonte più aggiornata.
UPDATE public.giocatori_squadra gs
SET nascita = pg.data_nascita
FROM public.profili_giocatore pg
WHERE pg.giocatore_id = gs.id;
ALTER TABLE public.giocatori_squadra ENABLE TRIGGER enforce_giocatori_squadra_update;
+8
View File
@@ -5,6 +5,12 @@
-- I path dei file puntano a oggetti che nel bucket non esistono: i pulsanti di download
-- falliscono finché non carichi qualcosa dall'app o dallo Studio (http://127.0.0.1:54323).
-- Il trigger di sync M18 (`sincronizza_nascita_pubblica`) aggiorna `giocatori_squadra.nascita`
-- appena questo INSERT tocca `data_nascita`, ma qui gira senza un JWT reale: né il ramo admin
-- né quello del proprietario dello slot del trigger di sicurezza passerebbero. Va disabilitato
-- solo per questo seed, mai un problema in produzione dove il seed non gira.
ALTER TABLE public.giocatori_squadra DISABLE TRIGGER enforce_giocatori_squadra_update;
INSERT INTO public.profili_giocatore
(giocatore_id, data_nascita, luogo_nascita, indirizzo, telefono, email,
documento_tipo, documento_numero, documento_rilasciato_da,
@@ -29,6 +35,8 @@ VALUES
'2027-09-15', 'g2/certificato.pdf', NULL)
ON CONFLICT (giocatore_id) DO NOTHING;
ALTER TABLE public.giocatori_squadra ENABLE TRIGGER enforce_giocatori_squadra_update;
-- Il primo amministratore non si può seminare qui: `user_roles.user_id` punta a un utente
-- di `auth.users`, che su un database appena creato non esiste ancora. Dopo il primo login
-- (in locale come in produzione) basta una riga: