-- ============================================================ -- Migration 009: Notification Channels + User Preferences -- Phase 2: Channel-based opt-out system -- ============================================================ -- 1. Notification channels (platform-managed) -- Platform defines channels like "billing", "security", "marketing" -- Users can opt out of non-mandatory channels CREATE TABLE IF NOT EXISTS public.notification_channels ( channel_key VARCHAR(50) PRIMARY KEY, name VARCHAR(100) NOT NULL, description TEXT, is_mandatory BOOLEAN NOT NULL DEFAULT false, -- mandatory=true: users cannot opt out (e.g. security alerts) -- mandatory=false: users can disable (e.g. marketing) is_enabled BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Seed default channels INSERT INTO public.notification_channels (channel_key, name, description, is_mandatory) VALUES ('system', '系统通知', '平台级系统公告与维护通知', true), ('security', '安全告警', '账号安全、异常登录等安全相关通知', true), ('billing', '账单通知', '订阅续费、发票、扣款通知', false), ('feature', '新功能', '产品新功能与版本更新通知', false), ('marketing', '营销推广', '促销活动、优惠券、活动邀请', false), ('ops', '运维报警', '服务器异常、告警触发通知', false) ON CONFLICT (channel_key) DO NOTHING; -- 2. User notification preferences (per-user opt-out per channel) -- Only non-mandatory channels can be opted out CREATE TABLE IF NOT EXISTS public.user_notification_preferences ( user_id VARCHAR(100) NOT NULL, channel_key VARCHAR(50) NOT NULL REFERENCES public.notification_channels(channel_key) ON DELETE CASCADE, enabled BOOLEAN NOT NULL DEFAULT true, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), PRIMARY KEY (user_id, channel_key) ); CREATE INDEX IF NOT EXISTS idx_unp_user ON public.user_notification_preferences (user_id); -- 3. Notification segment members (for BY_SEGMENT targeting, Phase 4) -- Platform populates this table via ETL/cron jobs CREATE TABLE IF NOT EXISTS public.notification_segment_members ( segment_key VARCHAR(100) NOT NULL, tenant_id VARCHAR(100) NOT NULL, synced_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), PRIMARY KEY (segment_key, tenant_id) ); CREATE INDEX IF NOT EXISTS idx_nsm_segment ON public.notification_segment_members (segment_key); CREATE INDEX IF NOT EXISTS idx_nsm_tenant ON public.notification_segment_members (tenant_id);