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>
74 lines
3.1 KiB
Markdown
74 lines
3.1 KiB
Markdown
# 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](notifiche.md)).
|
|
|
|
---
|
|
|
|
## 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.
|