Files
CRAPP/supabase/migrations/20260929140000_m23_promemoria_gia_generati.sql
T
davideandClaude Sonnet 5.5 e9013b703b 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>
2026-09-29 14:19:54 +02:00

73 lines
3.8 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- M23 — I promemoria evento non ricompaiono più se il giocatore elimina la notifica (DD-037)
--
-- Fino a M22 `genera_promemoria_eventi()` evitava i doppioni con `ON CONFLICT DO NOTHING` sul
-- vincolo UNIQUE (giocatore_id, evento_id, tipo) di `notifiche_utente`: la riga della notifica
-- era l'unica traccia che il promemoria fosse già stato generato. Ma il giocatore può eliminare
-- la notifica (swipe o ×, DD-030): senza più la riga, al giro successivo del job (ogni ora per
-- il promemoria a 24 ore, ogni 15 minuti per quello a 3 ore, finché l'evento non inizia) il
-- promemoria veniva rigenerato come non letto e, dal trigger di M22, riaccodato per email.
--
-- Il registro `promemoria_eventi_generati` ricorda in modo indipendente che il promemoria è già
-- stato generato per (giocatore, evento, tipo). Eliminare la notifica non lo tocca: il
-- promemoria non torna, e non parte una nuova mail. Il registro sparisce con l'evento o con il
-- giocatore (ON DELETE CASCADE), come le altre tabelle collegate (DD-029).
CREATE TABLE public.promemoria_eventi_generati (
giocatore_id text NOT NULL REFERENCES public.giocatori_squadra(id) ON DELETE CASCADE,
evento_id text NOT NULL REFERENCES public.eventi_app(id) ON DELETE CASCADE,
tipo text NOT NULL CHECK (tipo IN ('evento_promemoria_24h', 'evento_promemoria_3h')),
generato_il timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (giocatore_id, evento_id, tipo)
);
COMMENT ON TABLE public.promemoria_eventi_generati IS
'Promemoria evento già generati per giocatore, evento e tipo: indipendente da notifiche_utente, così eliminare la notifica non la fa rigenerare (M23, DD-037).';
-- Solo la funzione SECURITY DEFINER e la service role la toccano: RLS attiva senza policy.
ALTER TABLE public.promemoria_eventi_generati ENABLE ROW LEVEL SECURITY;
REVOKE ALL ON public.promemoria_eventi_generati FROM PUBLIC, anon, authenticated;
GRANT ALL ON public.promemoria_eventi_generati TO service_role;
-- I promemoria già presenti in `notifiche_utente` risultano generati. Senza questo, la prima
-- notifica eliminata dopo la migration verrebbe rigenerata come prima.
INSERT INTO public.promemoria_eventi_generati (giocatore_id, evento_id, tipo)
SELECT giocatore_id, evento_id, tipo
FROM public.notifiche_utente
WHERE tipo IN ('evento_promemoria_24h', 'evento_promemoria_3h')
AND evento_id IS NOT NULL
ON CONFLICT DO NOTHING;
-- Stessa firma e stessi criteri di M17 (i cron non cambiano). La differenza: prima si registra
-- il promemoria come generato, e la notifica si crea solo per le righe appena registrate. Il
-- vincolo UNIQUE su `notifiche_utente` resta come seconda difesa.
CREATE OR REPLACE FUNCTION public.genera_promemoria_eventi(p_tipo text, p_finestra interval)
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
BEGIN
WITH da_generare AS (
SELECT d AS giocatore_id, e.id AS evento_id, e.titolo, e.data, e.ora
FROM public.eventi_app e
CROSS JOIN LATERAL public.giocatori_destinatari_evento(e.id) AS d
WHERE public.ora_evento_a_time(e.ora) IS NOT NULL
AND ((e.data + public.ora_evento_a_time(e.ora)) AT TIME ZONE 'Europe/Rome') - now()
BETWEEN interval '0' AND p_finestra
),
nuovi AS (
INSERT INTO public.promemoria_eventi_generati (giocatore_id, evento_id, tipo)
SELECT giocatore_id, evento_id, p_tipo FROM da_generare
ON CONFLICT DO NOTHING
RETURNING giocatore_id, evento_id
)
INSERT INTO public.notifiche_utente (giocatore_id, tipo, titolo, corpo, evento_id)
SELECT g.giocatore_id, p_tipo, 'Promemoria: ' || g.titolo,
to_char(g.data, 'DD/MM/YYYY') || ' alle ' || g.ora,
g.evento_id
FROM nuovi n
JOIN da_generare g ON g.giocatore_id = n.giocatore_id AND g.evento_id = n.evento_id
ON CONFLICT (giocatore_id, evento_id, tipo) DO NOTHING;
END;
$$;