CREATE EXTENSION IF NOT EXISTS pgcrypto; CREATE TABLE users ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), external_ref text NOT NULL UNIQUE, created_at timestamptz NOT NULL DEFAULT now(), preferred_language text NOT NULL, device_platform text NOT NULL ); CREATE TABLE sessions ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, started_at timestamptz NOT NULL DEFAULT now(), ended_at timestamptz, experiment_variant text NOT NULL, app_version text NOT NULL, device_model text, os_version text, locale_code text NOT NULL ); CREATE TABLE events ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), sport_type text NOT NULL, source_ref text NOT NULL UNIQUE, title_en text NOT NULL, title_sv text NOT NULL, status text NOT NULL, preview_start_ms bigint NOT NULL, preview_end_ms bigint NOT NULL, reveal_start_ms bigint NOT NULL, reveal_end_ms bigint NOT NULL, lock_at timestamptz NOT NULL, settle_at timestamptz NOT NULL ); CREATE TABLE event_media ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), event_id uuid NOT NULL REFERENCES events(id) ON DELETE CASCADE, media_type text NOT NULL, hls_master_url text NOT NULL, poster_url text, duration_ms bigint NOT NULL, preview_start_ms bigint NOT NULL, preview_end_ms bigint NOT NULL, reveal_start_ms bigint NOT NULL, reveal_end_ms bigint NOT NULL, UNIQUE (event_id, media_type) ); CREATE TABLE markets ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), event_id uuid NOT NULL REFERENCES events(id) ON DELETE CASCADE, question_key text NOT NULL, market_type text NOT NULL, status text NOT NULL, lock_at timestamptz NOT NULL, settlement_rule_key text NOT NULL ); CREATE TABLE outcomes ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), market_id uuid NOT NULL REFERENCES markets(id) ON DELETE CASCADE, outcome_code text NOT NULL, label_key text NOT NULL, sort_order integer NOT NULL, UNIQUE (market_id, outcome_code) ); CREATE TABLE odds_versions ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), market_id uuid NOT NULL REFERENCES markets(id) ON DELETE CASCADE, version_no integer NOT NULL, created_at timestamptz NOT NULL DEFAULT now(), is_current boolean NOT NULL DEFAULT false, UNIQUE (market_id, version_no) ); CREATE UNIQUE INDEX odds_versions_current_idx ON odds_versions (market_id) WHERE is_current; CREATE TABLE outcome_odds ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), odds_version_id uuid NOT NULL REFERENCES odds_versions(id) ON DELETE CASCADE, outcome_id uuid NOT NULL REFERENCES outcomes(id) ON DELETE CASCADE, decimal_odds numeric(10,4) NOT NULL, fractional_num integer NOT NULL, fractional_den integer NOT NULL, UNIQUE (odds_version_id, outcome_id) ); CREATE TABLE bet_intents ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL REFERENCES users(id), session_id uuid NOT NULL REFERENCES sessions(id), event_id uuid NOT NULL REFERENCES events(id), market_id uuid NOT NULL REFERENCES markets(id), outcome_id uuid NOT NULL REFERENCES outcomes(id), idempotency_key text NOT NULL UNIQUE, client_sent_at timestamptz NOT NULL, server_received_at timestamptz NOT NULL DEFAULT now(), accepted boolean NOT NULL, acceptance_code text NOT NULL, accepted_odds_version_id uuid REFERENCES odds_versions(id) ); CREATE TABLE settlements ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), market_id uuid NOT NULL UNIQUE REFERENCES markets(id) ON DELETE CASCADE, settled_at timestamptz NOT NULL, winning_outcome_id uuid NOT NULL REFERENCES outcomes(id) ); CREATE TABLE experiment_assignments ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, session_id uuid NOT NULL REFERENCES sessions(id) ON DELETE CASCADE, variant text NOT NULL, assigned_at timestamptz NOT NULL DEFAULT now(), UNIQUE (session_id) ); CREATE TABLE localization_keys ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), key_name text NOT NULL UNIQUE, description text NOT NULL ); CREATE TABLE localization_values ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), localization_key_id uuid NOT NULL REFERENCES localization_keys(id) ON DELETE CASCADE, locale_code text NOT NULL, text_value text NOT NULL, UNIQUE (localization_key_id, locale_code) ); CREATE TABLE analytics_event_types ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), event_name text NOT NULL UNIQUE, description text NOT NULL ); CREATE TABLE analytics_events ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), analytics_event_type_id uuid NOT NULL REFERENCES analytics_event_types(id), session_id uuid NOT NULL REFERENCES sessions(id) ON DELETE CASCADE, user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, occurred_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE analytics_event_attributes ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), analytics_event_id uuid NOT NULL REFERENCES analytics_events(id) ON DELETE CASCADE, attribute_key text NOT NULL, attribute_value text NOT NULL ); CREATE TABLE audit_logs ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), created_at timestamptz NOT NULL DEFAULT now(), actor_type text NOT NULL, actor_id uuid, action_name text NOT NULL, target_type text NOT NULL, target_id uuid, trace_id text NOT NULL, note text ); CREATE TABLE audit_log_attributes ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), audit_log_id uuid NOT NULL REFERENCES audit_logs(id) ON DELETE CASCADE, attribute_key text NOT NULL, attribute_value text NOT NULL ); CREATE INDEX sessions_user_id_idx ON sessions (user_id); CREATE INDEX sessions_started_at_idx ON sessions (started_at); CREATE INDEX events_status_idx ON events (status); CREATE INDEX markets_event_id_idx ON markets (event_id); CREATE INDEX markets_lock_at_idx ON markets (lock_at); CREATE INDEX bet_intents_session_id_idx ON bet_intents (session_id); CREATE INDEX bet_intents_market_id_idx ON bet_intents (market_id); CREATE INDEX bet_intents_idempotency_key_idx ON bet_intents (idempotency_key); CREATE INDEX analytics_events_session_id_idx ON analytics_events (session_id); CREATE INDEX analytics_events_user_id_idx ON analytics_events (user_id); CREATE INDEX analytics_events_type_id_idx ON analytics_events (analytics_event_type_id); CREATE INDEX audit_logs_created_at_idx ON audit_logs (created_at);