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:
@@ -40,6 +40,7 @@ Serve a rispondere a domande del tipo:
|
||||
| [DD-022](#dd-022--lapp-è-solo-chiara) | App solo chiara |
|
||||
| [DD-023](#dd-023--ogni-scrittura-è-limitata-a-chi-la-fa) | Scritture limitate per ruolo |
|
||||
| [DD-024](#dd-024--le-route-che-avvisano-la-squadra-chiedono-le-credenziali) | Route di notifica autenticate |
|
||||
| [DD-025](#dd-025--il-promemoria-palloni-lo-manda-ladmin-per-un-evento) | Promemoria palloni manuale |
|
||||
|
||||
**In valutazione**
|
||||
|
||||
@@ -839,8 +840,8 @@ i chiamanti sono diversi (`src/lib/auth-route.server.ts`):
|
||||
`src/lib/ruoli.ts` (DD-011). `401` senza token valido, `403` con token ma senza ruolo. Il
|
||||
controllo viene **prima** della validazione dell'input, così la risposta non rivela
|
||||
nemmeno se un evento esiste.
|
||||
- `promemoria-palloni` → `richiediSegreto`: intestazione `x-cron-segreto` confrontata con la
|
||||
variabile `CRON_SEGRETO`. La chiama un cron, che una sessione non ce l'ha.
|
||||
- `promemoria-palloni` → all'inizio `richiediSegreto`, con un segreto da cron; sostituito
|
||||
subito dopo da `richiediAdmin` quando la route è diventata manuale (DD-025).
|
||||
|
||||
`csi`, `push-config`, `push-subscribe` e `push-messaggio` restano aperte: le chiamano il
|
||||
browser prima del login e il service worker, dove qualsiasi segreto sarebbe pubblico.
|
||||
@@ -857,9 +858,8 @@ browser prima del login e il service worker, dove qualsiasi segreto sarebbe pubb
|
||||
|
||||
**Conseguenze**
|
||||
|
||||
- Se `CRON_SEGRETO` non è configurata, `promemoria-palloni` risponde `503` e il promemoria
|
||||
non parte. È voluto: una porta che si riapre da sola quando manca una variabile
|
||||
d'ambiente non se ne accorge nessuno. Va impostata negli ambienti di deploy e nel cron.
|
||||
- Nessuna variabile d'ambiente da configurare: dopo DD-025 tutte e tre le route usano lo
|
||||
stesso controllo sul ruolo.
|
||||
- I due pulsanti dell'app mandano ora il token con `intestazioniAutenticate()`
|
||||
(`src/lib/auth.ts`), letto al momento della chiamata e non da uno stato React, così non si
|
||||
spedisce un token scaduto.
|
||||
@@ -871,3 +871,55 @@ browser prima del login e il service worker, dove qualsiasi segreto sarebbe pubb
|
||||
**Riesame**
|
||||
Se un giorno l'app userà `createServerFn`, il middleware già presente diventa la strada
|
||||
naturale e questi controlli vanno riletti alla sua luce.
|
||||
|
||||
### DD-025 — Il promemoria palloni lo manda l'admin, per un evento
|
||||
|
||||
**Data:** 6 settembre 2026
|
||||
**Stato:** Accettata
|
||||
|
||||
**Contesto**
|
||||
`promemoria-palloni` era disegnata per un cron quotidiano: calcolava chi è di turno **oggi** e
|
||||
gli mandava una push. DD-024 l'aveva chiusa con un segreto condiviso, coerente con quel
|
||||
disegno. Ma nel repository non c'è nessun cron, e `docs/modules/palloni.md` lo annotava già
|
||||
come «da verificare lato hosting»: nei fatti quel promemoria non è mai partito. Una route che
|
||||
funziona e che nessuno chiama.
|
||||
|
||||
Nel frattempo l'app aveva già il precedente giusto: `apri-sondaggio` è manuale fin dall'inizio
|
||||
(«Nessun cron: l'invio è manuale», CHANGELOG v1.0.6).
|
||||
|
||||
**Decisione**
|
||||
Il promemoria lo fa partire un amministratore dal pulsante «Avvisa chi è di turno», dentro il
|
||||
riquadro palloni della pagina evento. La route accetta un `eventoId` e avvisa i destinatari di
|
||||
**quell'evento** — chi deve prendere i palloni e chi deve riportarli — invece della giornata
|
||||
corrente. Il controllo di accesso diventa `richiediAdmin` come le altre due, e
|
||||
`richiediSegreto` con la sua variabile `CRON_SEGRETO` spariscono.
|
||||
|
||||
Il testo dell'avviso viaggia in coda su `promemoria_push`: la push parte vuota e il service
|
||||
worker chiede a `push-messaggio` cosa mostrare, ma quella route sa raccontare solo la giornata
|
||||
corrente. Senza la coda, un avviso mandato il martedì per il sabato arriverebbe con il testo
|
||||
generico.
|
||||
|
||||
**Alternative scartate**
|
||||
|
||||
- Un pulsante «manda il promemoria di oggi» in Dashboard → rispecchia la route com'era, ma
|
||||
premuto un martedì qualsiasi risponderebbe «inviate: 0» e sembrerebbe rotto. L'admin
|
||||
ragiona per evento, non per giornata.
|
||||
- Tenere il cron e configurarlo davvero → più lavoro, una variabile d'ambiente da gestire in
|
||||
ogni ambiente, e nessuno l'aveva chiesto. Un pulsante che funziona batte uno scheduler che
|
||||
non esiste.
|
||||
- Calcolare il testo al volo come fa `messaggioPalloniOggi` → funziona solo se l'avviso parte
|
||||
il giorno stesso, cioè proprio il vincolo da cui volevamo uscire.
|
||||
|
||||
**Conseguenze**
|
||||
|
||||
- Il promemoria è ora una scelta consapevole di un admin, non un automatismo: se nessuno preme
|
||||
il pulsante, non parte niente. È un passo indietro rispetto all'idea originale, ma un passo
|
||||
avanti rispetto alla realtà, dove non partiva mai.
|
||||
- `destinatariPromemoriaPalloni()` non è più usata dalla route ma resta in `palloni-core.ts`,
|
||||
perché `push-messaggio` continua a costruire il testo della giornata per le push senza coda.
|
||||
- Sparisce `CRON_SEGRETO`: nessuna variabile d'ambiente nuova da configurare in nessun
|
||||
ambiente.
|
||||
|
||||
**Riesame**
|
||||
Se la squadra si accorge che l'admin si dimentica di premere il pulsante. A quel punto il cron
|
||||
torna utile, e con lui il segreto: DD-024 descrive già come farlo.
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user