CREATE TABLE IF NOT EXISTS creator_settings ( id boolean PRIMARY KEY DEFAULT true CHECK (id), lookback_days integer NOT NULL DEFAULT 30 CHECK (lookback_days > 0), new_work_interval_seconds bigint NOT NULL DEFAULT 1800 CHECK (new_work_interval_seconds > 0), metric_initial_interval_seconds bigint NOT NULL DEFAULT 3600 CHECK (metric_initial_interval_seconds > 0), metric_multiplier double precision NOT NULL DEFAULT 2 CHECK (metric_multiplier > 1), metric_max_interval_seconds bigint NOT NULL DEFAULT 86400 CHECK (metric_max_interval_seconds >= metric_initial_interval_seconds), metric_age_seconds bigint NOT NULL DEFAULT 2592000 CHECK (metric_age_seconds > 0), ai_provider text NOT NULL DEFAULT '', ai_model text NOT NULL DEFAULT '', ai_configured boolean NOT NULL DEFAULT false, transcription_provider text NOT NULL DEFAULT '', transcription_model text NOT NULL DEFAULT '', transcription_configured boolean NOT NULL DEFAULT false, updated_at timestamptz NOT NULL DEFAULT now() ); INSERT INTO creator_settings (id) VALUES (true) ON CONFLICT (id) DO NOTHING; CREATE TABLE IF NOT EXISTS creator_account_profile ( account_id text PRIMARY KEY REFERENCES social_account(id) ON DELETE CASCADE, login_username text NOT NULL DEFAULT '', password_configured boolean NOT NULL DEFAULT false, real_name_status text NOT NULL DEFAULT 'unknown' CHECK (real_name_status IN ('unknown', 'not_real_name', 'recorded')), real_name text NOT NULL DEFAULT '', identity_number text NOT NULL DEFAULT '', note text NOT NULL DEFAULT '', business_status text NOT NULL DEFAULT 'normal' CHECK (business_status IN ('normal', 'muted', 'banned', 'deleted')), big_account boolean NOT NULL DEFAULT false, reply_requirements text NOT NULL DEFAULT '', login_status text NOT NULL DEFAULT 'unknown' CHECK (login_status IN ('unknown', 'logged_in', 'needs_login', 'failed', 'manual_required')), login_reason text NOT NULL DEFAULT '', login_checked_at timestamptz, cooldown_seconds bigint NOT NULL DEFAULT 86400 CHECK (cooldown_seconds > 0), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS creator_competitor ( id text PRIMARY KEY, platform text NOT NULL CHECK (platform IN ('douyin', 'xiaohongshu')), platform_account_key text NOT NULL, nickname text NOT NULL DEFAULT '', avatar_url text NOT NULL DEFAULT '', homepage_url text NOT NULL, enabled boolean NOT NULL DEFAULT true, sync_status text NOT NULL DEFAULT 'idle' CHECK (sync_status IN ('idle', 'running', 'paused', 'failed', 'blocked')), sync_cursor text NOT NULL DEFAULT '', sync_error text NOT NULL DEFAULT '', last_sync_at timestamptz, next_sync_at timestamptz, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), UNIQUE (platform, platform_account_key) ); CREATE TABLE IF NOT EXISTS creator_work ( id text PRIMARY KEY, platform text NOT NULL CHECK (platform IN ('douyin', 'xiaohongshu')), work_key text NOT NULL, source_type text NOT NULL CHECK (source_type IN ('owned', 'competitor')), source_id text NOT NULL, author_name text NOT NULL DEFAULT '', title text NOT NULL DEFAULT '', body text NOT NULL DEFAULT '', published_at timestamptz, published_at_status text NOT NULL DEFAULT 'verified' CHECK (published_at_status IN ('verified', 'pending_verification', 'invalid', 'future')), original_url text NOT NULL DEFAULT '', cover_url text NOT NULL DEFAULT '', likes bigint, comments_count bigint, shares bigint, latest_metrics_at timestamptz, next_metric_at timestamptz, metric_stop_reason text NOT NULL DEFAULT '', created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), UNIQUE (platform, work_key) ); CREATE INDEX IF NOT EXISTS creator_work_source_idx ON creator_work (source_type, source_id, published_at DESC); CREATE TABLE IF NOT EXISTS creator_work_metric ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, work_id text NOT NULL REFERENCES creator_work(id) ON DELETE CASCADE, collected_at timestamptz NOT NULL DEFAULT now(), likes bigint, comments_count bigint, shares bigint, UNIQUE (work_id, collected_at) ); CREATE TABLE IF NOT EXISTS creator_material_job ( work_id text PRIMARY KEY REFERENCES creator_work(id) ON DELETE CASCADE, selected boolean NOT NULL DEFAULT false, select_confirmed_at timestamptz, download_status text NOT NULL DEFAULT 'not_started' CHECK (download_status IN ('not_started', 'running', 'succeeded', 'failed')), video_reference text NOT NULL DEFAULT '', audio_status text NOT NULL DEFAULT 'not_started' CHECK (audio_status IN ('not_started', 'running', 'succeeded', 'no_audio', 'failed')), audio_reference text NOT NULL DEFAULT '', transcription_status text NOT NULL DEFAULT 'not_started' CHECK (transcription_status IN ('not_started', 'running', 'succeeded', 'no_speech', 'failed')), transcript text NOT NULL DEFAULT '', failed_step text NOT NULL DEFAULT '', failure_reason text NOT NULL DEFAULT '', rewrite_confirmed_at timestamptz, rewrite_requirement text NOT NULL DEFAULT '', generated_title text NOT NULL DEFAULT '', generated_script text NOT NULL DEFAULT '', created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS creator_comment ( id text PRIMARY KEY, platform text NOT NULL CHECK (platform IN ('douyin', 'xiaohongshu')), comment_key text NOT NULL, work_id text NOT NULL REFERENCES creator_work(id) ON DELETE CASCADE, author_uid text NOT NULL DEFAULT '', author_name text NOT NULL DEFAULT '', content text NOT NULL, published_at timestamptz, collected_at timestamptz NOT NULL DEFAULT now(), comment_type text NOT NULL DEFAULT 'top_level' CHECK (comment_type IN ('top_level', 'reply', 'unknown')), UNIQUE (platform, comment_key) ); CREATE INDEX IF NOT EXISTS creator_comment_work_idx ON creator_comment (work_id, published_at DESC); CREATE TABLE IF NOT EXISTS creator_lead_rule ( id text PRIMARY KEY, name text NOT NULL, enabled boolean NOT NULL DEFAULT true, source_type text NOT NULL DEFAULT 'all' CHECK (source_type IN ('all', 'owned', 'competitor')), topic text NOT NULL, include_keywords jsonb NOT NULL, exclude_keywords jsonb NOT NULL DEFAULT '[]'::jsonb, ai_requirement text NOT NULL, updated_at timestamptz NOT NULL DEFAULT now(), created_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS creator_comment_rule_result ( comment_id text NOT NULL REFERENCES creator_comment(id) ON DELETE CASCADE, rule_id text NOT NULL REFERENCES creator_lead_rule(id) ON DELETE CASCADE, status text NOT NULL CHECK (status IN ('not_analysed', 'topic_no_match', 'keyword_no_match', 'not_lead', 'lead', 'failed')), reason text NOT NULL DEFAULT '', matched_keywords jsonb NOT NULL DEFAULT '[]'::jsonb, rule_snapshot jsonb NOT NULL, analysed_at timestamptz, PRIMARY KEY (comment_id, rule_id) ); CREATE TABLE IF NOT EXISTS creator_relation ( big_account_id text NOT NULL REFERENCES social_account(id) ON DELETE CASCADE, small_account_id text PRIMARY KEY REFERENCES social_account(id) ON DELETE CASCADE, created_at timestamptz NOT NULL DEFAULT now(), CHECK (big_account_id <> small_account_id) ); CREATE TABLE IF NOT EXISTS creator_strategy ( id text PRIMARY KEY, big_account_id text NOT NULL REFERENCES social_account(id) ON DELETE CASCADE, execution_account_id text NOT NULL REFERENCES social_account(id) ON DELETE RESTRICT, position integer NOT NULL CHECK (position > 0), enabled boolean NOT NULL DEFAULT true, event_types jsonb NOT NULL, action text NOT NULL CHECK (action IN ('dm', 'reply_comment', 'like_comment', 'like_work', 'follow', 'repost')), target_type text NOT NULL DEFAULT '', candidate_texts jsonb NOT NULL DEFAULT '[]'::jsonb, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), UNIQUE (big_account_id, position) ); CREATE TABLE IF NOT EXISTS creator_cooldown ( big_account_id text NOT NULL REFERENCES social_account(id) ON DELETE CASCADE, interactor_uid text NOT NULL, event_id text NOT NULL, strategy_id text NOT NULL, execution_account_id text NOT NULL, started_at timestamptz NOT NULL DEFAULT now(), expires_at timestamptz NOT NULL, PRIMARY KEY (big_account_id, interactor_uid) ); CREATE TABLE IF NOT EXISTS creator_event ( id text PRIMARY KEY, platform text NOT NULL CHECK (platform IN ('douyin', 'xiaohongshu')), receiving_account_id text NOT NULL REFERENCES social_account(id) ON DELETE CASCADE, event_key text NOT NULL, event_type text NOT NULL, interactor_uid text NOT NULL DEFAULT '', comment_id text, work_id text, platform_event_at timestamptz, received_at timestamptz NOT NULL DEFAULT now(), processing_started_at timestamptz, displayed_at timestamptz, state text NOT NULL DEFAULT 'received' CHECK (state IN ('received', 'baseline', 'ignored', 'unmatched', 'blocked', 'processing', 'succeeded', 'failed', 'uncertain')), reason text NOT NULL DEFAULT '', strategy_id text, execution_account_id text, UNIQUE (platform, receiving_account_id, event_key) ); CREATE TABLE IF NOT EXISTS creator_operation ( id text PRIMARY KEY, idempotency_key text NOT NULL UNIQUE, source text NOT NULL CHECK (source IN ('manual', 'automatic')), action text NOT NULL, platform text NOT NULL CHECK (platform IN ('douyin', 'xiaohongshu')), account_id text NOT NULL REFERENCES social_account(id) ON DELETE RESTRICT, target_uid text NOT NULL DEFAULT '', target_comment_id text NOT NULL DEFAULT '', target_work_id text NOT NULL DEFAULT '', text text NOT NULL DEFAULT '', event_id text, strategy_id text, request_hash text NOT NULL, state text NOT NULL DEFAULT 'created' CHECK (state IN ('created', 'processing', 'succeeded', 'failed', 'uncertain', 'blocked')), evidence jsonb NOT NULL DEFAULT '{}'::jsonb, reason text NOT NULL DEFAULT '', created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS creator_conversation ( id text PRIMARY KEY, platform text NOT NULL CHECK (platform IN ('douyin', 'xiaohongshu')), account_id text NOT NULL REFERENCES social_account(id) ON DELETE CASCADE, peer_uid text NOT NULL, peer_name text NOT NULL DEFAULT '', last_message_at timestamptz, UNIQUE (account_id, peer_uid) ); CREATE TABLE IF NOT EXISTS creator_message ( id text PRIMARY KEY, conversation_id text NOT NULL REFERENCES creator_conversation(id) ON DELETE CASCADE, platform_message_key text NOT NULL DEFAULT '', direction text NOT NULL CHECK (direction IN ('inbound', 'outbound')), message_type text NOT NULL DEFAULT 'text', text text NOT NULL DEFAULT '', sent_state text NOT NULL DEFAULT 'received' CHECK (sent_state IN ('received', 'pending', 'succeeded', 'failed', 'uncertain')), message_at timestamptz, created_at timestamptz NOT NULL DEFAULT now(), UNIQUE (conversation_id, platform_message_key) );