-- Article pipeline: track articles from discovery through posting CREATE TABLE IF NOT EXISTS official_account_articles ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), config_id UUID NOT NULL REFERENCES official_account_configs(id) ON DELETE CASCADE, guid TEXT NOT NULL, -- unique article ID (Google News URL or RSS GUID) title TEXT NOT NULL DEFAULT '', link TEXT NOT NULL, -- resolved URL (what gets posted) source_name TEXT NOT NULL DEFAULT '', source_url TEXT NOT NULL DEFAULT '', description TEXT NOT NULL DEFAULT '', pub_date TIMESTAMPTZ, status TEXT NOT NULL DEFAULT 'discovered', -- discovered | posted | failed | skipped post_id UUID REFERENCES public.posts(id) ON DELETE SET NULL, error_message TEXT, discovered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), posted_at TIMESTAMPTZ, CONSTRAINT unique_article_guid_per_config UNIQUE (config_id, guid) ); CREATE INDEX IF NOT EXISTS idx_oaa_config_status ON official_account_articles(config_id, status); CREATE INDEX IF NOT EXISTS idx_oaa_discovered ON official_account_articles(discovered_at DESC); CREATE INDEX IF NOT EXISTS idx_oaa_guid ON official_account_articles(guid); -- Migrate existing posted articles into the new table INSERT INTO official_account_articles (config_id, guid, title, link, source_name, status, post_id, discovered_at, posted_at) SELECT config_id, article_url, article_title, article_url, source_name, 'posted', post_id, posted_at, posted_at FROM official_account_posted_articles ON CONFLICT (config_id, guid) DO NOTHING;