- 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>
61 lines
2.4 KiB
TypeScript
61 lines
2.4 KiB
TypeScript
import { createFileRoute } from "@tanstack/react-router";
|
|
import { z } from "zod";
|
|
import { richiediAdmin } from "@/lib/auth-route.server";
|
|
import { leggiEventi } from "@/lib/eventi.server";
|
|
import { inviaPush } from "@/lib/webpush.server";
|
|
import { isAllenatore } from "@/lib/giocatori-squadra";
|
|
import { leggiGiocatoriSquadra } from "@/lib/giocatori-squadra.server";
|
|
|
|
const schema = z.object({ eventoId: z.string().min(1).max(50) });
|
|
|
|
/** Avviso "sondaggio pre-partita aperto": lo fa partire un admin dalla pagina partita. */
|
|
export const Route = createFileRoute("/api/public/apri-sondaggio")({
|
|
server: {
|
|
handlers: {
|
|
POST: async ({ request }) => {
|
|
const negato = await richiediAdmin(request);
|
|
if (negato) return negato;
|
|
|
|
const parsed = schema.safeParse(await request.json());
|
|
if (!parsed.success) return new Response("Dati non validi", { status: 400 });
|
|
|
|
const eventi = await leggiEventi();
|
|
const partita = eventi.find((e) => e.id === parsed.data.eventoId);
|
|
if (!partita) return new Response("Evento non trovato", { status: 404 });
|
|
|
|
const { supabaseAdmin } = await import("@/integrations/supabase/client.server");
|
|
const { data: tutte } = await supabaseAdmin
|
|
.from("push_subscriptions")
|
|
.select("endpoint, p256dh, auth, giocatore_id");
|
|
// Il sondaggio cacche non riguarda l'allenatore, che non le vede (DD-034).
|
|
const allenatori = new Set(
|
|
(await leggiGiocatoriSquadra()).filter(isAllenatore).map((g) => g.id),
|
|
);
|
|
const iscrizioni = (tutte ?? []).filter((i) => !allenatori.has(i.giocatore_id));
|
|
|
|
const titolo = "💩 Sondaggio pre-partita aperto";
|
|
const testo = `${partita.titolo} · ore ${partita.ora}. Quante cacche hai fatto? Rispondi prima del fischio d'inizio.`;
|
|
|
|
let inviate = 0;
|
|
for (const iscrizione of iscrizioni) {
|
|
try {
|
|
const { stato } = await inviaPush(iscrizione, titolo, testo);
|
|
if (stato === 404 || stato === 410) {
|
|
await supabaseAdmin
|
|
.from("push_subscriptions")
|
|
.delete()
|
|
.eq("endpoint", iscrizione.endpoint);
|
|
} else if (stato >= 200 && stato < 300) {
|
|
inviate += 1;
|
|
}
|
|
} catch (error) {
|
|
console.error("apri-sondaggio", error);
|
|
}
|
|
}
|
|
|
|
return Response.json({ inviate, destinatari: iscrizioni.length });
|
|
},
|
|
},
|
|
},
|
|
});
|