Sposta le route di notifiche push su PocketBase
Le route server in src/routes/api/public/ (promemoria-palloni, sollecita-presenze, apri-sondaggio, push-subscribe, notifiche-attive) usano pbAdmin al posto di supabaseAdmin per leggere/scrivere push_subscriptions e i dati necessari a comporre gli avvisi. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,8 @@ import { inviaPush } from "@/lib/webpush.server";
|
|||||||
|
|
||||||
const schema = z.object({ eventoId: z.string().min(1).max(50) });
|
const schema = z.object({ eventoId: z.string().min(1).max(50) });
|
||||||
|
|
||||||
|
type RigaIscrizione = { id: string; endpoint: string; p256dh: string; auth: string };
|
||||||
|
|
||||||
/** Avviso "sondaggio pre-partita aperto": lo fa partire un admin dalla pagina partita. */
|
/** Avviso "sondaggio pre-partita aperto": lo fa partire un admin dalla pagina partita. */
|
||||||
export const Route = createFileRoute("/api/public/apri-sondaggio")({
|
export const Route = createFileRoute("/api/public/apri-sondaggio")({
|
||||||
server: {
|
server: {
|
||||||
@@ -21,23 +23,21 @@ export const Route = createFileRoute("/api/public/apri-sondaggio")({
|
|||||||
const partita = eventi.find((e) => e.id === parsed.data.eventoId);
|
const partita = eventi.find((e) => e.id === parsed.data.eventoId);
|
||||||
if (!partita) return new Response("Evento non trovato", { status: 404 });
|
if (!partita) return new Response("Evento non trovato", { status: 404 });
|
||||||
|
|
||||||
const { supabaseAdmin } = await import("@/integrations/supabase/client.server");
|
const { pbAdmin } = await import("@/integrations/pocketbase/client.server");
|
||||||
const { data: iscrizioni } = await supabaseAdmin
|
const admin = await pbAdmin();
|
||||||
.from("push_subscriptions")
|
const iscrizioni = await admin
|
||||||
.select("endpoint, p256dh, auth");
|
.collection("push_subscriptions")
|
||||||
|
.getFullList<RigaIscrizione>();
|
||||||
|
|
||||||
const titolo = "💩 Sondaggio pre-partita aperto";
|
const titolo = "💩 Sondaggio pre-partita aperto";
|
||||||
const testo = `${partita.titolo} · ore ${partita.ora}. Quante cacche hai fatto? Rispondi prima del fischio d'inizio.`;
|
const testo = `${partita.titolo} · ore ${partita.ora}. Quante cacche hai fatto? Rispondi prima del fischio d'inizio.`;
|
||||||
|
|
||||||
let inviate = 0;
|
let inviate = 0;
|
||||||
for (const iscrizione of iscrizioni ?? []) {
|
for (const iscrizione of iscrizioni) {
|
||||||
try {
|
try {
|
||||||
const { stato } = await inviaPush(iscrizione, titolo, testo);
|
const { stato } = await inviaPush(iscrizione, titolo, testo);
|
||||||
if (stato === 404 || stato === 410) {
|
if (stato === 404 || stato === 410) {
|
||||||
await supabaseAdmin
|
await admin.collection("push_subscriptions").delete(iscrizione.id);
|
||||||
.from("push_subscriptions")
|
|
||||||
.delete()
|
|
||||||
.eq("endpoint", iscrizione.endpoint);
|
|
||||||
} else if (stato >= 200 && stato < 300) {
|
} else if (stato >= 200 && stato < 300) {
|
||||||
inviate += 1;
|
inviate += 1;
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@ export const Route = createFileRoute("/api/public/apri-sondaggio")({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response.json({ inviate, destinatari: (iscrizioni ?? []).length });
|
return Response.json({ inviate, destinatari: iscrizioni.length });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,16 +9,20 @@ export const Route = createFileRoute("/api/public/notifiche-attive")({
|
|||||||
const negato = await richiediAdmin(request);
|
const negato = await richiediAdmin(request);
|
||||||
if (negato) return negato;
|
if (negato) return negato;
|
||||||
|
|
||||||
const { supabaseAdmin } = await import("@/integrations/supabase/client.server");
|
const { pbAdmin } = await import("@/integrations/pocketbase/client.server");
|
||||||
const { data, error } = await supabaseAdmin
|
const admin = await pbAdmin();
|
||||||
.from("push_subscriptions")
|
|
||||||
.select("giocatore_id");
|
try {
|
||||||
if (error) {
|
const righe = await admin
|
||||||
|
.collection("push_subscriptions")
|
||||||
|
.getFullList<{ giocatore: string }>();
|
||||||
|
return Response.json({
|
||||||
|
giocatoreIds: idsConNotificheAttive(righe.map((r) => ({ giocatore_id: r.giocatore }))),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
console.error("notifiche-attive", error);
|
console.error("notifiche-attive", error);
|
||||||
return new Response("Errore lettura", { status: 500 });
|
return new Response("Errore lettura", { status: 500 });
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response.json({ giocatoreIds: idsConNotificheAttive(data ?? []) });
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,6 +9,15 @@ import { leggiEventi } from "@/lib/eventi.server";
|
|||||||
|
|
||||||
const schema = z.object({ eventoId: z.string().min(1).max(50) });
|
const schema = z.object({ eventoId: z.string().min(1).max(50) });
|
||||||
|
|
||||||
|
type RigaTurno = { evento: string; giocatore: string };
|
||||||
|
type RigaIscrizione = {
|
||||||
|
id: string;
|
||||||
|
endpoint: string;
|
||||||
|
giocatore: string;
|
||||||
|
p256dh: string;
|
||||||
|
auth: string;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Promemoria del turno palloni per un evento: lo fa partire un admin dalla pagina
|
* Promemoria del turno palloni per un evento: lo fa partire un admin dalla pagina
|
||||||
* dell'evento (DD-025). Il testo viaggia cifrato dentro la push.
|
* dell'evento (DD-025). Il testo viaggia cifrato dentro la push.
|
||||||
@@ -27,13 +36,12 @@ export const Route = createFileRoute("/api/public/promemoria-palloni")({
|
|||||||
const evento = eventi.find((e) => e.id === parsed.data.eventoId);
|
const evento = eventi.find((e) => e.id === parsed.data.eventoId);
|
||||||
if (!evento) return new Response("Evento non trovato", { status: 404 });
|
if (!evento) return new Response("Evento non trovato", { status: 404 });
|
||||||
|
|
||||||
const { supabaseAdmin } = await import("@/integrations/supabase/client.server");
|
const { pbAdmin } = await import("@/integrations/pocketbase/client.server");
|
||||||
|
const admin = await pbAdmin();
|
||||||
|
|
||||||
const { data: righe } = await supabaseAdmin
|
const righeTurni = await admin.collection("turni_palloni").getFullList<RigaTurno>();
|
||||||
.from("turni_palloni")
|
|
||||||
.select("evento_id, giocatore_id");
|
|
||||||
const salvati: Record<string, string> = {};
|
const salvati: Record<string, string> = {};
|
||||||
for (const riga of righe ?? []) salvati[riga.evento_id] = riga.giocatore_id;
|
for (const riga of righeTurni) salvati[riga.evento] = riga.giocatore;
|
||||||
|
|
||||||
const squadra = await leggiGiocatoriSquadra();
|
const squadra = await leggiGiocatoriSquadra();
|
||||||
const rosa = squadra
|
const rosa = squadra
|
||||||
@@ -44,25 +52,21 @@ export const Route = createFileRoute("/api/public/promemoria-palloni")({
|
|||||||
const avvisi = avvisiPalloniEvento(turni, eventi, evento.id);
|
const avvisi = avvisiPalloniEvento(turni, eventi, evento.id);
|
||||||
if (avvisi.length === 0) return Response.json({ inviate: 0, destinatari: 0 });
|
if (avvisi.length === 0) return Response.json({ inviate: 0, destinatari: 0 });
|
||||||
|
|
||||||
const { data: iscrizioni } = await supabaseAdmin
|
const idsFiltro = avvisi.map((a) => a.giocatoreId);
|
||||||
.from("push_subscriptions")
|
const filtro = idsFiltro.map((_, i) => `giocatore = {:g${i}}`).join(" || ");
|
||||||
.select("endpoint, giocatore_id, p256dh, auth")
|
const parametri = Object.fromEntries(idsFiltro.map((id, i) => [`g${i}`, id]));
|
||||||
.in(
|
const iscrizioni = await admin
|
||||||
"giocatore_id",
|
.collection("push_subscriptions")
|
||||||
avvisi.map((a) => a.giocatoreId),
|
.getFullList<RigaIscrizione>({ filter: admin.filter(filtro, parametri) });
|
||||||
);
|
|
||||||
|
|
||||||
let inviate = 0;
|
let inviate = 0;
|
||||||
for (const iscrizione of iscrizioni ?? []) {
|
for (const iscrizione of iscrizioni) {
|
||||||
const avviso = avvisi.find((a) => a.giocatoreId === iscrizione.giocatore_id);
|
const avviso = avvisi.find((a) => a.giocatoreId === iscrizione.giocatore);
|
||||||
if (!avviso) continue;
|
if (!avviso) continue;
|
||||||
try {
|
try {
|
||||||
const { stato } = await inviaPush(iscrizione, avviso.titolo, avviso.testo);
|
const { stato } = await inviaPush(iscrizione, avviso.titolo, avviso.testo);
|
||||||
if (stato === 404 || stato === 410) {
|
if (stato === 404 || stato === 410) {
|
||||||
await supabaseAdmin
|
await admin.collection("push_subscriptions").delete(iscrizione.id);
|
||||||
.from("push_subscriptions")
|
|
||||||
.delete()
|
|
||||||
.eq("endpoint", iscrizione.endpoint);
|
|
||||||
} else if (stato >= 200 && stato < 300) {
|
} else if (stato >= 200 && stato < 300) {
|
||||||
inviate += 1;
|
inviate += 1;
|
||||||
}
|
}
|
||||||
@@ -71,7 +75,7 @@ export const Route = createFileRoute("/api/public/promemoria-palloni")({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response.json({ inviate, destinatari: (iscrizioni ?? []).length });
|
return Response.json({ inviate, destinatari: iscrizioni.length });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,17 +17,24 @@ export const Route = createFileRoute("/api/public/push-subscribe")({
|
|||||||
const parsed = schemaIscrizione.safeParse(await request.json());
|
const parsed = schemaIscrizione.safeParse(await request.json());
|
||||||
if (!parsed.success) return new Response("Dati non validi", { status: 400 });
|
if (!parsed.success) return new Response("Dati non validi", { status: 400 });
|
||||||
|
|
||||||
const { supabaseAdmin } = await import("@/integrations/supabase/client.server");
|
const { pbAdmin } = await import("@/integrations/pocketbase/client.server");
|
||||||
const { error } = await supabaseAdmin.from("push_subscriptions").upsert(
|
const { upsertByFilter } = await import("@/integrations/pocketbase/upsert");
|
||||||
{
|
const admin = await pbAdmin();
|
||||||
endpoint: parsed.data.endpoint,
|
|
||||||
giocatore_id: parsed.data.giocatoreId,
|
try {
|
||||||
p256dh: parsed.data.p256dh,
|
await upsertByFilter(
|
||||||
auth: parsed.data.auth,
|
"push_subscriptions",
|
||||||
},
|
"endpoint = {:endpoint}",
|
||||||
{ onConflict: "endpoint" },
|
{ endpoint: parsed.data.endpoint },
|
||||||
);
|
{
|
||||||
if (error) {
|
endpoint: parsed.data.endpoint,
|
||||||
|
giocatore: parsed.data.giocatoreId,
|
||||||
|
p256dh: parsed.data.p256dh,
|
||||||
|
auth: parsed.data.auth,
|
||||||
|
},
|
||||||
|
admin,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
console.error("push-subscribe", error);
|
console.error("push-subscribe", error);
|
||||||
return new Response("Errore salvataggio", { status: 500 });
|
return new Response("Errore salvataggio", { status: 500 });
|
||||||
}
|
}
|
||||||
@@ -37,11 +44,15 @@ export const Route = createFileRoute("/api/public/push-subscribe")({
|
|||||||
const parsed = schemaCancellazione.safeParse(await request.json());
|
const parsed = schemaCancellazione.safeParse(await request.json());
|
||||||
if (!parsed.success) return new Response("Dati non validi", { status: 400 });
|
if (!parsed.success) return new Response("Dati non validi", { status: 400 });
|
||||||
|
|
||||||
const { supabaseAdmin } = await import("@/integrations/supabase/client.server");
|
const { pbAdmin } = await import("@/integrations/pocketbase/client.server");
|
||||||
await supabaseAdmin
|
const admin = await pbAdmin();
|
||||||
.from("push_subscriptions")
|
const esistente = await admin
|
||||||
.delete()
|
.collection("push_subscriptions")
|
||||||
.eq("endpoint", parsed.data.endpoint);
|
.getFirstListItem(
|
||||||
|
admin.filter("endpoint = {:endpoint}", { endpoint: parsed.data.endpoint }),
|
||||||
|
)
|
||||||
|
.catch(() => null);
|
||||||
|
if (esistente) await admin.collection("push_subscriptions").delete(esistente.id);
|
||||||
return Response.json({ ok: true });
|
return Response.json({ ok: true });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ const schema = z.object({
|
|||||||
da: z.string().min(1).max(60).optional(),
|
da: z.string().min(1).max(60).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
type RigaPresenza = { giocatore: string; stato: string };
|
||||||
|
type RigaIscrizione = { id: string; endpoint: string; p256dh: string; auth: string };
|
||||||
|
|
||||||
export const Route = createFileRoute("/api/public/sollecita-presenze")({
|
export const Route = createFileRoute("/api/public/sollecita-presenze")({
|
||||||
server: {
|
server: {
|
||||||
handlers: {
|
handlers: {
|
||||||
@@ -26,22 +29,26 @@ export const Route = createFileRoute("/api/public/sollecita-presenze")({
|
|||||||
const evento = eventi.find((e) => e.id === parsed.data.eventoId);
|
const evento = eventi.find((e) => e.id === parsed.data.eventoId);
|
||||||
if (!evento) return new Response("Evento non trovato", { status: 404 });
|
if (!evento) return new Response("Evento non trovato", { status: 404 });
|
||||||
|
|
||||||
const { supabaseAdmin } = await import("@/integrations/supabase/client.server");
|
const { pbAdmin } = await import("@/integrations/pocketbase/client.server");
|
||||||
|
const admin = await pbAdmin();
|
||||||
|
|
||||||
const { data: righe } = await supabaseAdmin
|
const righe = await admin.collection("risposte_presenze").getFullList<RigaPresenza>({
|
||||||
.from("risposte_presenze")
|
filter: admin.filter("evento = {:evento}", { evento: evento.id }),
|
||||||
.select("giocatore_id, stato")
|
});
|
||||||
.eq("evento_id", evento.id);
|
|
||||||
|
|
||||||
const squadra = await leggiGiocatoriSquadra();
|
const squadra = await leggiGiocatoriSquadra();
|
||||||
const destinatari = destinatariSollecito(squadra, righe ?? []);
|
const destinatari = destinatariSollecito(
|
||||||
|
squadra,
|
||||||
|
righe.map((r) => ({ giocatore_id: r.giocatore, stato: r.stato })),
|
||||||
|
);
|
||||||
|
|
||||||
if (destinatari.length === 0) return Response.json({ inviate: 0, destinatari: 0 });
|
if (destinatari.length === 0) return Response.json({ inviate: 0, destinatari: 0 });
|
||||||
|
|
||||||
const { data: iscrizioni } = await supabaseAdmin
|
const filtro = destinatari.map((_, i) => `giocatore = {:g${i}}`).join(" || ");
|
||||||
.from("push_subscriptions")
|
const parametri = Object.fromEntries(destinatari.map((id, i) => [`g${i}`, id]));
|
||||||
.select("endpoint, p256dh, auth")
|
const iscrizioni = await admin
|
||||||
.in("giocatore_id", destinatari);
|
.collection("push_subscriptions")
|
||||||
|
.getFullList<RigaIscrizione>({ filter: admin.filter(filtro, parametri) });
|
||||||
|
|
||||||
const titolo = "Manca la tua risposta";
|
const titolo = "Manca la tua risposta";
|
||||||
const testo = `${evento.titolo} · ${formatData(evento.data)} ore ${evento.ora}. ${
|
const testo = `${evento.titolo} · ${formatData(evento.data)} ore ${evento.ora}. ${
|
||||||
@@ -49,14 +56,11 @@ export const Route = createFileRoute("/api/public/sollecita-presenze")({
|
|||||||
} una conferma: presente, assente o in ritardo?`;
|
} una conferma: presente, assente o in ritardo?`;
|
||||||
|
|
||||||
let inviate = 0;
|
let inviate = 0;
|
||||||
for (const iscrizione of iscrizioni ?? []) {
|
for (const iscrizione of iscrizioni) {
|
||||||
try {
|
try {
|
||||||
const { stato } = await inviaPush(iscrizione, titolo, testo);
|
const { stato } = await inviaPush(iscrizione, titolo, testo);
|
||||||
if (stato === 404 || stato === 410) {
|
if (stato === 404 || stato === 410) {
|
||||||
await supabaseAdmin
|
await admin.collection("push_subscriptions").delete(iscrizione.id);
|
||||||
.from("push_subscriptions")
|
|
||||||
.delete()
|
|
||||||
.eq("endpoint", iscrizione.endpoint);
|
|
||||||
} else if (stato >= 200 && stato < 300) {
|
} else if (stato >= 200 && stato < 300) {
|
||||||
inviate += 1;
|
inviate += 1;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user