2025-12-10 20:52:44 +08:00
"""Baseline migration - all tables consolidated
Revision ID: 20251210_baseline
Revises:
Create Date: 2024-12-10
This is the consolidated baseline migration that creates all tables from scratch.
Includes all schema changes up to circuit breaker v2.
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers
revision = "20251210_baseline"
down_revision = None
branch_labels = None
depends_on = None
def upgrade () -> None :
2025-12-17 16:41:10 +08:00
# Create ENUM types (with IF NOT EXISTS for idempotency)
op . execute ( "DO $$ BEGIN CREATE TYPE userrole AS ENUM ('admin', 'user'); EXCEPTION WHEN duplicate_object THEN NULL; END $$" )
2025-12-10 20:52:44 +08:00
op . execute (
2025-12-17 16:41:10 +08:00
"DO $$ BEGIN CREATE TYPE providerbillingtype AS ENUM ('monthly_quota', 'pay_as_you_go', 'free_tier'); EXCEPTION WHEN duplicate_object THEN NULL; END $$"
2025-12-10 20:52:44 +08:00
)
# ==================== users ====================
op . create_table (
"users" ,
sa . Column ( "id" , sa . String ( 36 ), primary_key = True , index = True ),
sa . Column ( "email" , sa . String ( 255 ), unique = True , index = True , nullable = False ),
sa . Column ( "username" , sa . String ( 100 ), unique = True , index = True , nullable = False ),
sa . Column ( "password_hash" , sa . String ( 255 ), nullable = False ),
sa . Column (
"role" ,
2025-12-17 16:41:10 +08:00
postgresql . ENUM ( "admin" , "user" , name = "userrole" , create_type = False ),
2025-12-10 20:52:44 +08:00
nullable = False ,
server_default = "user" ,
),
sa . Column ( "allowed_providers" , sa . JSON , nullable = True ),
sa . Column ( "allowed_endpoints" , sa . JSON , nullable = True ),
sa . Column ( "allowed_models" , sa . JSON , nullable = True ),
sa . Column ( "model_capability_settings" , sa . JSON , nullable = True ),
sa . Column ( "quota_usd" , sa . Float , nullable = True ),
sa . Column ( "used_usd" , sa . Float , server_default = "0.0" ),
sa . Column ( "total_usd" , sa . Float , server_default = "0.0" ),
sa . Column ( "is_active" , sa . Boolean , server_default = "true" , nullable = False ),
sa . Column ( "is_deleted" , sa . Boolean , server_default = "false" , nullable = False ),
sa . Column (
"created_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
sa . Column (
"updated_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
sa . Column ( "last_login_at" , sa . DateTime ( timezone = True ), nullable = True ),
)
# ==================== providers ====================
op . create_table (
"providers" ,
sa . Column ( "id" , sa . String ( 36 ), primary_key = True , index = True ),
sa . Column ( "name" , sa . String ( 100 ), unique = True , index = True , nullable = False ),
sa . Column ( "display_name" , sa . String ( 100 ), nullable = False ),
sa . Column ( "description" , sa . Text , nullable = True ),
sa . Column ( "website" , sa . String ( 500 ), nullable = True ),
sa . Column (
"billing_type" ,
2025-12-17 16:41:10 +08:00
postgresql . ENUM (
2025-12-10 20:52:44 +08:00
"monthly_quota" , "pay_as_you_go" , "free_tier" , name = "providerbillingtype" , create_type = False
),
nullable = False ,
server_default = "pay_as_you_go" ,
),
sa . Column ( "monthly_quota_usd" , sa . Float , nullable = True ),
sa . Column ( "monthly_used_usd" , sa . Float , server_default = "0.0" ),
sa . Column ( "quota_reset_day" , sa . Integer , server_default = "30" ),
sa . Column ( "quota_last_reset_at" , sa . DateTime ( timezone = True ), nullable = True ),
sa . Column ( "quota_expires_at" , sa . DateTime ( timezone = True ), nullable = True ),
sa . Column ( "rpm_limit" , sa . Integer , nullable = True ),
sa . Column ( "rpm_used" , sa . Integer , server_default = "0" ),
sa . Column ( "rpm_reset_at" , sa . DateTime ( timezone = True ), nullable = True ),
sa . Column ( "provider_priority" , sa . Integer , server_default = "100" ),
sa . Column ( "is_active" , sa . Boolean , server_default = "true" , nullable = False ),
sa . Column ( "rate_limit" , sa . Integer , nullable = True ),
sa . Column ( "concurrent_limit" , sa . Integer , nullable = True ),
sa . Column ( "config" , sa . JSON , nullable = True ),
sa . Column (
"created_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
sa . Column (
"updated_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
)
# ==================== global_models ====================
op . create_table (
"global_models" ,
sa . Column ( "id" , sa . String ( 36 ), primary_key = True , index = True ),
sa . Column ( "name" , sa . String ( 100 ), unique = True , index = True , nullable = False ),
sa . Column ( "display_name" , sa . String ( 100 ), nullable = False ),
sa . Column ( "description" , sa . Text , nullable = True ),
sa . Column ( "icon_url" , sa . String ( 500 ), nullable = True ),
sa . Column ( "official_url" , sa . String ( 500 ), nullable = True ),
sa . Column ( "default_price_per_request" , sa . Float , nullable = True ),
sa . Column ( "default_tiered_pricing" , sa . JSON , nullable = False ),
sa . Column ( "default_supports_vision" , sa . Boolean , server_default = "false" , nullable = True ),
sa . Column ( "default_supports_function_calling" , sa . Boolean , server_default = "false" , nullable = True ),
sa . Column ( "default_supports_streaming" , sa . Boolean , server_default = "true" , nullable = True ),
sa . Column ( "default_supports_extended_thinking" , sa . Boolean , server_default = "false" , nullable = True ),
sa . Column ( "default_supports_image_generation" , sa . Boolean , server_default = "false" , nullable = True ),
sa . Column ( "supported_capabilities" , sa . JSON , nullable = True ),
sa . Column ( "is_active" , sa . Boolean , server_default = "true" , nullable = False ),
sa . Column ( "usage_count" , sa . Integer , server_default = "0" , nullable = False , index = True ),
sa . Column (
"created_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
sa . Column (
"updated_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
)
# ==================== api_keys ====================
op . create_table (
"api_keys" ,
sa . Column ( "id" , sa . String ( 36 ), primary_key = True , index = True ),
sa . Column (
"user_id" , sa . String ( 36 ), sa . ForeignKey ( "users.id" , ondelete = "CASCADE" ), nullable = False
),
sa . Column ( "key_hash" , sa . String ( 64 ), unique = True , index = True , nullable = False ),
sa . Column ( "key_encrypted" , sa . Text , nullable = True ),
sa . Column ( "name" , sa . String ( 100 ), nullable = True ),
sa . Column ( "total_requests" , sa . Integer , server_default = "0" ),
sa . Column ( "total_cost_usd" , sa . Float , server_default = "0.0" ),
sa . Column ( "balance_used_usd" , sa . Float , server_default = "0.0" ),
sa . Column ( "current_balance_usd" , sa . Float , nullable = True ),
sa . Column ( "is_standalone" , sa . Boolean , server_default = "false" , nullable = False ),
sa . Column ( "allowed_providers" , sa . JSON , nullable = True ),
sa . Column ( "allowed_endpoints" , sa . JSON , nullable = True ),
sa . Column ( "allowed_api_formats" , sa . JSON , nullable = True ),
sa . Column ( "allowed_models" , sa . JSON , nullable = True ),
sa . Column ( "rate_limit" , sa . Integer , server_default = "100" ),
sa . Column ( "concurrent_limit" , sa . Integer , server_default = "5" , nullable = True ),
sa . Column ( "force_capabilities" , sa . JSON , nullable = True ),
sa . Column ( "is_active" , sa . Boolean , server_default = "true" , nullable = False ),
sa . Column ( "last_used_at" , sa . DateTime ( timezone = True ), nullable = True ),
sa . Column ( "expires_at" , sa . DateTime ( timezone = True ), nullable = True ),
sa . Column ( "auto_delete_on_expiry" , sa . Boolean , server_default = "false" , nullable = False ),
sa . Column (
"created_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
sa . Column (
"updated_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
)
# ==================== provider_endpoints ====================
op . create_table (
"provider_endpoints" ,
sa . Column ( "id" , sa . String ( 36 ), primary_key = True , index = True ),
sa . Column (
"provider_id" ,
sa . String ( 36 ),
sa . ForeignKey ( "providers.id" , ondelete = "CASCADE" ),
nullable = False ,
),
sa . Column ( "api_format" , sa . String ( 50 ), nullable = False ),
sa . Column ( "base_url" , sa . String ( 500 ), nullable = False ),
sa . Column ( "headers" , sa . JSON , nullable = True ),
sa . Column ( "timeout" , sa . Integer , server_default = "300" ),
sa . Column ( "max_retries" , sa . Integer , server_default = "3" ),
sa . Column ( "max_concurrent" , sa . Integer , nullable = True ),
sa . Column ( "rate_limit" , sa . Integer , nullable = True ),
sa . Column ( "is_active" , sa . Boolean , server_default = "true" , nullable = False ),
sa . Column ( "custom_path" , sa . String ( 200 ), nullable = True ),
sa . Column ( "config" , sa . JSON , nullable = True ),
sa . Column (
"created_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
sa . Column (
"updated_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
sa . UniqueConstraint ( "provider_id" , "api_format" , name = "uq_provider_api_format" ),
)
op . create_index (
"idx_endpoint_format_active" , "provider_endpoints" , [ "api_format" , "is_active" ]
)
# ==================== models ====================
op . create_table (
"models" ,
sa . Column ( "id" , sa . String ( 36 ), primary_key = True , index = True ),
sa . Column (
"provider_id" , sa . String ( 36 ), sa . ForeignKey ( "providers.id" ), nullable = False
),
sa . Column (
"global_model_id" ,
sa . String ( 36 ),
sa . ForeignKey ( "global_models.id" ),
nullable = False ,
index = True ,
),
sa . Column ( "provider_model_name" , sa . String ( 200 ), nullable = False ),
sa . Column ( "price_per_request" , sa . Float , nullable = True ),
sa . Column ( "tiered_pricing" , sa . JSON , nullable = True ),
sa . Column ( "supports_vision" , sa . Boolean , nullable = True ),
sa . Column ( "supports_function_calling" , sa . Boolean , nullable = True ),
sa . Column ( "supports_streaming" , sa . Boolean , nullable = True ),
sa . Column ( "supports_extended_thinking" , sa . Boolean , nullable = True ),
sa . Column ( "supports_image_generation" , sa . Boolean , nullable = True ),
sa . Column ( "is_active" , sa . Boolean , server_default = "true" , nullable = False ),
sa . Column ( "is_available" , sa . Boolean , server_default = "true" ),
sa . Column ( "config" , sa . JSON , nullable = True ),
sa . Column (
"created_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
sa . Column (
"updated_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
sa . UniqueConstraint ( "provider_id" , "provider_model_name" , name = "uq_provider_model" ),
)
# ==================== model_mappings ====================
op . create_table (
"model_mappings" ,
sa . Column ( "id" , sa . String ( 36 ), primary_key = True , index = True ),
sa . Column ( "source_model" , sa . String ( 200 ), nullable = False , index = True ),
sa . Column (
"target_global_model_id" ,
sa . String ( 36 ),
sa . ForeignKey ( "global_models.id" , ondelete = "CASCADE" ),
nullable = False ,
index = True ,
),
sa . Column (
"provider_id" , sa . String ( 36 ), sa . ForeignKey ( "providers.id" ), nullable = True , index = True
),
sa . Column ( "mapping_type" , sa . String ( 20 ), nullable = False , server_default = "alias" , index = True ),
sa . Column ( "is_active" , sa . Boolean , server_default = "true" , nullable = False ),
sa . Column (
"created_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
sa . Column (
"updated_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
sa . UniqueConstraint ( "source_model" , "provider_id" , name = "uq_model_mapping_source_provider" ),
)
# ==================== provider_api_keys ====================
op . create_table (
"provider_api_keys" ,
sa . Column ( "id" , sa . String ( 36 ), primary_key = True , index = True ),
sa . Column (
"endpoint_id" ,
sa . String ( 36 ),
sa . ForeignKey ( "provider_endpoints.id" , ondelete = "CASCADE" ),
nullable = False ,
),
sa . Column ( "api_key" , sa . String ( 500 ), nullable = False ),
sa . Column ( "name" , sa . String ( 100 ), nullable = False ),
sa . Column ( "note" , sa . String ( 500 ), nullable = True ),
sa . Column ( "rate_multiplier" , sa . Float , server_default = "1.0" , nullable = False ),
sa . Column ( "internal_priority" , sa . Integer , server_default = "50" ),
sa . Column ( "global_priority" , sa . Integer , nullable = True ),
sa . Column ( "max_concurrent" , sa . Integer , nullable = True ),
sa . Column ( "rate_limit" , sa . Integer , nullable = True ),
sa . Column ( "daily_limit" , sa . Integer , nullable = True ),
sa . Column ( "monthly_limit" , sa . Integer , nullable = True ),
sa . Column ( "allowed_models" , sa . JSON , nullable = True ),
sa . Column ( "capabilities" , sa . JSON , nullable = True ),
sa . Column ( "learned_max_concurrent" , sa . Integer , nullable = True ),
sa . Column ( "concurrent_429_count" , sa . Integer , server_default = "0" , nullable = False ),
sa . Column ( "rpm_429_count" , sa . Integer , server_default = "0" , nullable = False ),
sa . Column ( "last_429_at" , sa . DateTime ( timezone = True ), nullable = True ),
sa . Column ( "last_429_type" , sa . String ( 50 ), nullable = True ),
sa . Column ( "last_concurrent_peak" , sa . Integer , nullable = True ),
sa . Column ( "adjustment_history" , sa . JSON , nullable = True ),
# Sliding window fields (replaces high_utilization_start)
sa . Column ( "utilization_samples" , sa . JSON , nullable = True ),
sa . Column ( "last_probe_increase_at" , sa . DateTime ( timezone = True ), nullable = True ),
sa . Column ( "health_score" , sa . Float , server_default = "1.0" ),
sa . Column ( "consecutive_failures" , sa . Integer , server_default = "0" ),
sa . Column ( "last_failure_at" , sa . DateTime ( timezone = True ), nullable = True ),
sa . Column ( "cache_ttl_minutes" , sa . Integer , server_default = "5" , nullable = False ),
sa . Column ( "max_probe_interval_minutes" , sa . Integer , server_default = "32" , nullable = False ),
sa . Column ( "circuit_breaker_open" , sa . Boolean , server_default = "false" , nullable = False ),
sa . Column ( "circuit_breaker_open_at" , sa . DateTime ( timezone = True ), nullable = True ),
sa . Column ( "next_probe_at" , sa . DateTime ( timezone = True ), nullable = True ),
# Circuit breaker v2 fields
sa . Column ( "request_results_window" , sa . JSON , nullable = True ),
sa . Column ( "half_open_until" , sa . DateTime ( timezone = True ), nullable = True ),
sa . Column ( "half_open_successes" , sa . Integer , server_default = "0" , nullable = True ),
sa . Column ( "half_open_failures" , sa . Integer , server_default = "0" , nullable = True ),
sa . Column ( "request_count" , sa . Integer , server_default = "0" ),
sa . Column ( "success_count" , sa . Integer , server_default = "0" ),
sa . Column ( "error_count" , sa . Integer , server_default = "0" ),
sa . Column ( "total_response_time_ms" , sa . Integer , server_default = "0" ),
sa . Column ( "last_used_at" , sa . DateTime ( timezone = True ), nullable = True ),
sa . Column ( "last_error_at" , sa . DateTime ( timezone = True ), nullable = True ),
sa . Column ( "last_error_msg" , sa . Text , nullable = True ),
sa . Column ( "is_active" , sa . Boolean , server_default = "true" , nullable = False ),
sa . Column ( "expires_at" , sa . DateTime ( timezone = True ), nullable = True ),
sa . Column (
"created_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
sa . Column (
"updated_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
)
# ==================== usage ====================
op . create_table (
"usage" ,
sa . Column ( "id" , sa . String ( 36 ), primary_key = True , index = True ),
sa . Column (
"user_id" ,
sa . String ( 36 ),
sa . ForeignKey ( "users.id" , ondelete = "SET NULL" ),
nullable = True ,
),
sa . Column (
"api_key_id" ,
sa . String ( 36 ),
sa . ForeignKey ( "api_keys.id" , ondelete = "SET NULL" ),
nullable = True ,
),
sa . Column ( "request_id" , sa . String ( 100 ), unique = True , index = True , nullable = False ),
sa . Column ( "provider" , sa . String ( 100 ), nullable = False ),
sa . Column ( "model" , sa . String ( 100 ), nullable = False ),
sa . Column ( "target_model" , sa . String ( 100 ), nullable = True ),
sa . Column (
"provider_id" ,
sa . String ( 36 ),
sa . ForeignKey ( "providers.id" , ondelete = "SET NULL" ),
nullable = True ,
),
sa . Column (
"provider_endpoint_id" ,
sa . String ( 36 ),
sa . ForeignKey ( "provider_endpoints.id" , ondelete = "SET NULL" ),
nullable = True ,
),
sa . Column (
"provider_api_key_id" ,
sa . String ( 36 ),
sa . ForeignKey ( "provider_api_keys.id" , ondelete = "SET NULL" ),
nullable = True ,
),
sa . Column ( "input_tokens" , sa . Integer , server_default = "0" ),
sa . Column ( "output_tokens" , sa . Integer , server_default = "0" ),
sa . Column ( "total_tokens" , sa . Integer , server_default = "0" ),
sa . Column ( "cache_creation_input_tokens" , sa . Integer , server_default = "0" ),
sa . Column ( "cache_read_input_tokens" , sa . Integer , server_default = "0" ),
sa . Column ( "input_cost_usd" , sa . Float , server_default = "0.0" ),
sa . Column ( "output_cost_usd" , sa . Float , server_default = "0.0" ),
sa . Column ( "cache_cost_usd" , sa . Float , server_default = "0.0" ),
sa . Column ( "cache_creation_cost_usd" , sa . Float , server_default = "0.0" ),
sa . Column ( "cache_read_cost_usd" , sa . Float , server_default = "0.0" ),
sa . Column ( "request_cost_usd" , sa . Float , server_default = "0.0" ),
sa . Column ( "total_cost_usd" , sa . Float , server_default = "0.0" ),
sa . Column ( "actual_input_cost_usd" , sa . Float , server_default = "0.0" ),
sa . Column ( "actual_output_cost_usd" , sa . Float , server_default = "0.0" ),
sa . Column ( "actual_cache_creation_cost_usd" , sa . Float , server_default = "0.0" ),
sa . Column ( "actual_cache_read_cost_usd" , sa . Float , server_default = "0.0" ),
sa . Column ( "actual_request_cost_usd" , sa . Float , server_default = "0.0" ),
sa . Column ( "actual_total_cost_usd" , sa . Float , server_default = "0.0" ),
sa . Column ( "rate_multiplier" , sa . Float , server_default = "1.0" ),
sa . Column ( "input_price_per_1m" , sa . Float , nullable = True ),
sa . Column ( "output_price_per_1m" , sa . Float , nullable = True ),
sa . Column ( "cache_creation_price_per_1m" , sa . Float , nullable = True ),
sa . Column ( "cache_read_price_per_1m" , sa . Float , nullable = True ),
sa . Column ( "price_per_request" , sa . Float , nullable = True ),
sa . Column ( "request_type" , sa . String ( 50 ), nullable = True ),
sa . Column ( "api_format" , sa . String ( 50 ), nullable = True ),
sa . Column ( "is_stream" , sa . Boolean , server_default = "false" ),
sa . Column ( "status_code" , sa . Integer , nullable = True ),
sa . Column ( "error_message" , sa . Text , nullable = True ),
sa . Column ( "response_time_ms" , sa . Integer , nullable = True ),
sa . Column ( "status" , sa . String ( 20 ), server_default = "completed" , nullable = False , index = True ),
sa . Column ( "request_headers" , sa . JSON , nullable = True ),
sa . Column ( "request_body" , sa . JSON , nullable = True ),
sa . Column ( "provider_request_headers" , sa . JSON , nullable = True ),
sa . Column ( "response_headers" , sa . JSON , nullable = True ),
sa . Column ( "response_body" , sa . JSON , nullable = True ),
sa . Column ( "request_body_compressed" , sa . LargeBinary , nullable = True ),
sa . Column ( "response_body_compressed" , sa . LargeBinary , nullable = True ),
sa . Column ( "request_metadata" , sa . JSON , nullable = True ),
sa . Column (
"created_at" ,
sa . DateTime ( timezone = True ),
server_default = sa . func . now (),
nullable = False ,
index = True ,
),
)
2025-12-24 01:24:05 +08:00
# usage 表复合索引(优化常见查询)
op . create_index ( "idx_usage_user_created" , "usage" , [ "user_id" , "created_at" ])
op . create_index ( "idx_usage_apikey_created" , "usage" , [ "api_key_id" , "created_at" ])
op . create_index ( "idx_usage_provider_model_created" , "usage" , [ "provider" , "model" , "created_at" ])
2025-12-10 20:52:44 +08:00
# ==================== user_quotas ====================
op . create_table (
"user_quotas" ,
sa . Column ( "id" , sa . String ( 36 ), primary_key = True , index = True ),
sa . Column (
"user_id" , sa . String ( 36 ), sa . ForeignKey ( "users.id" , ondelete = "CASCADE" ), nullable = False
),
sa . Column ( "quota_type" , sa . String ( 50 ), nullable = False ),
sa . Column ( "quota_usd" , sa . Float , nullable = False ),
sa . Column ( "period_start" , sa . DateTime ( timezone = True ), nullable = False ),
sa . Column ( "period_end" , sa . DateTime ( timezone = True ), nullable = False ),
sa . Column ( "used_usd" , sa . Float , server_default = "0.0" ),
sa . Column ( "is_active" , sa . Boolean , server_default = "true" ),
sa . Column (
"created_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
sa . Column (
"updated_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
)
# ==================== system_configs ====================
op . create_table (
"system_configs" ,
sa . Column ( "id" , sa . String ( 36 ), primary_key = True , index = True ),
sa . Column ( "key" , sa . String ( 100 ), unique = True , nullable = False ),
sa . Column ( "value" , sa . JSON , nullable = False ),
sa . Column ( "description" , sa . Text , nullable = True ),
sa . Column (
"created_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
sa . Column (
"updated_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
)
# ==================== user_preferences ====================
op . create_table (
"user_preferences" ,
sa . Column ( "id" , sa . String ( 36 ), primary_key = True , index = True ),
sa . Column (
"user_id" ,
sa . String ( 36 ),
sa . ForeignKey ( "users.id" , ondelete = "CASCADE" ),
unique = True ,
nullable = False ,
),
sa . Column ( "avatar_url" , sa . String ( 500 ), nullable = True ),
sa . Column ( "bio" , sa . Text , nullable = True ),
sa . Column (
"default_provider_id" , sa . String ( 36 ), sa . ForeignKey ( "providers.id" ), nullable = True
),
sa . Column ( "theme" , sa . String ( 20 ), server_default = "light" ),
sa . Column ( "language" , sa . String ( 10 ), server_default = "zh-CN" ),
sa . Column ( "timezone" , sa . String ( 50 ), server_default = "Asia/Shanghai" ),
sa . Column ( "email_notifications" , sa . Boolean , server_default = "true" ),
sa . Column ( "usage_alerts" , sa . Boolean , server_default = "true" ),
sa . Column ( "announcement_notifications" , sa . Boolean , server_default = "true" ),
sa . Column (
"created_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
sa . Column (
"updated_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
)
# ==================== announcements ====================
op . create_table (
"announcements" ,
sa . Column ( "id" , sa . String ( 36 ), primary_key = True , index = True ),
sa . Column ( "title" , sa . String ( 200 ), nullable = False ),
sa . Column ( "content" , sa . Text , nullable = False ),
sa . Column ( "type" , sa . String ( 20 ), server_default = "info" ),
sa . Column ( "priority" , sa . Integer , server_default = "0" ),
sa . Column (
"author_id" ,
sa . String ( 36 ),
sa . ForeignKey ( "users.id" , ondelete = "SET NULL" ),
nullable = True ,
),
sa . Column ( "is_active" , sa . Boolean , server_default = "true" , index = True ),
sa . Column ( "is_pinned" , sa . Boolean , server_default = "false" ),
sa . Column ( "start_time" , sa . DateTime ( timezone = True ), nullable = True ),
sa . Column ( "end_time" , sa . DateTime ( timezone = True ), nullable = True ),
sa . Column (
"created_at" ,
sa . DateTime ( timezone = True ),
server_default = sa . func . now (),
nullable = False ,
index = True ,
),
sa . Column (
"updated_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
)
# ==================== announcement_reads ====================
op . create_table (
"announcement_reads" ,
sa . Column ( "id" , sa . String ( 36 ), primary_key = True , index = True ),
sa . Column (
"user_id" , sa . String ( 36 ), sa . ForeignKey ( "users.id" , ondelete = "CASCADE" ), nullable = False
),
sa . Column (
"announcement_id" , sa . String ( 36 ), sa . ForeignKey ( "announcements.id" ), nullable = False
),
sa . Column (
"read_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
sa . UniqueConstraint ( "user_id" , "announcement_id" , name = "uq_user_announcement" ),
)
# ==================== audit_logs ====================
op . create_table (
"audit_logs" ,
sa . Column ( "id" , sa . String ( 36 ), primary_key = True , index = True ),
sa . Column ( "event_type" , sa . String ( 50 ), nullable = False , index = True ),
sa . Column (
"user_id" ,
sa . String ( 36 ),
sa . ForeignKey ( "users.id" , ondelete = "SET NULL" ),
nullable = True ,
index = True ,
),
sa . Column ( "api_key_id" , sa . String ( 36 ), nullable = True ),
sa . Column ( "description" , sa . Text , nullable = False ),
sa . Column ( "ip_address" , sa . String ( 45 ), nullable = True ),
sa . Column ( "user_agent" , sa . String ( 500 ), nullable = True ),
sa . Column ( "request_id" , sa . String ( 100 ), nullable = True , index = True ),
sa . Column ( "event_metadata" , sa . JSON , nullable = True ),
sa . Column ( "status_code" , sa . Integer , nullable = True ),
sa . Column ( "error_message" , sa . Text , nullable = True ),
sa . Column (
"created_at" ,
sa . DateTime ( timezone = True ),
server_default = sa . func . now (),
nullable = False ,
index = True ,
),
)
# ==================== request_candidates ====================
op . create_table (
"request_candidates" ,
sa . Column ( "id" , sa . String ( 36 ), primary_key = True ),
sa . Column ( "request_id" , sa . String ( 100 ), nullable = False , index = True ),
sa . Column (
"user_id" , sa . String ( 36 ), sa . ForeignKey ( "users.id" , ondelete = "CASCADE" ), nullable = True
),
sa . Column (
"api_key_id" ,
sa . String ( 36 ),
sa . ForeignKey ( "api_keys.id" , ondelete = "CASCADE" ),
nullable = True ,
),
sa . Column ( "candidate_index" , sa . Integer , nullable = False ),
sa . Column ( "retry_index" , sa . Integer , nullable = False , server_default = "0" ),
sa . Column (
"provider_id" ,
sa . String ( 36 ),
sa . ForeignKey ( "providers.id" , ondelete = "CASCADE" ),
nullable = True ,
),
sa . Column (
"endpoint_id" ,
sa . String ( 36 ),
sa . ForeignKey ( "provider_endpoints.id" , ondelete = "CASCADE" ),
nullable = True ,
),
sa . Column (
"key_id" ,
sa . String ( 36 ),
sa . ForeignKey ( "provider_api_keys.id" , ondelete = "CASCADE" ),
nullable = True ,
),
sa . Column ( "status" , sa . String ( 20 ), nullable = False ),
sa . Column ( "skip_reason" , sa . Text , nullable = True ),
sa . Column ( "is_cached" , sa . Boolean , server_default = "false" ),
sa . Column ( "status_code" , sa . Integer , nullable = True ),
sa . Column ( "error_type" , sa . String ( 50 ), nullable = True ),
sa . Column ( "error_message" , sa . Text , nullable = True ),
sa . Column ( "latency_ms" , sa . Integer , nullable = True ),
sa . Column ( "concurrent_requests" , sa . Integer , nullable = True ),
sa . Column ( "extra_data" , sa . JSON , nullable = True ),
sa . Column ( "required_capabilities" , sa . JSON , nullable = True ),
sa . Column (
"created_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
sa . Column ( "started_at" , sa . DateTime ( timezone = True ), nullable = True ),
sa . Column ( "finished_at" , sa . DateTime ( timezone = True ), nullable = True ),
sa . UniqueConstraint (
"request_id" , "candidate_index" , "retry_index" , name = "uq_request_candidate_with_retry"
),
)
op . create_index ( "idx_request_candidates_request_id" , "request_candidates" , [ "request_id" ])
op . create_index ( "idx_request_candidates_status" , "request_candidates" , [ "status" ])
op . create_index ( "idx_request_candidates_provider_id" , "request_candidates" , [ "provider_id" ])
# ==================== stats_daily ====================
op . create_table (
"stats_daily" ,
sa . Column ( "id" , sa . String ( 36 ), primary_key = True ),
sa . Column ( "date" , sa . DateTime ( timezone = True ), nullable = False , unique = True , index = True ),
sa . Column ( "total_requests" , sa . Integer , server_default = "0" , nullable = False ),
sa . Column ( "success_requests" , sa . Integer , server_default = "0" , nullable = False ),
sa . Column ( "error_requests" , sa . Integer , server_default = "0" , nullable = False ),
sa . Column ( "input_tokens" , sa . BigInteger , server_default = "0" , nullable = False ),
sa . Column ( "output_tokens" , sa . BigInteger , server_default = "0" , nullable = False ),
sa . Column ( "cache_creation_tokens" , sa . BigInteger , server_default = "0" , nullable = False ),
sa . Column ( "cache_read_tokens" , sa . BigInteger , server_default = "0" , nullable = False ),
sa . Column ( "total_cost" , sa . Float , server_default = "0.0" , nullable = False ),
sa . Column ( "actual_total_cost" , sa . Float , server_default = "0.0" , nullable = False ),
sa . Column ( "input_cost" , sa . Float , server_default = "0.0" , nullable = False ),
sa . Column ( "output_cost" , sa . Float , server_default = "0.0" , nullable = False ),
sa . Column ( "cache_creation_cost" , sa . Float , server_default = "0.0" , nullable = False ),
sa . Column ( "cache_read_cost" , sa . Float , server_default = "0.0" , nullable = False ),
sa . Column ( "avg_response_time_ms" , sa . Float , server_default = "0.0" , nullable = False ),
sa . Column ( "fallback_count" , sa . Integer , server_default = "0" , nullable = False ),
sa . Column ( "unique_models" , sa . Integer , server_default = "0" , nullable = False ),
sa . Column ( "unique_providers" , sa . Integer , server_default = "0" , nullable = False ),
sa . Column (
"created_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
sa . Column (
"updated_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
)
# ==================== stats_summary ====================
op . create_table (
"stats_summary" ,
sa . Column ( "id" , sa . String ( 36 ), primary_key = True ),
sa . Column ( "cutoff_date" , sa . DateTime ( timezone = True ), nullable = False ),
sa . Column ( "all_time_requests" , sa . Integer , server_default = "0" , nullable = False ),
sa . Column ( "all_time_success_requests" , sa . Integer , server_default = "0" , nullable = False ),
sa . Column ( "all_time_error_requests" , sa . Integer , server_default = "0" , nullable = False ),
sa . Column ( "all_time_input_tokens" , sa . BigInteger , server_default = "0" , nullable = False ),
sa . Column ( "all_time_output_tokens" , sa . BigInteger , server_default = "0" , nullable = False ),
sa . Column (
"all_time_cache_creation_tokens" , sa . BigInteger , server_default = "0" , nullable = False
),
sa . Column ( "all_time_cache_read_tokens" , sa . BigInteger , server_default = "0" , nullable = False ),
sa . Column ( "all_time_cost" , sa . Float , server_default = "0.0" , nullable = False ),
sa . Column ( "all_time_actual_cost" , sa . Float , server_default = "0.0" , nullable = False ),
sa . Column ( "total_users" , sa . Integer , server_default = "0" , nullable = False ),
sa . Column ( "active_users" , sa . Integer , server_default = "0" , nullable = False ),
sa . Column ( "total_api_keys" , sa . Integer , server_default = "0" , nullable = False ),
sa . Column ( "active_api_keys" , sa . Integer , server_default = "0" , nullable = False ),
sa . Column (
"created_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
sa . Column (
"updated_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
)
# ==================== stats_user_daily ====================
op . create_table (
"stats_user_daily" ,
sa . Column ( "id" , sa . String ( 36 ), primary_key = True ),
sa . Column (
"user_id" , sa . String ( 36 ), sa . ForeignKey ( "users.id" , ondelete = "CASCADE" ), nullable = False
),
sa . Column ( "date" , sa . DateTime ( timezone = True ), nullable = False , index = True ),
sa . Column ( "total_requests" , sa . Integer , server_default = "0" , nullable = False ),
sa . Column ( "success_requests" , sa . Integer , server_default = "0" , nullable = False ),
sa . Column ( "error_requests" , sa . Integer , server_default = "0" , nullable = False ),
sa . Column ( "input_tokens" , sa . BigInteger , server_default = "0" , nullable = False ),
sa . Column ( "output_tokens" , sa . BigInteger , server_default = "0" , nullable = False ),
sa . Column ( "cache_creation_tokens" , sa . BigInteger , server_default = "0" , nullable = False ),
sa . Column ( "cache_read_tokens" , sa . BigInteger , server_default = "0" , nullable = False ),
sa . Column ( "total_cost" , sa . Float , server_default = "0.0" , nullable = False ),
sa . Column (
"created_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
sa . Column (
"updated_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
sa . UniqueConstraint ( "user_id" , "date" , name = "uq_stats_user_daily" ),
)
op . create_index ( "idx_stats_user_daily_user_date" , "stats_user_daily" , [ "user_id" , "date" ])
# ==================== api_key_provider_mappings ====================
op . create_table (
"api_key_provider_mappings" ,
sa . Column ( "id" , sa . String ( 36 ), primary_key = True , index = True ),
sa . Column (
"api_key_id" ,
sa . String ( 36 ),
sa . ForeignKey ( "api_keys.id" , ondelete = "CASCADE" ),
nullable = False ,
index = True ,
),
sa . Column (
"provider_id" ,
sa . String ( 36 ),
sa . ForeignKey ( "providers.id" , ondelete = "CASCADE" ),
nullable = False ,
index = True ,
),
sa . Column ( "priority_adjustment" , sa . Integer , server_default = "0" ),
sa . Column ( "weight_multiplier" , sa . Float , server_default = "1.0" ),
sa . Column ( "is_enabled" , sa . Boolean , server_default = "true" , nullable = False ),
sa . Column (
"created_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
sa . Column (
"updated_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
sa . UniqueConstraint ( "api_key_id" , "provider_id" , name = "uq_apikey_provider" ),
)
op . create_index (
"idx_apikey_provider_enabled" , "api_key_provider_mappings" , [ "api_key_id" , "is_enabled" ]
)
# ==================== provider_usage_tracking ====================
op . create_table (
"provider_usage_tracking" ,
sa . Column ( "id" , sa . String ( 36 ), primary_key = True , index = True ),
sa . Column (
"provider_id" ,
sa . String ( 36 ),
sa . ForeignKey ( "providers.id" , ondelete = "CASCADE" ),
nullable = False ,
index = True ,
),
sa . Column ( "window_start" , sa . DateTime ( timezone = True ), nullable = False , index = True ),
sa . Column ( "window_end" , sa . DateTime ( timezone = True ), nullable = False ),
sa . Column ( "total_requests" , sa . Integer , server_default = "0" ),
sa . Column ( "successful_requests" , sa . Integer , server_default = "0" ),
sa . Column ( "failed_requests" , sa . Integer , server_default = "0" ),
sa . Column ( "avg_response_time_ms" , sa . Float , server_default = "0.0" ),
sa . Column ( "total_response_time_ms" , sa . Float , server_default = "0.0" ),
sa . Column ( "total_cost_usd" , sa . Float , server_default = "0.0" ),
sa . Column (
"created_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
sa . Column (
"updated_at" , sa . DateTime ( timezone = True ), server_default = sa . func . now (), nullable = False
),
)
op . create_index (
"idx_provider_window" , "provider_usage_tracking" , [ "provider_id" , "window_start" ]
)
op . create_index ( "idx_window_time" , "provider_usage_tracking" , [ "window_start" , "window_end" ])
def downgrade () -> None :
# Drop tables in reverse order (respecting foreign key dependencies)
op . drop_table ( "provider_usage_tracking" )
op . drop_table ( "api_key_provider_mappings" )
op . drop_table ( "stats_user_daily" )
op . drop_table ( "stats_summary" )
op . drop_table ( "stats_daily" )
op . drop_table ( "request_candidates" )
op . drop_table ( "audit_logs" )
op . drop_table ( "announcement_reads" )
op . drop_table ( "announcements" )
op . drop_table ( "user_preferences" )
op . drop_table ( "system_configs" )
op . drop_table ( "user_quotas" )
op . drop_table ( "usage" )
op . drop_table ( "provider_api_keys" )
op . drop_table ( "model_mappings" )
op . drop_table ( "models" )
op . drop_table ( "provider_endpoints" )
op . drop_table ( "api_keys" )
op . drop_table ( "global_models" )
op . drop_table ( "providers" )
op . drop_table ( "users" )
# Drop ENUM types
op . execute ( "DROP TYPE IF EXISTS providerbillingtype" )
op . execute ( "DROP TYPE IF EXISTS userrole" )