Il promemoria palloni lo manda un admin dalla pagina evento (DD-025).

La route era disegnata per un cron quotidiano: calcolava chi è di turno oggi e
gli mandava una push. Ma nel repository nessun cron esiste, e palloni.md lo
annotava già come "da verificare lato hosting": nei fatti quel promemoria non è
mai partito. Il segreto condiviso introdotto ieri proteggeva una porta che
nessuno apriva, al prezzo di una variabile d'ambiente da configurare ovunque.

Ora la fa partire un amministratore dal pulsante "Avvisa chi è di turno", dentro
il riquadro palloni dell'evento. La route accetta un eventoId e avvisa i
destinatari di quell'evento invece della giornata corrente: chi deve prendere i
palloni e chi deve riportarli, con un testo diverso per ciascuno. Stesso
precedente di apri-sondaggio, manuale fin dalla v1.0.6.

Il testo va in coda su promemoria_push prima dell'invio. Serve: la push parte
vuota e il service worker chiede a push-messaggio cosa mostrare, ma quella route
sa raccontare solo la giornata corrente, quindi un avviso mandato il martedì per
il sabato arriverebbe con il testo generico.

avvisiPalloniEvento() è la nuova funzione pura, con i suoi test: due destinatari
con testi diversi, un avviso solo quando sono la stessa persona, niente avvisi
per un evento inesistente o senza turni. Un'asserzione verifica che il testo non
dica mai "oggi", perché può arrivare giorni prima.

richiediSegreto e CRON_SEGRETO spariscono: tutte e tre le route di notifica
usano ora richiediAdmin, e non resta nessuna variabile da configurare.
destinatariPromemoriaPalloni() resta in palloni-core.ts con i suoi test perché
push-messaggio continua a usarla per il testo calcolato al volo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-06 12:15:39 +02:00
co-authored by Claude Opus 5
parent 847972b582
commit ee9f3f1b8c
10 changed files with 260 additions and 96 deletions
+8 -11
View File
@@ -59,20 +59,17 @@ L'invio effettivo (`src/lib/webpush.server.ts`, funzione `inviaPush`) firma un J
(ECDSA P-256) e fa una POST senza corpo all'endpoint push del browser; è riusato identico da
`sollecita-presenze.ts` e `promemoria-palloni.ts`.
### Chi può farle partire (DD-024)
### Chi può farle partire (DD-024, DD-025)
Queste route usano la service role e saltano la RLS, quindi il permesso deve stare nella
route. `src/lib/auth-route.server.ts` fornisce i due controlli:
route. Tutte e tre partono da un gesto di un amministratore dentro l'app, quindi il controllo
è uno solo (`richiediAdmin` in `src/lib/auth-route.server.ts`) e non serve configurare nessuna
variabile d'ambiente.
| Route | Controllo | Chi la chiama |
| -------------------------------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `apri-sondaggio`, `sollecita-presenze` | `richiediAdmin` — token della sessione Supabase, poi ruolo `admin` in `user_roles` | l'app, dal pulsante riservato agli admin |
| `promemoria-palloni` | `richiediSegreto` — intestazione `x-cron-segreto` uguale alla variabile `CRON_SEGRETO` | un cron, senza sessione |
| `csi`, `push-config`, `push-subscribe`, `push-messaggio` | nessuno | il browser prima del login e il service worker, che una sessione non ce l'hanno |
**`CRON_SEGRETO` va configurata negli ambienti**: se manca, `promemoria-palloni` risponde
503 e il promemoria non parte. È voluto — una porta che si riapre da sola quando manca una
configurazione non se ne accorge nessuno.
| Route | Controllo | Chi la chiama |
| ------------------------------------------------------------ | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `apri-sondaggio`, `sollecita-presenze`, `promemoria-palloni` | `richiediAdmin` — token della sessione Supabase, poi ruolo `admin` in `user_roles` | l'app, da un pulsante riservato agli admin |
| `csi`, `push-config`, `push-subscribe`, `push-messaggio` | nessuno | il browser prima del login e il service worker, che una sessione non ce l'hanno |
---
+15 -12
View File
@@ -42,23 +42,26 @@ compaiono.
## 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)).
La fa partire un **amministratore** dal pulsante «Avvisa chi è di turno» dentro il riquadro
palloni dell'evento (`TurnoPalloni.tsx`), riservato agli admin (DD-025). Riceve l'`eventoId`,
e `avvisiPalloniEvento()` calcola i due destinatari di _quell'evento_: chi deve **prendere** i
palloni e chi deve **riportarli** (l'incaricato dell'evento precedente), con un testo diverso
per ciascuno.
Il testo va messo in coda su `promemoria_push` prima di inviare la push, perché la push parte
"vuota" e il service worker chiede a `/api/public/push-messaggio` cosa mostrare — e quella
route da sola sa raccontare solo la giornata corrente, quindi un avviso mandato con giorni di
anticipo arriverebbe con il testo generico. Stesso meccanismo di `apri-sondaggio` (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`.
- **L'invio è manuale**: nessun cron manda il promemoria da solo, se l'admin non preme il
pulsante non parte niente (DD-025). `destinatariPromemoriaPalloni()` — la versione "chi è di
turno oggi" — resta in `palloni-core.ts` perché la usa `push-messaggio` per il testo
calcolato al volo, ma nessuno scheduler la interroga.
- 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