-- 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 = 'active' 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 = 'active' 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();$$);