CREATE EXTENSION IF NOT EXISTS "pgcrypto"; CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'user' CHECK (role IN ('admin', 'user')), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE user_assets ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, symbol VARCHAR(20) NOT NULL, name TEXT NOT NULL DEFAULT '', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE (user_id, symbol) ); CREATE TABLE sources ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name TEXT NOT NULL, type TEXT NOT NULL CHECK (type IN ('bloomberg', 'stocktwits', 'reuters', 'watcherguru')), enabled BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE articles ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), source_id UUID NOT NULL REFERENCES sources(id) ON DELETE CASCADE, title TEXT NOT NULL, content TEXT NOT NULL DEFAULT '', url TEXT NOT NULL UNIQUE, published_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE article_symbols ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), article_id UUID NOT NULL REFERENCES articles(id) ON DELETE CASCADE, symbol VARCHAR(20) NOT NULL, UNIQUE (article_id, symbol) ); CREATE TABLE scrape_credentials ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), source_id UUID NOT NULL REFERENCES sources(id) ON DELETE CASCADE UNIQUE, username TEXT NOT NULL DEFAULT '', password_encrypted TEXT NOT NULL DEFAULT '', updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE scrape_jobs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), source_id UUID NOT NULL REFERENCES sources(id) ON DELETE CASCADE, status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'running', 'done', 'error')), started_at TIMESTAMPTZ, finished_at TIMESTAMPTZ, articles_found INT NOT NULL DEFAULT 0, error_msg TEXT NOT NULL DEFAULT '', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE ai_providers ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name TEXT NOT NULL CHECK (name IN ('openai', 'anthropic', 'gemini', 'ollama')), api_key_encrypted TEXT NOT NULL DEFAULT '', model TEXT NOT NULL DEFAULT '', endpoint TEXT NOT NULL DEFAULT '', is_active BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE summaries ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, content TEXT NOT NULL, ai_provider_id UUID REFERENCES ai_providers(id) ON DELETE SET NULL, generated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE settings ( key TEXT PRIMARY KEY, value TEXT NOT NULL DEFAULT '' ); -- Index pour les performances CREATE INDEX idx_articles_source_id ON articles(source_id); CREATE INDEX idx_articles_published_at ON articles(published_at DESC); CREATE INDEX idx_article_symbols_symbol ON article_symbols(symbol); CREATE INDEX idx_summaries_user_id ON summaries(user_id); CREATE INDEX idx_summaries_generated_at ON summaries(generated_at DESC); CREATE INDEX idx_scrape_jobs_status ON scrape_jobs(status); CREATE INDEX idx_user_assets_user_id ON user_assets(user_id); -- Sources initiales INSERT INTO sources (name, type, enabled) VALUES ('Bloomberg', 'bloomberg', TRUE), ('Yahoo Finance', 'stocktwits', TRUE); -- Paramètres par défaut INSERT INTO settings (key, value) VALUES ('scrape_interval_minutes', '60'), ('articles_lookback_hours', '24'), ('summary_max_articles', '50');