Files
CRAPP/docs/modules/palloni.md
T
davideandClaude Opus 5 847972b582 Chiede le credenziali alle route che avvisano tutta la squadra (DD-024).
Le route in src/routes/api/public/ girano con la service role e saltano la RLS,
quindi DD-023 non le copre. Nessuna faceva un controllo di accesso: cercando
"authorization" in quella cartella l'unico header era lo User-Agent con cui
csi.ts chiama il portale CSI. Chiunque conoscesse l'URL poteva far suonare i
telefoni della squadra, e promemoria-palloni accetta perfino una POST con il
corpo vuoto.

La difesa apparente delle altre due — serve un id evento valido — non è una
difesa: l'id è "e" più il timestamp in base 36, compare negli URL che la squadra
si scambia ed è elencabile da qualsiasi utente loggato.

auth-route.server.ts porta i due controlli, diversi perché i chiamanti sono
diversi. apri-sondaggio e sollecita-presenze usano richiediAdmin: token della
sessione verificato con auth.getUser, poi ruolo admin da user_roles, la stessa
fonte di ruoli.ts. Il controllo precede la validazione dell'input, così la
risposta non rivela nemmeno se un evento esiste. promemoria-palloni usa
richiediSegreto, perché la chiama un cron che una sessione non ce l'ha: se
CRON_SEGRETO non è configurata la route resta chiusa con 503, perché una porta
che si riapre da sola quando manca una variabile non se ne accorge nessuno.

csi, push-config, push-subscribe e push-messaggio restano aperte: le chiamano il
browser prima del login e il service worker, dove qualsiasi segreto finirebbe
nel bundle.

Lato client i due pulsanti admin mandano il token con intestazioniAutenticate(),
letto al momento della chiamata e non da uno stato React.

permessi-route.test.ts copre il giro intero — nessun token, giocatore, admin —
avviando il server di sviluppo puntato al database locale, perché servono utenti
veri. Il controllo positivo è il 404: l'admin supera l'accesso e arriva alla
validazione. In api.test.ts restano i rifiuti che non richiedono un utente e
sparisce la verifica della validazione di sollecita-presenze, che ora sta dietro
all'accesso.

I limiti noti di palloni.md sono aggiornati: il secret che il piano originale
prevedeva ora c'è. Resta vero che nessun cron chiama la route, quindi il
promemoria quotidiano non parte da solo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 12:05:39 +02:00

3.1 KiB

Modulo — Palloni

Stato: implementato (v1.0) File principali: src/lib/palloni.ts, src/lib/palloni-core.ts, src/components/crapp/TurnoPalloni.tsx, src/components/crapp/PromemoriaPalloni.tsx, src/routes/api/public/promemoria-palloni.ts


Obiettivo

Gestire un turno a rotazione condiviso per chi porta e riporta i palloni ad allenamenti e partite, con proposta automatica, possibilità di modifica manuale e promemoria push il giorno stesso.


Dati

Tabella turni_palloni (evento_id, giocatore_id, aggiornato_da, aggiornato_il) — contiene solo i turni confermati manualmente; le proposte automatiche non salvate non vi compaiono.


Implementazione

  • completaTurni() (palloni-core.ts) propone, per ogni partita o evento extra senza turno già salvato, il candidato con meno turni fatti, poi quello che non lo fa da più tempo, poi per ordine alfabetico — un algoritmo greedy, non un ordine fisso né solo per data. Gli allenamenti non ricevono proposta automatica: restano «da assegnare» finché qualcuno non sceglie un incaricato in TurnoPalloni (scelta della squadra).
  • useAssegnaTurno() (palloni.ts) conferma una proposta o riassegna manualmente, con upsert su evento_id.
  • Il conteggio "quante volte hai portato i palloni" mostrato nel profilo e nei badge è ricalcolato a runtime da conteggioTurni() su turni salvati più proposte non ancora confermate (partite/eventi) — non è uno storico in tabella dedicata.
  • TurnoPalloni.tsx mostra/assegna il turno sulla card di un evento; PromemoriaPalloni.tsx è il banner in Home per il giocatore di turno.

Route API pubblica /api/public/promemoria-palloni

Pensata per essere chiamata quotidianamente da uno scheduler esterno (pg_cron o simile, secondo docs/PORTABILITA.md), non da nessun componente client. Calcola i destinatari del giorno — chi deve prendere i palloni oggi e chi deve riportarli (l'incaricato dell'evento precedente) — e invia loro una push "vuota" (src/lib/webpush.server.ts); il testo effettivo viene calcolato al volo dal service worker interrogando /api/public/push-messaggio (vedi Notifiche).


Limiti noti

  • Nessun cron nel repository: lo scheduling effettivo (se esiste) è configurato fuori dal codice versionato — da verificare lato Supabase/hosting. Finché non esiste, il promemoria quotidiano non parte da solo.
  • La route è protetta dal segreto previsto dal piano originale (DD-024): chi la chiama deve mandare x-cron-segreto uguale alla variabile CRON_SEGRETO. Se la variabile non è configurata nell'ambiente la route risponde 503.
  • Il conteggio dei turni include anche le proposte non confermate: badge e statistiche possono contare turni mai effettivamente convalidati da nessuno.
  • La rotazione non considera le assenze dichiarate: può proporre il turno a chi ha risposto "assente" o "infortunato" per quell'evento.

Evoluzioni possibili

  • Versionare il cron (es. una migration con cron.schedule) invece di configurarlo solo lato dashboard.
  • Escludere dalla rotazione chi ha già dichiarato assenza per l'evento.