diff --git a/supabase/kong.yml b/supabase/kong.yml new file mode 100644 index 0000000..c28d99a --- /dev/null +++ b/supabase/kong.yml @@ -0,0 +1,9 @@ +_format_version: "2.1" + +services: + - name: postgrest + url: http://rest:3000 + routes: + - name: rest-v1 + paths: + - /rest/v1/ diff --git a/supabase/migrations/00-run-init.sh b/supabase/migrations/00-run-init.sh new file mode 100755 index 0000000..bfb5279 --- /dev/null +++ b/supabase/migrations/00-run-init.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -e + +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<'EOSQL' + +-- Create authenticator role (no-login, used by PostgREST) +DO $$ +BEGIN + IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'authenticator') THEN + CREATE ROLE authenticator NOLOGIN; + END IF; +END +$$; + +-- Grant authenticator usage on public schema and read access to all tables +GRANT USAGE ON SCHEMA public TO authenticator; +GRANT SELECT ON ALL TABLES IN SCHEMA public TO authenticator; +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO authenticator; + +EOSQL diff --git a/supabase/migrations/01-init.sql b/supabase/migrations/01-init.sql new file mode 100644 index 0000000..54dd98e --- /dev/null +++ b/supabase/migrations/01-init.sql @@ -0,0 +1,103 @@ +-- ============================================================ +-- 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_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); diff --git a/supabase/migrations/post-boot.sql b/supabase/migrations/post-boot.sql new file mode 100644 index 0000000..b5cec10 --- /dev/null +++ b/supabase/migrations/post-boot.sql @@ -0,0 +1,8 @@ +-- ============================================================ +-- post-boot — runs after all migrations have been applied. +-- Grants INSERT/UPDATE to authenticator on user-facing tables. +-- (Run after 01-init.sql so tables exist.) +-- ============================================================ + +GRANT INSERT, UPDATE ON search_queries TO authenticator; +GRANT INSERT, UPDATE ON notifications TO authenticator;