-- ===================================================== -- Module Notification Rules & Queue -- Shared notification infrastructure for courses, events, bookings. -- ===================================================== -- Notification rules: define what triggers notifications CREATE TABLE IF NOT EXISTS public.module_notification_rules ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), account_id uuid NOT NULL REFERENCES public.accounts(id) ON DELETE CASCADE, module text NOT NULL CHECK (module IN ('courses', 'events', 'bookings')), trigger_event text NOT NULL CHECK (trigger_event IN ( 'course.participant_enrolled', 'course.participant_waitlisted', 'course.participant_promoted', 'course.participant_cancelled', 'course.status_changed', 'course.session_reminder', 'event.registration_confirmed', 'event.registration_waitlisted', 'event.registration_promoted', 'event.registration_cancelled', 'event.status_changed', 'event.reminder', 'booking.confirmed', 'booking.check_in_reminder', 'booking.checked_in', 'booking.checked_out', 'booking.cancelled' )), channel text NOT NULL DEFAULT 'in_app' CHECK (channel IN ('in_app', 'email', 'both')), recipient_type text NOT NULL DEFAULT 'admin' CHECK (recipient_type IN ('admin', 'participant', 'guest', 'instructor', 'specific_user')), recipient_config jsonb NOT NULL DEFAULT '{}', subject_template text, message_template text NOT NULL, is_active boolean NOT NULL DEFAULT true, created_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS ix_module_notification_rules_lookup ON public.module_notification_rules(account_id, module, trigger_event) WHERE is_active = true; ALTER TABLE public.module_notification_rules ENABLE ROW LEVEL SECURITY; REVOKE ALL ON public.module_notification_rules FROM authenticated, service_role; GRANT SELECT, INSERT, UPDATE, DELETE ON public.module_notification_rules TO authenticated; GRANT ALL ON public.module_notification_rules TO service_role; CREATE POLICY module_notification_rules_select ON public.module_notification_rules FOR SELECT TO authenticated USING (public.has_role_on_account(account_id)); CREATE POLICY module_notification_rules_mutate ON public.module_notification_rules FOR ALL TO authenticated USING ( public.has_permission(auth.uid(), account_id, 'settings.manage'::public.app_permissions) ) WITH CHECK ( public.has_permission(auth.uid(), account_id, 'settings.manage'::public.app_permissions) ); -- Pending notifications queue CREATE TABLE IF NOT EXISTS public.pending_module_notifications ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, account_id uuid NOT NULL REFERENCES public.accounts(id) ON DELETE CASCADE, module text NOT NULL CHECK (module IN ('courses', 'events', 'bookings')), trigger_event text NOT NULL, entity_id uuid NOT NULL, context jsonb NOT NULL DEFAULT '{}', processed_at timestamptz, created_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS ix_pending_module_notifications_unprocessed ON public.pending_module_notifications(created_at) WHERE processed_at IS NULL; ALTER TABLE public.pending_module_notifications ENABLE ROW LEVEL SECURITY; REVOKE ALL ON public.pending_module_notifications FROM authenticated, service_role; GRANT SELECT ON public.pending_module_notifications TO authenticated; GRANT ALL ON public.pending_module_notifications TO service_role; CREATE POLICY pending_module_notifications_select ON public.pending_module_notifications FOR SELECT TO authenticated USING (public.has_role_on_account(account_id)); -- Enqueue helper CREATE OR REPLACE FUNCTION public.enqueue_module_notification( p_account_id uuid, p_module text, p_trigger_event text, p_entity_id uuid, p_context jsonb DEFAULT '{}' ) RETURNS void LANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$ BEGIN INSERT INTO public.pending_module_notifications (account_id, module, trigger_event, entity_id, context) VALUES (p_account_id, p_module, p_trigger_event, p_entity_id, p_context); END; $$; GRANT EXECUTE ON FUNCTION public.enqueue_module_notification(uuid, text, text, uuid, jsonb) TO authenticated; GRANT EXECUTE ON FUNCTION public.enqueue_module_notification(uuid, text, text, uuid, jsonb) TO service_role;