tasq/supabase/migrations/20260318_add_scheduled_notifications.sql

76 lines
3.3 KiB
PL/PgSQL

-- Migration: add scheduled_notifications queue and enqueue function for shift reminders
-- Creates a queue table and stored procedure to enqueue 15-minute and end-of-shift reminders
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- Queue table for scheduled notifications
CREATE TABLE IF NOT EXISTS public.scheduled_notifications (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
schedule_id uuid NOT NULL REFERENCES public.duty_schedules(id) ON DELETE CASCADE,
user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
notify_type text NOT NULL, -- 'start_15' | 'end'
scheduled_for timestamptz NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
processed boolean NOT NULL DEFAULT false,
processed_at timestamptz,
retry_count int NOT NULL DEFAULT 0,
last_error text
);
CREATE INDEX IF NOT EXISTS idx_scheduled_notifications_scheduled_for ON public.scheduled_notifications(scheduled_for);
CREATE INDEX IF NOT EXISTS idx_scheduled_notifications_processed ON public.scheduled_notifications(processed);
-- Ensure uniqueness per schedule/user/notify type
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint c
JOIN pg_class t ON c.conrelid = t.oid
WHERE c.conname = 'uniq_sched_user_type' AND t.relname = 'scheduled_notifications'
) THEN
ALTER TABLE public.scheduled_notifications ADD CONSTRAINT uniq_sched_user_type UNIQUE (schedule_id, user_id, notify_type);
END IF;
END$$;
-- The project already has notification_pushes and try_mark_notification_pushed (see earlier migrations).
-- Enqueue function: finds duty_schedules in the configured windows and inserts into queue.
CREATE OR REPLACE FUNCTION public.enqueue_due_shift_notifications()
RETURNS void LANGUAGE plpgsql AS $$
DECLARE
rec RECORD;
BEGIN
-- 15-minute-before reminders
FOR rec IN
SELECT id AS schedule_id, user_id AS user_id, start_time AS start_at
FROM public.duty_schedules
WHERE start_time BETWEEN now() + interval '15 minutes' - interval '30 seconds' AND now() + interval '15 minutes' + interval '30 seconds'
AND status IN ('arrival', 'late')
LOOP
-- Skip if user already checked in for this duty (attendance_logs references duty_schedule_id)
IF EXISTS (SELECT 1 FROM public.attendance_logs al WHERE al.duty_schedule_id = rec.schedule_id AND al.check_in_at IS NOT NULL) THEN
CONTINUE;
END IF;
INSERT INTO public.scheduled_notifications (schedule_id, user_id, notify_type, scheduled_for)
VALUES (rec.schedule_id, rec.user_id, 'start_15', rec.start_at)
ON CONFLICT (schedule_id, user_id, notify_type) DO NOTHING;
END LOOP;
-- End-of-shift reminders (near exact end)
FOR rec IN
SELECT id AS schedule_id, user_id AS user_id, end_time AS end_at
FROM public.duty_schedules
WHERE end_time BETWEEN now() - interval '30 seconds' AND now() + interval '30 seconds'
AND status IN ('arrival', 'late')
LOOP
INSERT INTO public.scheduled_notifications (schedule_id, user_id, notify_type, scheduled_for)
VALUES (rec.schedule_id, rec.user_id, 'end', rec.end_at)
ON CONFLICT (schedule_id, user_id, notify_type) DO NOTHING;
END LOOP;
END;
$$;
-- NOTE: Schedule this function with pg_cron or your Postgres scheduler. Example (requires pg_cron extension enabled):
-- SELECT cron.schedule('shift_reminders_every_min', '*/1 * * * *', $$SELECT public.enqueue_due_shift_notifications();$$);