Promemoria senza ritorni e tre canali per ogni notifica (DD-037, DD-038)
m23: un registro (promemoria_eventi_generati) ricorda i promemoria a 24 e 3 ore già generati, così eliminare la notifica non li fa rigenerare dal cron né riaccodare la mail a ogni giro. m24: la push dei promemoria, che nascono nel database, la manda il worker mailer/ con lo stesso modulo dell'app (coda notifiche_push_coda, esiti, backoff, iscrizioni scadute eliminate, push scartate oltre 3 ore). Il sondaggio pre-partita scrive anche notifiche_utente (tipo sondaggio_cacche): compare in-app e arriva per email. L'immagine del worker si costruisce dalla radice del repository per includere src/lib/webpush.server.ts. Test: 9 di integrazione per m23, 17 per la coda push, 6 per la route del sondaggio, nuovi casi unitari sul worker; prova end-to-end con un servizio push finto. Documentazione e catalogo delle notifiche aggiornati. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
58eba642f7
commit
e9013b703b
27 files changed
+2076
-92
No files matched your search
+50
-4
@@ -1,9 +1,11 @@
|
||||
// Worker delle notifiche email (M22, DD-036): legge la coda su Supabase e invia via SMTP.
|
||||
// Solo chiamate in uscita, nessuna porta da aprire. Uso: bun mailer/mailer.mjs
|
||||
// La logica sta in mailer-core.ts; qui c'è solo la colla con rete e SMTP.
|
||||
// Worker delle notifiche (M22 email, M24 push dei promemoria; DD-036, DD-038): legge le code su
|
||||
// Supabase e invia via SMTP e via Web Push. Solo chiamate in uscita, nessuna porta da aprire.
|
||||
// Uso: bun mailer/mailer.mjs. La logica sta in mailer-core.ts; qui c'è solo la colla con rete,
|
||||
// SMTP e il modulo push dell'app (src/lib/webpush.server.ts, lo stesso usato dalle route).
|
||||
import nodemailer from "nodemailer";
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { elaboraCiclo, leggiConfig } from "./mailer-core.ts";
|
||||
import { inviaPush } from "../src/lib/webpush.server.ts";
|
||||
import { elaboraCiclo, elaboraCicloPush, leggiConfig } from "./mailer-core.ts";
|
||||
|
||||
const FILE_SALUTE = process.env.MAILER_FILE_SALUTE ?? "/tmp/mailer-ok";
|
||||
|
||||
@@ -32,6 +34,22 @@ async function rpc(nome, corpo) {
|
||||
return testo ? JSON.parse(testo) : null;
|
||||
}
|
||||
|
||||
/** Elimina un'iscrizione push scaduta (il servizio push ha risposto 404 o 410). */
|
||||
async function eliminaIscrizione(endpoint) {
|
||||
const risposta = await fetch(
|
||||
`${config.supabaseUrl}/rest/v1/push_subscriptions?endpoint=eq.${encodeURIComponent(endpoint)}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
apikey: config.serviceRoleKey,
|
||||
Authorization: `Bearer ${config.serviceRoleKey}`,
|
||||
},
|
||||
signal: AbortSignal.timeout(15_000),
|
||||
},
|
||||
);
|
||||
if (!risposta.ok) throw new Error(`delete push_subscriptions: HTTP ${risposta.status}`);
|
||||
}
|
||||
|
||||
const trasporto = nodemailer.createTransport({
|
||||
host: config.smtp.host,
|
||||
port: config.smtp.port,
|
||||
@@ -64,6 +82,21 @@ const dipendenze = {
|
||||
log,
|
||||
};
|
||||
|
||||
const dipendenzePush = {
|
||||
prendi: (max) => rpc("prendi_push_promemoria", { p_max: max }),
|
||||
segna: (id, esito, errore, prossimo) =>
|
||||
rpc("esito_push_promemoria", {
|
||||
p_id: id,
|
||||
p_esito: esito,
|
||||
p_errore: errore ?? null,
|
||||
p_prossimo: prossimo ? prossimo.toISOString() : null,
|
||||
}),
|
||||
invia: (iscrizione, titolo, testo) => inviaPush(iscrizione, titolo, testo),
|
||||
elimina: eliminaIscrizione,
|
||||
adesso: () => new Date(),
|
||||
log,
|
||||
};
|
||||
|
||||
const opzioni = {
|
||||
limiteGiorno: config.limiteGiorno,
|
||||
lotto: config.lotto,
|
||||
@@ -76,6 +109,11 @@ for (const segnale of ["SIGTERM", "SIGINT"]) process.on(segnale, () => (fermo =
|
||||
const attendi = (ms) => new Promise((ok) => setTimeout(ok, ms));
|
||||
|
||||
log(`avviato: SMTP ${config.smtp.host}:${config.smtp.port}, ogni ${config.pollSecondi}s`);
|
||||
log(
|
||||
config.vapid
|
||||
? "push dei promemoria: attiva"
|
||||
: "push dei promemoria: DISATTIVATA (VAPID_PUBLIC_KEY e VAPID_PRIVATE_KEY assenti), solo email",
|
||||
);
|
||||
trasporto.verify().then(
|
||||
() => log("SMTP raggiungibile e credenziali accettate"),
|
||||
(errore) => log(`SMTP non verificato (${errore?.code ?? "errore"}): controlla SMTP_* nel .env`),
|
||||
@@ -90,6 +128,14 @@ while (!fermo) {
|
||||
} catch (errore) {
|
||||
console.error(`${new Date().toISOString()} ciclo fallito: ${errore?.message ?? errore}`);
|
||||
}
|
||||
if (config.vapid) {
|
||||
// Un errore delle push non deve fermare le email né il segnale di salute del worker.
|
||||
try {
|
||||
await elaboraCicloPush(dipendenzePush, { lotto: config.lotto });
|
||||
} catch (errore) {
|
||||
console.error(`${new Date().toISOString()} ciclo push fallito: ${errore?.message ?? errore}`);
|
||||
}
|
||||
}
|
||||
await attendi(config.pollSecondi * 1000);
|
||||
}
|
||||
log("arrestato");
|
||||
Reference in new issue
Block a user