105 lines
4.3 KiB
SQL
105 lines
4.3 KiB
SQL
-- ============================================================
|
|
-- willhaben-tracker — initial schema migration
|
|
-- ============================================================
|
|
|
|
-- -----------------------------------------------------------
|
|
-- 1. users (whitelisted Telegram users)
|
|
-- -----------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
telegram_id bigint UNIQUE NOT NULL,
|
|
username text,
|
|
first_name text,
|
|
is_admin boolean NOT NULL DEFAULT false,
|
|
is_active boolean NOT NULL DEFAULT true,
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- -----------------------------------------------------------
|
|
-- 2. search_queries (saved searches per user)
|
|
-- -----------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS search_queries (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id uuid REFERENCES users(id) ON DELETE CASCADE NOT NULL,
|
|
keyword text NOT NULL,
|
|
interval_minutes int NOT NULL DEFAULT 60,
|
|
is_active boolean NOT NULL DEFAULT true,
|
|
last_scraped_at timestamptz,
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- -----------------------------------------------------------
|
|
-- 3. ads (raw ad snapshots, globally deduplicated by wh_ad_id)
|
|
-- -----------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS ads (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
wh_ad_id text UNIQUE NOT NULL,
|
|
raw_json jsonb NOT NULL,
|
|
title text NOT NULL,
|
|
price numeric,
|
|
location text,
|
|
url text,
|
|
published_at timestamptz,
|
|
first_seen_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- -----------------------------------------------------------
|
|
-- 4. query_ads (junction: which query found which ad)
|
|
-- -----------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS query_ads (
|
|
search_query_id uuid REFERENCES search_queries(id) ON DELETE CASCADE NOT NULL,
|
|
ad_id uuid REFERENCES ads(id) ON DELETE CASCADE NOT NULL,
|
|
first_seen_at timestamptz NOT NULL DEFAULT now(),
|
|
is_notified boolean NOT NULL DEFAULT false,
|
|
PRIMARY KEY (search_query_id, ad_id)
|
|
);
|
|
|
|
-- -----------------------------------------------------------
|
|
-- 5. notifications (audit log of sent Telegram messages)
|
|
-- -----------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS notifications (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id uuid REFERENCES users(id) ON DELETE CASCADE NOT NULL,
|
|
ad_id uuid REFERENCES ads(id) ON DELETE SET NULL,
|
|
message_id int,
|
|
sent_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- -----------------------------------------------------------
|
|
-- 6. scrape_logs (worker health / debugging)
|
|
-- -----------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS scrape_logs (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
search_query_id uuid REFERENCES search_queries(id) ON DELETE CASCADE NOT NULL,
|
|
status text NOT NULL CHECK (status IN ('success', 'error', 'rate_limited')),
|
|
ads_found int NOT NULL DEFAULT 0,
|
|
new_ads int NOT NULL DEFAULT 0,
|
|
error_message text,
|
|
scraped_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- ============================================================
|
|
-- Indexes
|
|
-- ============================================================
|
|
|
|
-- Users: fast lookup by telegram_id (unique constraint already implies an index)
|
|
|
|
-- Search queries: user membership lookups
|
|
CREATE INDEX IF NOT EXISTS idx_search_queries_user_id ON search_queries(user_id);
|
|
|
|
-- Scheduler polling: active queries ordered by last scraped time
|
|
CREATE INDEX IF NOT EXISTS idx_search_queries_active_scraped
|
|
ON search_queries(is_active, last_scraped_at) WHERE is_active = true;
|
|
|
|
-- Notifier lookups: un-notified ads per query
|
|
CREATE INDEX IF NOT EXISTS idx_query_ads_notified
|
|
ON query_ads(search_query_id, is_notified) WHERE is_notified = false;
|
|
|
|
-- Notifications: recent messages per user
|
|
CREATE INDEX IF NOT EXISTS idx_notifications_user_sent
|
|
ON notifications(user_id, sent_at DESC);
|
|
|
|
-- Scrape logs: latest runs per query
|
|
CREATE INDEX IF NOT EXISTS idx_scrape_logs_query_at
|
|
ON scrape_logs(search_query_id, scraped_at DESC);
|