Implementa il ruolo allenatore nell'app
- Modello: tipo sugli slot, inRosa() al posto del filtro su attivo in tutte le letture della rosa di gioco (presenze, convocati, voti, palloni, classifiche, badge, sollecito); gli allenatori restano solo nei compleanni e in Squadra. - Permessi: user_roles letto una volta per tutti i ruoli; usePuoGestireEventi() apre /eventi e il sollecito presenze anche all'allenatore, lato server con richiediGestoreEventi(). Palloni, sondaggio e messaggi restano all'admin. - Identità: useGiocatoreInCampo() è null per l'allenatore, così non risponde alle presenze e non vota; useSonoAllenatore() decide cosa si vede. - Profilo: per l'allenatore solo Docs (nome, cognome, nascita, telefono, email) e Opzioni; card avatar e tab Opzioni estratte per riusarle. - Home, Squadra e partita: niente statistiche personali, badge, cacche e obiettivi per l'allenatore; in Rosa compare con la dicitura «Allenatore». - Admin: tipo in «Aggiungi», allenatori a parte in Profili, fuori da conteggi ed export CSI. Test unit su rosa di gioco e completamento, route test sull'allenatore. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
15a010855e
commit
2ec72fd08f
35 files changed
+1215
-577
No files matched your search
@@ -66,8 +66,19 @@ if (!locale) {
|
||||
body: JSON.stringify({ user_id: idAdmin, role: "admin" }),
|
||||
});
|
||||
|
||||
// Il ruolo allenatore lo scrive il trigger di M21 al collegamento dello slot; qui basta
|
||||
// la riga in `user_roles`, che è ciò che la route legge (DD-034).
|
||||
const emailAllenatore = `test-route-allenatore-${Date.now()}@example.test`;
|
||||
const idAllenatore = await creaUtente(emailAllenatore);
|
||||
await fetch(`${SUPABASE}/rest/v1/user_roles`, {
|
||||
method: "POST",
|
||||
headers: { ...authAdmin, "content-type": "application/json" },
|
||||
body: JSON.stringify({ user_id: idAllenatore, role: "allenatore" }),
|
||||
});
|
||||
|
||||
const tokenGiocatore = await accedi(emailGiocatore);
|
||||
const tokenAdmin = await accedi(emailAdmin);
|
||||
const tokenAllenatore = await accedi(emailAllenatore);
|
||||
|
||||
const server = await avviaServer();
|
||||
console.log(`permessi route su ${server.baseUrl} (database ${SUPABASE})`);
|
||||
@@ -108,6 +119,20 @@ if (!locale) {
|
||||
}
|
||||
});
|
||||
|
||||
await prova(
|
||||
"l'allenatore sollecita le presenze ma non manda palloni né sondaggio",
|
||||
async () => {
|
||||
const sollecito = await chiama("/api/public/sollecita-presenze", {
|
||||
authorization: `Bearer ${tokenAllenatore}`,
|
||||
});
|
||||
assert.equal(sollecito.status, 404, "sollecito: superato l'accesso, evento inesistente");
|
||||
for (const percorso of ["/api/public/apri-sondaggio", "/api/public/promemoria-palloni"]) {
|
||||
const res = await chiama(percorso, { authorization: `Bearer ${tokenAllenatore}` });
|
||||
assert.equal(res.status, 403, `${percorso}: restano all'admin`);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/** Messaggio libero: stesso controllo d'accesso, corpo diverso. */
|
||||
const chiamaMessaggio = (intestazioni: Record<string, string> = {}) =>
|
||||
fetch(`${server.baseUrl}/api/public/notifica-personalizzata`, {
|
||||
@@ -125,6 +150,11 @@ if (!locale) {
|
||||
assert.equal(res.status, 403);
|
||||
});
|
||||
|
||||
await prova("l'allenatore non manda messaggi personalizzati", async () => {
|
||||
const res = await chiamaMessaggio({ authorization: `Bearer ${tokenAllenatore}` });
|
||||
assert.equal(res.status, 403);
|
||||
});
|
||||
|
||||
await prova("un amministratore manda il messaggio (nessun dispositivo iscritto)", async () => {
|
||||
const res = await chiamaMessaggio({ authorization: `Bearer ${tokenAdmin}` });
|
||||
assert.equal(res.status, 200);
|
||||
|
||||
Reference in new issue
Block a user