2026-09-14 08:05:15 +02:00
|
|
|
import { createFileRoute } from "@tanstack/react-router";
|
2026-09-15 11:19:07 +02:00
|
|
|
import type { SupabaseClient } from "@supabase/supabase-js";
|
2026-09-14 08:05:15 +02:00
|
|
|
import { z } from "zod";
|
|
|
|
|
import { richiediAdmin } from "@/lib/auth-route.server";
|
|
|
|
|
import { inviaPush } from "@/lib/webpush.server";
|
|
|
|
|
|
|
|
|
|
const schema = z.object({
|
|
|
|
|
messaggio: z.string().min(1).max(300),
|
|
|
|
|
giocatoreId: z.string().min(1).max(50).optional(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const Route = createFileRoute("/api/public/notifica-personalizzata")({
|
|
|
|
|
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 { supabaseAdmin } = await import("@/integrations/supabase/client.server");
|
2026-09-15 11:19:07 +02:00
|
|
|
// `types.ts` non include ancora `notifiche_utente` (M17): stesso aggiramento di
|
|
|
|
|
// `giocatori-squadra.server.ts` finché non viene rigenerato.
|
|
|
|
|
const client = supabaseAdmin as unknown as SupabaseClient;
|
2026-09-14 08:05:15 +02:00
|
|
|
|
|
|
|
|
let query = supabaseAdmin.from("push_subscriptions").select("endpoint, p256dh, auth");
|
|
|
|
|
if (parsed.data.giocatoreId) {
|
|
|
|
|
query = query.eq("giocatore_id", parsed.data.giocatoreId);
|
|
|
|
|
}
|
|
|
|
|
const { data: iscrizioni } = await query;
|
|
|
|
|
|
|
|
|
|
const titolo = "Messaggio dallo staff";
|
|
|
|
|
const testo = parsed.data.messaggio.trim();
|
|
|
|
|
|
|
|
|
|
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("notifica-personalizzata", error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-15 11:19:07 +02:00
|
|
|
// Storico in-app (M17): indipendente dall'esito della push, e non limitato a chi ha
|
|
|
|
|
// un dispositivo iscritto. Senza `giocatoreId` va a tutta la rosa attiva.
|
|
|
|
|
let destinatariNotifica: string[];
|
|
|
|
|
if (parsed.data.giocatoreId) {
|
|
|
|
|
destinatariNotifica = [parsed.data.giocatoreId];
|
|
|
|
|
} else {
|
|
|
|
|
const { data: rosa } = await client
|
|
|
|
|
.from("giocatori_squadra")
|
|
|
|
|
.select("id")
|
|
|
|
|
.eq("attivo", true);
|
|
|
|
|
destinatariNotifica = ((rosa ?? []) as Array<{ id: string }>).map((r) => r.id);
|
|
|
|
|
}
|
|
|
|
|
if (destinatariNotifica.length > 0) {
|
|
|
|
|
await client.from("notifiche_utente").insert(
|
|
|
|
|
destinatariNotifica.map((giocatoreId) => ({
|
|
|
|
|
giocatore_id: giocatoreId,
|
|
|
|
|
tipo: "admin",
|
|
|
|
|
titolo,
|
|
|
|
|
corpo: testo,
|
|
|
|
|
})),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-14 08:05:15 +02:00
|
|
|
return Response.json({ inviate, destinatari: iscrizioni?.length ?? 0 });
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|