-- 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 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 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 INTEGER DEFAULT false, custom_headers TEXT, external_checkin_url TEXT, global_weight REAL DEFAULT 1, post_refresh_probe_enabled INTEGER DEFAULT false, post_refresh_probe_model TEXT DEFAULT '', post_refresh_probe_scope TEXT DEFAULT 'single', post_refresh_probe_latency_threshold_ms INTEGER DEFAULT 0, is_pinned INTEGER DEFAULT false, sort_order INTEGER DEFAULT 0, created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')) ); 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 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 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 (datetime('now')), updated_at TEXT DEFAULT (datetime('now')) ); 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 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 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 INTEGER DEFAULT true, created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')) ); 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 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, site_id INTEGER NOT NULL, username TEXT, access_token TEXT NOT NULL, api_token TEXT, balance REAL DEFAULT 0, balance_used REAL DEFAULT 0, quota REAL DEFAULT 0, unit_cost REAL, value_score REAL DEFAULT 0, status TEXT DEFAULT 'active', checkin_enabled INTEGER DEFAULT true, last_checkin_at TEXT, last_balance_refresh TEXT, extra_config TEXT, is_pinned INTEGER DEFAULT false, sort_order INTEGER DEFAULT 0, oauth_provider TEXT, oauth_account_key TEXT, oauth_project_id TEXT, created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')), 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 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name TEXT NOT NULL, key TEXT NOT NULL, description TEXT, group_name TEXT, tags TEXT, enabled INTEGER DEFAULT true, expires_at TEXT, max_cost REAL, used_cost REAL DEFAULT 0, max_requests INTEGER, used_requests INTEGER 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 INTEGER REFERENCES users(id), created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')) ); 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 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, site_id INTEGER NOT NULL, model_name TEXT NOT NULL, created_at TEXT DEFAULT (datetime('now')), 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 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, site_id INTEGER NOT NULL, url TEXT NOT NULL, enabled INTEGER DEFAULT true, sort_order INTEGER DEFAULT 0, cooldown_until TEXT, last_selected_at TEXT, last_failed_at TEXT, last_failure_reason TEXT, created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')), 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 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, account_id INTEGER 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 INTEGER DEFAULT true, is_default INTEGER DEFAULT false, created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')), 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 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, account_id INTEGER NOT NULL, status TEXT NOT NULL, message TEXT, reward TEXT, created_at TEXT DEFAULT (datetime('now')), 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 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, account_id INTEGER NOT NULL, model_name TEXT NOT NULL, available INTEGER, latency_ms INTEGER, checked_at TEXT DEFAULT (datetime('now')), is_manual INTEGER 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 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, token_id INTEGER NOT NULL, model_name TEXT NOT NULL, available INTEGER, latency_ms INTEGER, checked_at TEXT DEFAULT (datetime('now')), 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 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, route_id INTEGER NOT NULL, account_id INTEGER NOT NULL, token_id INTEGER, oauth_route_unit_id INTEGER, source_model TEXT, priority INTEGER DEFAULT 0, weight INTEGER DEFAULT 10, enabled INTEGER DEFAULT true, manual_override INTEGER DEFAULT false, success_count INTEGER DEFAULT 0, fail_count INTEGER DEFAULT 0, total_latency_ms INTEGER DEFAULT 0, total_cost REAL DEFAULT 0, last_used_at TEXT, last_selected_at TEXT, last_fail_at TEXT, consecutive_fail_count INTEGER NOT NULL DEFAULT 0, cooldown_level INTEGER 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 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, group_route_id INTEGER NOT NULL, source_route_id INTEGER 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 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, route_id INTEGER, channel_id INTEGER, account_id INTEGER, downstream_api_key_id INTEGER, model_requested TEXT, model_actual TEXT, status TEXT, http_status INTEGER, latency_ms INTEGER, first_byte_latency_ms INTEGER, prompt_tokens INTEGER, completion_tokens INTEGER, total_tokens INTEGER, estimated_cost REAL, error_message TEXT, retry_count INTEGER DEFAULT 0, is_stream INTEGER, client_family TEXT, client_app_id TEXT, client_app_name TEXT, client_confidence TEXT, billing_details TEXT, created_at TEXT DEFAULT (datetime('now')) ); 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 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 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 INTEGER, account_id INTEGER, status_snapshot TEXT, upstream_response_meta TEXT, last_upstream_status INTEGER, last_polled_at TEXT, created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')) ); 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 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 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 INTEGER NOT NULL, sha256 TEXT NOT NULL, content_base64 TEXT NOT NULL, created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')), 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 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, downstream_path TEXT NOT NULL, client_kind TEXT, session_id TEXT, trace_hint TEXT, requested_model TEXT, downstream_api_key_id INTEGER, request_headers_json TEXT, request_body_json TEXT, sticky_session_key TEXT, sticky_hit_channel_id INTEGER, selected_channel_id INTEGER, selected_route_id INTEGER, selected_account_id INTEGER, selected_site_id INTEGER, selected_site_platform TEXT, endpoint_candidates_json TEXT, endpoint_runtime_state_json TEXT, decision_summary_json TEXT, final_status TEXT, final_http_status INTEGER, final_upstream_path TEXT, final_response_headers_json TEXT, final_response_body_json TEXT, created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')) ); 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 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, trace_id INTEGER NOT NULL, attempt_index INTEGER 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 INTEGER, response_headers_json TEXT, response_body_json TEXT, raw_error_text TEXT, recover_applied INTEGER DEFAULT false, downgrade_decision INTEGER DEFAULT false, downgrade_reason TEXT, memory_write_json TEXT, created_at TEXT DEFAULT (datetime('now')), 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 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, type TEXT NOT NULL, title TEXT NOT NULL, message TEXT, level TEXT NOT NULL DEFAULT 'info', read INTEGER DEFAULT false, related_id INTEGER, related_type TEXT, created_at TEXT DEFAULT (datetime('now')) ); 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 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, site_id INTEGER 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 (datetime('now')), last_seen_at TEXT DEFAULT (datetime('now')), 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 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, site_id INTEGER NOT NULL, provider TEXT NOT NULL, name TEXT NOT NULL, strategy TEXT NOT NULL DEFAULT 'round_robin', enabled INTEGER DEFAULT true, created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')), 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 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, unit_id INTEGER NOT NULL, account_id INTEGER NOT NULL, sort_order INTEGER DEFAULT 0, success_count INTEGER DEFAULT 0, fail_count INTEGER DEFAULT 0, total_latency_ms INTEGER DEFAULT 0, total_cost REAL DEFAULT 0, last_used_at TEXT, last_selected_at TEXT, last_fail_at TEXT, consecutive_fail_count INTEGER NOT NULL DEFAULT 0, cooldown_level INTEGER NOT NULL DEFAULT 0, cooldown_until TEXT, created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')), 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 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 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 (datetime('now')), updated_at TEXT DEFAULT (datetime('now')) ); 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 INTEGER NOT NULL DEFAULT 0, watermark_created_at TEXT, recompute_from_id INTEGER, 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 (datetime('now')), updated_at TEXT DEFAULT (datetime('now')) ); 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 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, local_day TEXT NOT NULL, site_id INTEGER NOT NULL, model TEXT NOT NULL, total_calls INTEGER NOT NULL DEFAULT 0, success_calls INTEGER NOT NULL DEFAULT 0, failed_calls INTEGER NOT NULL DEFAULT 0, total_tokens INTEGER NOT NULL DEFAULT 0, total_spend REAL NOT NULL DEFAULT 0, total_latency_ms INTEGER NOT NULL DEFAULT 0, latency_count INTEGER NOT NULL DEFAULT 0, created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')), 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 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, local_day TEXT NOT NULL, site_id INTEGER NOT NULL, total_calls INTEGER NOT NULL DEFAULT 0, success_calls INTEGER NOT NULL DEFAULT 0, failed_calls INTEGER NOT NULL DEFAULT 0, total_tokens INTEGER NOT NULL DEFAULT 0, total_summary_spend REAL NOT NULL DEFAULT 0, total_site_spend REAL NOT NULL DEFAULT 0, total_latency_ms INTEGER NOT NULL DEFAULT 0, latency_count INTEGER NOT NULL DEFAULT 0, created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')), 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 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, bucket_start_utc TEXT NOT NULL, site_id INTEGER NOT NULL, total_calls INTEGER NOT NULL DEFAULT 0, success_calls INTEGER NOT NULL DEFAULT 0, failed_calls INTEGER NOT NULL DEFAULT 0, total_tokens INTEGER NOT NULL DEFAULT 0, total_summary_spend REAL NOT NULL DEFAULT 0, total_site_spend REAL NOT NULL DEFAULT 0, total_latency_ms INTEGER NOT NULL DEFAULT 0, latency_count INTEGER NOT NULL DEFAULT 0, created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')), 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);