-- ============================================================ -- Migration 010: Notification Campaigns + Read Event Log -- Phase 3: Campaign scheduling + analytics -- ============================================================ -- 1. Notification campaigns -- A campaign is a scheduled or recurring notification push job CREATE TABLE IF NOT EXISTS public.notification_campaigns ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR(200) NOT NULL, description TEXT, status VARCHAR(20) NOT NULL DEFAULT 'DRAFT', -- DRAFT | SCHEDULED | RUNNING | COMPLETED | CANCELLED | FAILED -- Linked notification template (optional: can create ad-hoc body) notification_id UUID REFERENCES public.notifications(id) ON DELETE SET NULL, -- Schedule schedule_type VARCHAR(20) NOT NULL DEFAULT 'ONCE', -- ONCE | RECURRING scheduled_at TIMESTAMPTZ, -- for ONCE: exact send time cron_expr VARCHAR(100), -- for RECURRING: cron expression timezone VARCHAR(50) NOT NULL DEFAULT 'UTC', next_run_at TIMESTAMPTZ, -- computed by scheduler last_run_at TIMESTAMPTZ, -- Execution stats target_count INT NOT NULL DEFAULT 0, -- estimated / actual target size sent_count INT NOT NULL DEFAULT 0, read_count INT NOT NULL DEFAULT 0, created_by VARCHAR(100), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_campaigns_status ON public.notification_campaigns (status); CREATE INDEX IF NOT EXISTS idx_campaigns_scheduled_at ON public.notification_campaigns (scheduled_at) WHERE status IN ('SCHEDULED', 'RUNNING'); CREATE INDEX IF NOT EXISTS idx_campaigns_next_run ON public.notification_campaigns (next_run_at) WHERE status = 'SCHEDULED'; -- 2. Campaign execution log -- Records each run of a campaign (for RECURRING campaigns, one row per run) CREATE TABLE IF NOT EXISTS public.campaign_execution_log ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), campaign_id UUID NOT NULL REFERENCES public.notification_campaigns(id) ON DELETE CASCADE, notification_id UUID REFERENCES public.notifications(id) ON DELETE SET NULL, started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), finished_at TIMESTAMPTZ, status VARCHAR(20) NOT NULL DEFAULT 'RUNNING', -- RUNNING | COMPLETED | FAILED target_count INT NOT NULL DEFAULT 0, sent_count INT NOT NULL DEFAULT 0, error TEXT ); CREATE INDEX IF NOT EXISTS idx_cel_campaign ON public.campaign_execution_log (campaign_id); -- 3. Notification delivery event log -- Detailed per-notification read events (for analytics / funnel) CREATE TABLE IF NOT EXISTS public.notification_event_log ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), notification_id UUID NOT NULL REFERENCES public.notifications(id) ON DELETE CASCADE, campaign_id UUID REFERENCES public.notification_campaigns(id) ON DELETE SET NULL, event_type VARCHAR(30) NOT NULL, -- DELIVERED | READ | CLICKED | DISMISSED user_id VARCHAR(100) NOT NULL, tenant_id VARCHAR(100) NOT NULL, occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), metadata JSONB DEFAULT '{}'::jsonb ); CREATE INDEX IF NOT EXISTS idx_nel_notification ON public.notification_event_log (notification_id); CREATE INDEX IF NOT EXISTS idx_nel_campaign ON public.notification_event_log (campaign_id) WHERE campaign_id IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_nel_user ON public.notification_event_log (user_id); CREATE INDEX IF NOT EXISTS idx_nel_occurred ON public.notification_event_log (occurred_at);