-- BoosAPI initial schema -- Generated from the existing SQLite database (hub.db) -- This migration creates all tables, indexes, and constraints. -- ============================================================ -- SITES -- ============================================================ CREATE TABLE IF NOT EXISTS sites ( id BIGSERIAL PRIMARY KEY, name TEXT NOT NULL, url TEXT NOT NULL, platform TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'active', api_key TEXT, proxy_url TEXT, use_system_proxy BOOLEAN DEFAULT FALSE, custom_headers TEXT, external_checkin_url TEXT, global_weight DOUBLE PRECISION DEFAULT 1, post_refresh_probe_enabled BOOLEAN DEFAULT FALSE, post_refresh_probe_model TEXT DEFAULT '', post_refresh_probe_scope TEXT DEFAULT 'single', post_refresh_probe_latency_threshold_ms BIGINT DEFAULT 0, is_pinned BOOLEAN DEFAULT FALSE, sort_order BIGINT DEFAULT 0, created_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), updated_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT) ); CREATE UNIQUE INDEX IF NOT EXISTS sites_platform_url_unique ON sites (platform, url); CREATE INDEX IF NOT EXISTS sites_status_idx ON sites (status); -- ============================================================ -- USERS -- ============================================================ CREATE TABLE IF NOT EXISTS users ( id BIGSERIAL PRIMARY KEY, username TEXT NOT NULL, email TEXT NOT NULL, password_hash TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'user', status TEXT NOT NULL DEFAULT 'active', created_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), updated_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT) ); CREATE UNIQUE INDEX IF NOT EXISTS users_email_unique ON users (email); CREATE INDEX IF NOT EXISTS users_status_idx ON users (status); -- ============================================================ -- TOKEN ROUTES -- ============================================================ CREATE TABLE IF NOT EXISTS token_routes ( id BIGSERIAL PRIMARY KEY, model_pattern TEXT NOT NULL, model_mapping TEXT, display_name TEXT, display_icon TEXT, decision_snapshot TEXT, decision_refreshed_at TEXT, routing_strategy TEXT DEFAULT 'weighted', route_mode TEXT DEFAULT 'pattern', enabled BOOLEAN DEFAULT TRUE, created_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), updated_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT) ); CREATE INDEX IF NOT EXISTS token_routes_model_pattern_idx ON token_routes (model_pattern); CREATE INDEX IF NOT EXISTS token_routes_enabled_idx ON token_routes (enabled); -- ============================================================ -- ACCOUNTS -- ============================================================ CREATE TABLE IF NOT EXISTS accounts ( id BIGSERIAL PRIMARY KEY, site_id BIGINT NOT NULL, username TEXT, access_token TEXT NOT NULL, api_token TEXT, balance DOUBLE PRECISION DEFAULT 0, balance_used DOUBLE PRECISION DEFAULT 0, quota DOUBLE PRECISION DEFAULT 0, unit_cost DOUBLE PRECISION, value_score DOUBLE PRECISION DEFAULT 0, status TEXT DEFAULT 'active', checkin_enabled BOOLEAN DEFAULT TRUE, last_checkin_at TEXT, last_balance_refresh TEXT, extra_config TEXT, is_pinned BOOLEAN DEFAULT FALSE, sort_order BIGINT DEFAULT 0, oauth_provider TEXT, oauth_account_key TEXT, oauth_project_id TEXT, created_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), updated_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), FOREIGN KEY (site_id) REFERENCES sites(id) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS accounts_site_id_idx ON accounts (site_id); CREATE INDEX IF NOT EXISTS accounts_status_idx ON accounts (status); CREATE INDEX IF NOT EXISTS accounts_site_status_idx ON accounts (site_id, status); CREATE INDEX IF NOT EXISTS accounts_oauth_provider_idx ON accounts (oauth_provider); CREATE INDEX IF NOT EXISTS accounts_oauth_identity_idx ON accounts (oauth_provider, oauth_account_key, oauth_project_id); -- ============================================================ -- DOWNSTREAM API KEYS -- ============================================================ CREATE TABLE IF NOT EXISTS downstream_api_keys ( id BIGSERIAL PRIMARY KEY, name TEXT NOT NULL, key TEXT NOT NULL, description TEXT, group_name TEXT, tags TEXT, enabled BOOLEAN DEFAULT TRUE, expires_at TEXT, max_cost DOUBLE PRECISION, used_cost DOUBLE PRECISION DEFAULT 0, max_requests BIGINT, used_requests BIGINT DEFAULT 0, supported_models TEXT, allowed_route_ids TEXT, site_weight_multipliers TEXT, excluded_site_ids TEXT, excluded_credential_refs TEXT, last_used_at TEXT, user_id BIGINT REFERENCES users(id), created_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), updated_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT) ); CREATE UNIQUE INDEX IF NOT EXISTS downstream_api_keys_key_unique ON downstream_api_keys (key); CREATE INDEX IF NOT EXISTS downstream_api_keys_name_idx ON downstream_api_keys (name); CREATE INDEX IF NOT EXISTS downstream_api_keys_enabled_idx ON downstream_api_keys (enabled); CREATE INDEX IF NOT EXISTS downstream_api_keys_expires_at_idx ON downstream_api_keys (expires_at); -- ============================================================ -- SITE DISABLED MODELS -- ============================================================ CREATE TABLE IF NOT EXISTS site_disabled_models ( id BIGSERIAL PRIMARY KEY, site_id BIGINT NOT NULL, model_name TEXT NOT NULL, created_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), FOREIGN KEY (site_id) REFERENCES sites(id) ON DELETE CASCADE ); CREATE UNIQUE INDEX IF NOT EXISTS site_disabled_models_site_model_unique ON site_disabled_models (site_id, model_name); CREATE INDEX IF NOT EXISTS site_disabled_models_site_id_idx ON site_disabled_models (site_id); -- ============================================================ -- SITE API ENDPOINTS -- ============================================================ CREATE TABLE IF NOT EXISTS site_api_endpoints ( id BIGSERIAL PRIMARY KEY, site_id BIGINT NOT NULL, url TEXT NOT NULL, enabled BOOLEAN DEFAULT TRUE, sort_order BIGINT DEFAULT 0, cooldown_until TEXT, last_selected_at TEXT, last_failed_at TEXT, last_failure_reason TEXT, created_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), updated_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), FOREIGN KEY (site_id) REFERENCES sites(id) ON DELETE CASCADE ); CREATE UNIQUE INDEX IF NOT EXISTS site_api_endpoints_site_url_unique ON site_api_endpoints (site_id, url); CREATE INDEX IF NOT EXISTS site_api_endpoints_site_enabled_sort_idx ON site_api_endpoints (site_id, enabled, sort_order); CREATE INDEX IF NOT EXISTS site_api_endpoints_site_cooldown_idx ON site_api_endpoints (site_id, cooldown_until); -- ============================================================ -- ACCOUNT TOKENS -- ============================================================ CREATE TABLE IF NOT EXISTS account_tokens ( id BIGSERIAL PRIMARY KEY, account_id BIGINT NOT NULL, name TEXT NOT NULL, token TEXT NOT NULL, source TEXT DEFAULT 'manual', token_group TEXT, value_status TEXT NOT NULL DEFAULT 'ready', enabled BOOLEAN DEFAULT TRUE, is_default BOOLEAN DEFAULT FALSE, created_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), updated_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS account_tokens_account_id_idx ON account_tokens (account_id); CREATE INDEX IF NOT EXISTS account_tokens_account_enabled_idx ON account_tokens (account_id, enabled); CREATE INDEX IF NOT EXISTS account_tokens_enabled_idx ON account_tokens (enabled); -- ============================================================ -- CHECKIN LOGS -- ============================================================ CREATE TABLE IF NOT EXISTS checkin_logs ( id BIGSERIAL PRIMARY KEY, account_id BIGINT NOT NULL, status TEXT NOT NULL, message TEXT, reward TEXT, created_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS checkin_logs_account_created_at_idx ON checkin_logs (account_id, created_at); CREATE INDEX IF NOT EXISTS checkin_logs_created_at_idx ON checkin_logs (created_at); CREATE INDEX IF NOT EXISTS checkin_logs_status_idx ON checkin_logs (status); -- ============================================================ -- MODEL AVAILABILITY -- ============================================================ CREATE TABLE IF NOT EXISTS model_availability ( id BIGSERIAL PRIMARY KEY, account_id BIGINT NOT NULL, model_name TEXT NOT NULL, available BIGINT, latency_ms BIGINT, checked_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), is_manual BOOLEAN DEFAULT FALSE, FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE ); CREATE UNIQUE INDEX IF NOT EXISTS model_availability_account_model_unique ON model_availability (account_id, model_name); CREATE INDEX IF NOT EXISTS model_availability_account_available_idx ON model_availability (account_id, available); CREATE INDEX IF NOT EXISTS model_availability_model_name_idx ON model_availability (model_name); -- ============================================================ -- TOKEN MODEL AVAILABILITY -- ============================================================ CREATE TABLE IF NOT EXISTS token_model_availability ( id BIGSERIAL PRIMARY KEY, token_id BIGINT NOT NULL, model_name TEXT NOT NULL, available BIGINT, latency_ms BIGINT, checked_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), FOREIGN KEY (token_id) REFERENCES account_tokens(id) ON DELETE CASCADE ); CREATE UNIQUE INDEX IF NOT EXISTS token_model_availability_token_model_unique ON token_model_availability (token_id, model_name); CREATE INDEX IF NOT EXISTS token_model_availability_token_available_idx ON token_model_availability (token_id, available); CREATE INDEX IF NOT EXISTS token_model_availability_model_name_idx ON token_model_availability (model_name); CREATE INDEX IF NOT EXISTS token_model_availability_available_idx ON token_model_availability (available); -- ============================================================ -- ROUTE CHANNELS -- ============================================================ CREATE TABLE IF NOT EXISTS route_channels ( id BIGSERIAL PRIMARY KEY, route_id BIGINT NOT NULL, account_id BIGINT NOT NULL, token_id BIGINT, oauth_route_unit_id BIGINT, source_model TEXT, priority BIGINT DEFAULT 0, weight BIGINT DEFAULT 10, enabled BOOLEAN DEFAULT TRUE, manual_override BOOLEAN DEFAULT FALSE, success_count BIGINT DEFAULT 0, fail_count BIGINT DEFAULT 0, total_latency_ms BIGINT DEFAULT 0, total_cost DOUBLE PRECISION DEFAULT 0, last_used_at TEXT, last_selected_at TEXT, last_fail_at TEXT, consecutive_fail_count BIGINT NOT NULL DEFAULT 0, cooldown_level BIGINT NOT NULL DEFAULT 0, cooldown_until TEXT, FOREIGN KEY (route_id) REFERENCES token_routes(id) ON DELETE CASCADE, FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE, FOREIGN KEY (token_id) REFERENCES account_tokens(id) ON DELETE SET NULL ); CREATE INDEX IF NOT EXISTS route_channels_route_id_idx ON route_channels (route_id); CREATE INDEX IF NOT EXISTS route_channels_account_id_idx ON route_channels (account_id); CREATE INDEX IF NOT EXISTS route_channels_token_id_idx ON route_channels (token_id); CREATE INDEX IF NOT EXISTS route_channels_route_enabled_idx ON route_channels (route_id, enabled); CREATE INDEX IF NOT EXISTS route_channels_route_token_idx ON route_channels (route_id, token_id); CREATE INDEX IF NOT EXISTS route_channels_oauth_route_unit_id_idx ON route_channels (oauth_route_unit_id); -- ============================================================ -- ROUTE GROUP SOURCES -- ============================================================ CREATE TABLE IF NOT EXISTS route_group_sources ( id BIGSERIAL PRIMARY KEY, group_route_id BIGINT NOT NULL, source_route_id BIGINT NOT NULL, FOREIGN KEY (group_route_id) REFERENCES token_routes(id) ON DELETE CASCADE, FOREIGN KEY (source_route_id) REFERENCES token_routes(id) ON DELETE CASCADE ); CREATE UNIQUE INDEX IF NOT EXISTS route_group_sources_group_source_unique ON route_group_sources (group_route_id, source_route_id); CREATE INDEX IF NOT EXISTS route_group_sources_source_route_id_idx ON route_group_sources (source_route_id); -- ============================================================ -- PROXY LOGS -- ============================================================ CREATE TABLE IF NOT EXISTS proxy_logs ( id BIGSERIAL PRIMARY KEY, route_id BIGINT, channel_id BIGINT, account_id BIGINT, downstream_api_key_id BIGINT, model_requested TEXT, model_actual TEXT, status TEXT, http_status BIGINT, latency_ms BIGINT, first_byte_latency_ms BIGINT, prompt_tokens BIGINT, completion_tokens BIGINT, total_tokens BIGINT, estimated_cost DOUBLE PRECISION, error_message TEXT, retry_count BIGINT DEFAULT 0, is_stream BIGINT, client_family TEXT, client_app_id TEXT, client_app_name TEXT, client_confidence TEXT, billing_details TEXT, created_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT) ); CREATE INDEX IF NOT EXISTS proxy_logs_created_at_idx ON proxy_logs (created_at); CREATE INDEX IF NOT EXISTS proxy_logs_account_created_at_idx ON proxy_logs (account_id, created_at); CREATE INDEX IF NOT EXISTS proxy_logs_status_created_at_idx ON proxy_logs (status, created_at); CREATE INDEX IF NOT EXISTS proxy_logs_model_actual_created_at_idx ON proxy_logs (model_actual, created_at); CREATE INDEX IF NOT EXISTS proxy_logs_downstream_api_key_created_at_idx ON proxy_logs (downstream_api_key_id, created_at); CREATE INDEX IF NOT EXISTS proxy_logs_client_app_id_created_at_idx ON proxy_logs (client_app_id, created_at); CREATE INDEX IF NOT EXISTS proxy_logs_client_family_created_at_idx ON proxy_logs (client_family, created_at); -- ============================================================ -- PROXY VIDEO TASKS -- ============================================================ CREATE TABLE IF NOT EXISTS proxy_video_tasks ( id BIGSERIAL PRIMARY KEY, public_id TEXT NOT NULL, upstream_video_id TEXT NOT NULL, site_url TEXT NOT NULL, token_value TEXT NOT NULL, requested_model TEXT, actual_model TEXT, channel_id BIGINT, account_id BIGINT, status_snapshot TEXT, upstream_response_meta TEXT, last_upstream_status BIGINT, last_polled_at TEXT, created_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), updated_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT) ); CREATE UNIQUE INDEX IF NOT EXISTS proxy_video_tasks_public_id_unique ON proxy_video_tasks (public_id); CREATE INDEX IF NOT EXISTS proxy_video_tasks_upstream_video_id_idx ON proxy_video_tasks (upstream_video_id); CREATE INDEX IF NOT EXISTS proxy_video_tasks_created_at_idx ON proxy_video_tasks (created_at); -- ============================================================ -- PROXY FILES -- ============================================================ CREATE TABLE IF NOT EXISTS proxy_files ( id BIGSERIAL PRIMARY KEY, public_id TEXT NOT NULL, owner_type TEXT NOT NULL, owner_id TEXT NOT NULL, filename TEXT NOT NULL, mime_type TEXT NOT NULL, purpose TEXT, byte_size BIGINT NOT NULL, sha256 TEXT NOT NULL, content_base64 TEXT NOT NULL, created_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), updated_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), deleted_at TEXT ); CREATE UNIQUE INDEX IF NOT EXISTS proxy_files_public_id_unique ON proxy_files (public_id); CREATE INDEX IF NOT EXISTS proxy_files_owner_lookup_idx ON proxy_files (owner_type, owner_id, deleted_at); -- ============================================================ -- PROXY DEBUG TRACES -- ============================================================ CREATE TABLE IF NOT EXISTS proxy_debug_traces ( id BIGSERIAL PRIMARY KEY, downstream_path TEXT NOT NULL, client_kind TEXT, session_id TEXT, trace_hint TEXT, requested_model TEXT, downstream_api_key_id BIGINT, request_headers_json TEXT, request_body_json TEXT, sticky_session_key TEXT, sticky_hit_channel_id BIGINT, selected_channel_id BIGINT, selected_route_id BIGINT, selected_account_id BIGINT, selected_site_id BIGINT, selected_site_platform TEXT, endpoint_candidates_json TEXT, endpoint_runtime_state_json TEXT, decision_summary_json TEXT, final_status TEXT, final_http_status BIGINT, final_upstream_path TEXT, final_response_headers_json TEXT, final_response_body_json TEXT, created_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), updated_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT) ); CREATE INDEX IF NOT EXISTS proxy_debug_traces_created_at_idx ON proxy_debug_traces (created_at); CREATE INDEX IF NOT EXISTS proxy_debug_traces_session_created_at_idx ON proxy_debug_traces (session_id, created_at); CREATE INDEX IF NOT EXISTS proxy_debug_traces_model_created_at_idx ON proxy_debug_traces (requested_model, created_at); CREATE INDEX IF NOT EXISTS proxy_debug_traces_final_status_created_at_idx ON proxy_debug_traces (final_status, created_at); -- ============================================================ -- PROXY DEBUG ATTEMPTS -- ============================================================ CREATE TABLE IF NOT EXISTS proxy_debug_attempts ( id BIGSERIAL PRIMARY KEY, trace_id BIGINT NOT NULL, attempt_index BIGINT NOT NULL, endpoint TEXT NOT NULL, request_path TEXT NOT NULL, target_url TEXT NOT NULL, runtime_executor TEXT, request_headers_json TEXT, request_body_json TEXT, response_status BIGINT, response_headers_json TEXT, response_body_json TEXT, raw_error_text TEXT, recover_applied BOOLEAN DEFAULT FALSE, downgrade_decision BOOLEAN DEFAULT FALSE, downgrade_reason TEXT, memory_write_json TEXT, created_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), FOREIGN KEY (trace_id) REFERENCES proxy_debug_traces(id) ON DELETE CASCADE ); CREATE UNIQUE INDEX IF NOT EXISTS proxy_debug_attempts_trace_attempt_unique ON proxy_debug_attempts (trace_id, attempt_index); CREATE INDEX IF NOT EXISTS proxy_debug_attempts_trace_created_at_idx ON proxy_debug_attempts (trace_id, created_at); -- ============================================================ -- EVENTS -- ============================================================ CREATE TABLE IF NOT EXISTS events ( id BIGSERIAL PRIMARY KEY, type TEXT NOT NULL, title TEXT NOT NULL, message TEXT, level TEXT NOT NULL DEFAULT 'info', "read" BOOLEAN DEFAULT FALSE, related_id BIGINT, related_type TEXT, created_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT) ); CREATE INDEX IF NOT EXISTS events_read_created_at_idx ON events ("read", created_at); CREATE INDEX IF NOT EXISTS events_type_created_at_idx ON events (type, created_at); CREATE INDEX IF NOT EXISTS events_created_at_idx ON events (created_at); -- ============================================================ -- SITE ANNOUNCEMENTS -- ============================================================ CREATE TABLE IF NOT EXISTS site_announcements ( id BIGSERIAL PRIMARY KEY, site_id BIGINT NOT NULL, platform TEXT NOT NULL, source_key TEXT NOT NULL, title TEXT NOT NULL, content TEXT NOT NULL, level TEXT NOT NULL DEFAULT 'info', source_url TEXT, starts_at TEXT, ends_at TEXT, upstream_created_at TEXT, upstream_updated_at TEXT, first_seen_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), last_seen_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), read_at TEXT, dismissed_at TEXT, raw_payload TEXT, FOREIGN KEY (site_id) REFERENCES sites(id) ON DELETE CASCADE ); CREATE UNIQUE INDEX IF NOT EXISTS site_announcements_site_source_key_unique ON site_announcements (site_id, source_key); CREATE INDEX IF NOT EXISTS site_announcements_site_id_first_seen_at_idx ON site_announcements (site_id, first_seen_at); CREATE INDEX IF NOT EXISTS site_announcements_read_at_idx ON site_announcements (read_at); -- ============================================================ -- OAUTH ROUTE UNITS -- ============================================================ CREATE TABLE IF NOT EXISTS oauth_route_units ( id BIGSERIAL PRIMARY KEY, site_id BIGINT NOT NULL, provider TEXT NOT NULL, name TEXT NOT NULL, strategy TEXT NOT NULL DEFAULT 'round_robin', enabled BOOLEAN DEFAULT TRUE, created_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), updated_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), FOREIGN KEY (site_id) REFERENCES sites(id) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS oauth_route_units_site_provider_idx ON oauth_route_units (site_id, provider); CREATE INDEX IF NOT EXISTS oauth_route_units_enabled_idx ON oauth_route_units (enabled); -- ============================================================ -- OAUTH ROUTE UNIT MEMBERS -- ============================================================ CREATE TABLE IF NOT EXISTS oauth_route_unit_members ( id BIGSERIAL PRIMARY KEY, unit_id BIGINT NOT NULL, account_id BIGINT NOT NULL, sort_order BIGINT DEFAULT 0, success_count BIGINT DEFAULT 0, fail_count BIGINT DEFAULT 0, total_latency_ms BIGINT DEFAULT 0, total_cost DOUBLE PRECISION DEFAULT 0, last_used_at TEXT, last_selected_at TEXT, last_fail_at TEXT, consecutive_fail_count BIGINT NOT NULL DEFAULT 0, cooldown_level BIGINT NOT NULL DEFAULT 0, cooldown_until TEXT, created_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), updated_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), FOREIGN KEY (unit_id) REFERENCES oauth_route_units(id) ON DELETE CASCADE, FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE ); CREATE UNIQUE INDEX IF NOT EXISTS oauth_route_unit_members_unit_account_unique ON oauth_route_unit_members (unit_id, account_id); CREATE UNIQUE INDEX IF NOT EXISTS oauth_route_unit_members_account_unique ON oauth_route_unit_members (account_id); CREATE INDEX IF NOT EXISTS oauth_route_unit_members_unit_sort_idx ON oauth_route_unit_members (unit_id, sort_order); CREATE INDEX IF NOT EXISTS oauth_route_unit_members_unit_cooldown_idx ON oauth_route_unit_members (unit_id, cooldown_until); -- ============================================================ -- SETTINGS -- ============================================================ CREATE TABLE IF NOT EXISTS settings ( key TEXT PRIMARY KEY NOT NULL, value TEXT ); -- ============================================================ -- ADMIN SNAPSHOTS -- ============================================================ CREATE TABLE IF NOT EXISTS admin_snapshots ( id BIGSERIAL PRIMARY KEY, namespace TEXT NOT NULL, snapshot_key TEXT NOT NULL, payload TEXT NOT NULL, generated_at TEXT NOT NULL, expires_at TEXT NOT NULL, stale_until TEXT NOT NULL, created_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), updated_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT) ); CREATE UNIQUE INDEX IF NOT EXISTS admin_snapshots_namespace_key_unique ON admin_snapshots (namespace, snapshot_key); CREATE INDEX IF NOT EXISTS admin_snapshots_expires_at_idx ON admin_snapshots (expires_at); CREATE INDEX IF NOT EXISTS admin_snapshots_stale_until_idx ON admin_snapshots (stale_until); -- ============================================================ -- ANALYTICS PROJECTION CHECKPOINTS -- ============================================================ CREATE TABLE IF NOT EXISTS analytics_projection_checkpoints ( projector_key TEXT PRIMARY KEY NOT NULL, time_zone TEXT NOT NULL DEFAULT 'Local', last_proxy_log_id BIGINT NOT NULL DEFAULT 0, watermark_created_at TEXT, recompute_from_id BIGINT, recompute_requested_at TEXT, recompute_reason TEXT, recompute_started_at TEXT, recompute_completed_at TEXT, last_projected_at TEXT, last_successful_at TEXT, last_error TEXT, lease_owner TEXT, lease_token TEXT, lease_expires_at TEXT, created_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), updated_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT) ); CREATE INDEX IF NOT EXISTS analytics_projection_checkpoints_recompute_from_id_idx ON analytics_projection_checkpoints (recompute_from_id); CREATE INDEX IF NOT EXISTS analytics_projection_checkpoints_lease_expires_at_idx ON analytics_projection_checkpoints (lease_expires_at); -- ============================================================ -- MODEL DAY USAGE -- ============================================================ CREATE TABLE IF NOT EXISTS model_day_usage ( id BIGSERIAL PRIMARY KEY, local_day TEXT NOT NULL, site_id BIGINT NOT NULL, model TEXT NOT NULL, total_calls BIGINT NOT NULL DEFAULT 0, success_calls BIGINT NOT NULL DEFAULT 0, failed_calls BIGINT NOT NULL DEFAULT 0, total_tokens BIGINT NOT NULL DEFAULT 0, total_spend DOUBLE PRECISION NOT NULL DEFAULT 0, total_latency_ms BIGINT NOT NULL DEFAULT 0, latency_count BIGINT NOT NULL DEFAULT 0, created_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), updated_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), FOREIGN KEY (site_id) REFERENCES sites(id) ON DELETE CASCADE, CONSTRAINT model_day_usage_non_negative CHECK ( total_calls >= 0 AND success_calls >= 0 AND failed_calls >= 0 AND total_tokens >= 0 AND total_spend >= 0 AND total_latency_ms >= 0 AND latency_count >= 0 ) ); CREATE UNIQUE INDEX IF NOT EXISTS model_day_usage_day_site_model_unique ON model_day_usage (local_day, site_id, model); CREATE INDEX IF NOT EXISTS model_day_usage_day_idx ON model_day_usage (local_day); CREATE INDEX IF NOT EXISTS model_day_usage_site_id_idx ON model_day_usage (site_id); CREATE INDEX IF NOT EXISTS model_day_usage_model_idx ON model_day_usage (model); -- ============================================================ -- SITE DAY USAGE -- ============================================================ CREATE TABLE IF NOT EXISTS site_day_usage ( id BIGSERIAL PRIMARY KEY, local_day TEXT NOT NULL, site_id BIGINT NOT NULL, total_calls BIGINT NOT NULL DEFAULT 0, success_calls BIGINT NOT NULL DEFAULT 0, failed_calls BIGINT NOT NULL DEFAULT 0, total_tokens BIGINT NOT NULL DEFAULT 0, total_summary_spend DOUBLE PRECISION NOT NULL DEFAULT 0, total_site_spend DOUBLE PRECISION NOT NULL DEFAULT 0, total_latency_ms BIGINT NOT NULL DEFAULT 0, latency_count BIGINT NOT NULL DEFAULT 0, created_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), updated_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), FOREIGN KEY (site_id) REFERENCES sites(id) ON DELETE CASCADE, CONSTRAINT site_day_usage_non_negative CHECK ( total_calls >= 0 AND success_calls >= 0 AND failed_calls >= 0 AND total_tokens >= 0 AND total_summary_spend >= 0 AND total_site_spend >= 0 AND total_latency_ms >= 0 AND latency_count >= 0 ) ); CREATE UNIQUE INDEX IF NOT EXISTS site_day_usage_day_site_unique ON site_day_usage (local_day, site_id); CREATE INDEX IF NOT EXISTS site_day_usage_day_idx ON site_day_usage (local_day); CREATE INDEX IF NOT EXISTS site_day_usage_site_id_idx ON site_day_usage (site_id); -- ============================================================ -- SITE HOUR USAGE -- ============================================================ CREATE TABLE IF NOT EXISTS site_hour_usage ( id BIGSERIAL PRIMARY KEY, bucket_start_utc TEXT NOT NULL, site_id BIGINT NOT NULL, total_calls BIGINT NOT NULL DEFAULT 0, success_calls BIGINT NOT NULL DEFAULT 0, failed_calls BIGINT NOT NULL DEFAULT 0, total_tokens BIGINT NOT NULL DEFAULT 0, total_summary_spend DOUBLE PRECISION NOT NULL DEFAULT 0, total_site_spend DOUBLE PRECISION NOT NULL DEFAULT 0, total_latency_ms BIGINT NOT NULL DEFAULT 0, latency_count BIGINT NOT NULL DEFAULT 0, created_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), updated_at TEXT DEFAULT (CURRENT_TIMESTAMP::TEXT), FOREIGN KEY (site_id) REFERENCES sites(id) ON DELETE CASCADE, CONSTRAINT site_hour_usage_non_negative CHECK ( total_calls >= 0 AND success_calls >= 0 AND failed_calls >= 0 AND total_tokens >= 0 AND total_summary_spend >= 0 AND total_site_spend >= 0 AND total_latency_ms >= 0 AND latency_count >= 0 ) ); CREATE UNIQUE INDEX IF NOT EXISTS site_hour_usage_hour_site_unique ON site_hour_usage (bucket_start_utc, site_id); CREATE INDEX IF NOT EXISTS site_hour_usage_hour_idx ON site_hour_usage (bucket_start_utc); CREATE INDEX IF NOT EXISTS site_hour_usage_site_id_idx ON site_hour_usage (site_id);