diff --git a/bun.lock b/bun.lock index e71d0d264..b0b7d3c41 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "claude-code-hub", diff --git a/drizzle/0020_lazy_grandmaster.sql b/drizzle/0020_lazy_grandmaster.sql new file mode 100644 index 000000000..0ef7abe5a --- /dev/null +++ b/drizzle/0020_lazy_grandmaster.sql @@ -0,0 +1,5 @@ +ALTER TABLE "users" ADD COLUMN "limit_5h_usd" numeric(10, 2);--> statement-breakpoint +ALTER TABLE "users" ADD COLUMN "limit_weekly_usd" numeric(10, 2);--> statement-breakpoint +ALTER TABLE "users" ADD COLUMN "limit_monthly_usd" numeric(10, 2);--> statement-breakpoint +ALTER TABLE "users" ADD COLUMN "limit_concurrent_sessions" integer DEFAULT 0;--> statement-breakpoint +ALTER TABLE "users" ADD COLUMN "tags" text; \ No newline at end of file diff --git a/drizzle/meta/0020_snapshot.json b/drizzle/meta/0020_snapshot.json new file mode 100644 index 000000000..e8c98146a --- /dev/null +++ b/drizzle/meta/0020_snapshot.json @@ -0,0 +1,1526 @@ +{ + "id": "f6399f6b-6f3b-4c8b-bc12-7cb678fd5f08", + "prevId": "4ba39e7a-e353-4ed1-8e18-934a56fb0af6", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.error_rules": { + "name": "error_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'regex'" + }, + "category": { + "name": "category", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_error_rules_enabled": { + "name": "idx_error_rules_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_pattern": { + "name": "unique_pattern", + "columns": [ + { + "expression": "pattern", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_category": { + "name": "idx_category", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_match_type": { + "name": "idx_match_type", + "columns": [ + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.keys": { + "name": "keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "can_login_web_ui": { + "name": "can_login_web_ui", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_keys_user_id": { + "name": "idx_keys_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_created_at": { + "name": "idx_keys_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_deleted_at": { + "name": "idx_keys_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_request": { + "name": "message_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "provider_chain": { + "name": "provider_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "api_type": { + "name": "api_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "original_model": { + "name": "original_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cache_creation_input_tokens": { + "name": "cache_creation_input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "blocked_by": { + "name": "blocked_by", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "blocked_reason": { + "name": "blocked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "messages_count": { + "name": "messages_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_message_request_user_date_cost": { + "name": "idx_message_request_user_date_cost", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_query": { + "name": "idx_message_request_user_query", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id": { + "name": "idx_message_request_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_endpoint": { + "name": "idx_message_request_endpoint", + "columns": [ + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_id": { + "name": "idx_message_request_provider_id", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_id": { + "name": "idx_message_request_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key": { + "name": "idx_message_request_key", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_created_at": { + "name": "idx_message_request_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_deleted_at": { + "name": "idx_message_request_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_prices": { + "name": "model_prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "model_name": { + "name": "model_name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "price_data": { + "name": "price_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_model_prices_latest": { + "name": "idx_model_prices_latest", + "columns": [ + { + "expression": "model_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_model_name": { + "name": "idx_model_prices_model_name", + "columns": [ + { + "expression": "model_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_created_at": { + "name": "idx_model_prices_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_settings": { + "name": "notification_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "circuit_breaker_enabled": { + "name": "circuit_breaker_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "circuit_breaker_webhook": { + "name": "circuit_breaker_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "daily_leaderboard_enabled": { + "name": "daily_leaderboard_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "daily_leaderboard_webhook": { + "name": "daily_leaderboard_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "daily_leaderboard_time": { + "name": "daily_leaderboard_time", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false, + "default": "'09:00'" + }, + "daily_leaderboard_top_n": { + "name": "daily_leaderboard_top_n", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "cost_alert_enabled": { + "name": "cost_alert_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cost_alert_webhook": { + "name": "cost_alert_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "cost_alert_threshold": { + "name": "cost_alert_threshold", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": false, + "default": "'0.80'" + }, + "cost_alert_check_interval": { + "name": "cost_alert_check_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 60 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.providers": { + "name": "providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "weight": { + "name": "weight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false, + "default": "'1.0'" + }, + "group_tag": { + "name": "group_tag", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'claude'" + }, + "model_redirects": { + "name": "model_redirects", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "join_claude_pool": { + "name": "join_claude_pool", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "codex_instructions_strategy": { + "name": "codex_instructions_strategy", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false, + "default": "'auto'" + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "circuit_breaker_failure_threshold": { + "name": "circuit_breaker_failure_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "circuit_breaker_open_duration": { + "name": "circuit_breaker_open_duration", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1800000 + }, + "circuit_breaker_half_open_success_threshold": { + "name": "circuit_breaker_half_open_success_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 2 + }, + "proxy_url": { + "name": "proxy_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "proxy_fallback_to_direct": { + "name": "proxy_fallback_to_direct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "first_byte_timeout_streaming_ms": { + "name": "first_byte_timeout_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 30000 + }, + "streaming_idle_timeout_ms": { + "name": "streaming_idle_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10000 + }, + "request_timeout_non_streaming_ms": { + "name": "request_timeout_non_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 600000 + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tpm": { + "name": "tpm", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "rpm": { + "name": "rpm", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "rpd": { + "name": "rpd", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "cc": { + "name": "cc", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_providers_enabled_priority": { + "name": "idx_providers_enabled_priority", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "weight", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_group": { + "name": "idx_providers_group", + "columns": [ + { + "expression": "group_tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_created_at": { + "name": "idx_providers_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_deleted_at": { + "name": "idx_providers_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sensitive_words": { + "name": "sensitive_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "word": { + "name": "word", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'contains'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_sensitive_words_enabled": { + "name": "idx_sensitive_words_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_sensitive_words_created_at": { + "name": "idx_sensitive_words_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_settings": { + "name": "system_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "site_title": { + "name": "site_title", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "default": "'Claude Code Hub'" + }, + "allow_global_usage_view": { + "name": "allow_global_usage_view", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "currency_display": { + "name": "currency_display", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "enable_auto_cleanup": { + "name": "enable_auto_cleanup", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "cleanup_retention_days": { + "name": "cleanup_retention_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "cleanup_schedule": { + "name": "cleanup_schedule", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false, + "default": "'0 2 * * *'" + }, + "cleanup_batch_size": { + "name": "cleanup_batch_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10000 + }, + "enable_client_version_check": { + "name": "enable_client_version_check", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "rpm_limit": { + "name": "rpm_limit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 60 + }, + "daily_limit_usd": { + "name": "daily_limit_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false, + "default": "'100.00'" + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_users_active_role_sort": { + "name": "idx_users_active_role_sort", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_created_at": { + "name": "idx_users_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_deleted_at": { + "name": "idx_users_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 4e9185834..7bc34103a 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -141,6 +141,13 @@ "when": 1763393401417, "tag": "0019_far_whirlwind", "breakpoints": true + }, + { + "idx": 20, + "version": "7", + "when": 1763453780231, + "tag": "0020_lazy_grandmaster", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/actions/users.ts b/src/actions/users.ts index 604889f1e..1fef320f0 100644 --- a/src/actions/users.ts +++ b/src/actions/users.ts @@ -63,7 +63,12 @@ export async function getUsers(): Promise { role: user.role, rpm: user.rpm, dailyQuota: user.dailyQuota, + limit5hUsd: user.limit5hUsd, + limitWeeklyUsd: user.limitWeeklyUsd, + limitMonthlyUsd: user.limitMonthlyUsd, + limitConcurrentSessions: user.limitConcurrentSessions, providerGroup: user.providerGroup || undefined, + tags: user.tags || undefined, keys: keys.map((key) => { const stats = statisticsMap.get(key.id); // 用户可以查看和复制自己的密钥,管理员可以查看和复制所有密钥 @@ -111,7 +116,12 @@ export async function getUsers(): Promise { role: user.role, rpm: user.rpm, dailyQuota: user.dailyQuota, + limit5hUsd: user.limit5hUsd, + limitWeeklyUsd: user.limitWeeklyUsd, + limitMonthlyUsd: user.limitMonthlyUsd, + limitConcurrentSessions: user.limitConcurrentSessions, providerGroup: user.providerGroup || undefined, + tags: user.tags || undefined, keys: [], }; } @@ -130,8 +140,13 @@ export async function addUser(data: { name: string; note?: string; providerGroup?: string | null; + tags?: string | null; rpm?: number; dailyQuota?: number; + limit5hUsd?: number | null; + limitWeeklyUsd?: number | null; + limitMonthlyUsd?: number | null; + limitConcurrentSessions?: number; }): Promise { try { // Get translations for error messages @@ -152,8 +167,13 @@ export async function addUser(data: { name: data.name, note: data.note || "", providerGroup: data.providerGroup || "", + tags: data.tags || "", rpm: data.rpm || USER_DEFAULTS.RPM, dailyQuota: data.dailyQuota || USER_DEFAULTS.DAILY_QUOTA, + limit5hUsd: data.limit5hUsd, + limitWeeklyUsd: data.limitWeeklyUsd, + limitMonthlyUsd: data.limitMonthlyUsd, + limitConcurrentSessions: data.limitConcurrentSessions, }); if (!validationResult.success) { @@ -170,8 +190,13 @@ export async function addUser(data: { name: validatedData.name, description: validatedData.note || "", providerGroup: validatedData.providerGroup || null, + tags: validatedData.tags || null, rpm: validatedData.rpm, dailyQuota: validatedData.dailyQuota, + limit5hUsd: validatedData.limit5hUsd, + limitWeeklyUsd: validatedData.limitWeeklyUsd, + limitMonthlyUsd: validatedData.limitMonthlyUsd, + limitConcurrentSessions: validatedData.limitConcurrentSessions, }); // 为新用户创建默认密钥 @@ -205,8 +230,13 @@ export async function editUser( name?: string; note?: string; providerGroup?: string | null; + tags?: string | null; rpm?: number; dailyQuota?: number; + limit5hUsd?: number | null; + limitWeeklyUsd?: number | null; + limitMonthlyUsd?: number | null; + limitConcurrentSessions?: number; } ): Promise { try { @@ -223,7 +253,15 @@ export async function editUser( } // 定义敏感字段列表(仅管理员可修改) - const sensitiveFields = ["rpm", "dailyQuota", "providerGroup"] as const; + const sensitiveFields = [ + "rpm", + "dailyQuota", + "limit5hUsd", + "limitWeeklyUsd", + "limitMonthlyUsd", + "limitConcurrentSessions", + "providerGroup", + ] as const; const hasSensitiveFields = sensitiveFields.some((field) => data[field] !== undefined); // 权限检查:区分三种情况 @@ -245,8 +283,13 @@ export async function editUser( name: validatedData.name, description: validatedData.note, providerGroup: validatedData.providerGroup, + tags: validatedData.tags, rpm: validatedData.rpm, dailyQuota: validatedData.dailyQuota, + limit5hUsd: validatedData.limit5hUsd, + limitWeeklyUsd: validatedData.limitWeeklyUsd, + limitMonthlyUsd: validatedData.limitMonthlyUsd, + limitConcurrentSessions: validatedData.limitConcurrentSessions, }); } else if (session.user.id === userId) { // 普通用户修改自己的信息 @@ -277,6 +320,7 @@ export async function editUser( await updateUser(userId, { name: validatedData.name, description: validatedData.note, + tags: validatedData.tags, }); } else { // 普通用户尝试修改他人信息 diff --git a/src/drizzle/schema.ts b/src/drizzle/schema.ts index d1233be86..a1a9c5ef2 100644 --- a/src/drizzle/schema.ts +++ b/src/drizzle/schema.ts @@ -19,9 +19,21 @@ export const users = pgTable('users', { name: varchar('name').notNull(), description: text('description'), role: varchar('role').default('user'), + + // 兼容字段(保留向后兼容) rpmLimit: integer('rpm_limit').default(60), dailyLimitUsd: numeric('daily_limit_usd', { precision: 10, scale: 2 }).default('100.00'), + + // 统一的金额限流配置(与 keys 表对齐) + limit5hUsd: numeric('limit_5h_usd', { precision: 10, scale: 2 }), + limitWeeklyUsd: numeric('limit_weekly_usd', { precision: 10, scale: 2 }), + limitMonthlyUsd: numeric('limit_monthly_usd', { precision: 10, scale: 2 }), + limitConcurrentSessions: integer('limit_concurrent_sessions').default(0), + + // 分组和标签 providerGroup: varchar('provider_group', { length: 50 }), + tags: text('tags'), // 逗号分隔的标签列表,用于备注和区分用户 + createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow(), deletedAt: timestamp('deleted_at', { withTimezone: true }), diff --git a/src/lib/validation/schemas.ts b/src/lib/validation/schemas.ts index c0914dccd..1e2289d0b 100644 --- a/src/lib/validation/schemas.ts +++ b/src/lib/validation/schemas.ts @@ -14,6 +14,8 @@ export const CreateUserSchema = z.object({ name: z.string().min(1, "用户名不能为空").max(64, "用户名不能超过64个字符"), note: z.string().max(200, "备注不能超过200个字符").optional().default(""), providerGroup: z.string().max(50, "供应商分组不能超过50个字符").optional().default(""), + tags: z.string().max(500, "标签不能超过500个字符").optional(), + // 兼容字段 rpm: z.coerce .number() .int("RPM必须是整数") @@ -27,6 +29,32 @@ export const CreateUserSchema = z.object({ .max(USER_LIMITS.DAILY_QUOTA.MAX, `每日额度不能超过${USER_LIMITS.DAILY_QUOTA.MAX}美元`) .optional() .default(USER_DEFAULTS.DAILY_QUOTA), + // 统一的限额配置 + limit5hUsd: z.coerce + .number() + .min(0, "5小时消费上限不能为负数") + .max(10000, "5小时消费上限不能超过10000美元") + .nullable() + .optional(), + limitWeeklyUsd: z.coerce + .number() + .min(0, "周消费上限不能为负数") + .max(50000, "周消费上限不能超过50000美元") + .nullable() + .optional(), + limitMonthlyUsd: z.coerce + .number() + .min(0, "月消费上限不能为负数") + .max(200000, "月消费上限不能超过200000美元") + .nullable() + .optional(), + limitConcurrentSessions: z.coerce + .number() + .int("并发Session上限必须是整数") + .min(0, "并发Session上限不能为负数") + .max(1000, "并发Session上限不能超过1000") + .optional() + .default(0), }); /** @@ -36,6 +64,8 @@ export const UpdateUserSchema = z.object({ name: z.string().min(1, "用户名不能为空").max(64, "用户名不能超过64个字符").optional(), note: z.string().max(200, "备注不能超过200个字符").optional(), providerGroup: z.string().max(50, "供应商分组不能超过50个字符").nullable().optional(), + tags: z.string().max(500, "标签不能超过500个字符").nullable().optional(), + // 兼容字段 rpm: z.coerce .number() .int("RPM必须是整数") @@ -47,6 +77,31 @@ export const UpdateUserSchema = z.object({ .min(USER_LIMITS.DAILY_QUOTA.MIN, `每日额度不能低于${USER_LIMITS.DAILY_QUOTA.MIN}美元`) .max(USER_LIMITS.DAILY_QUOTA.MAX, `每日额度不能超过${USER_LIMITS.DAILY_QUOTA.MAX}美元`) .optional(), + // 统一的限额配置 + limit5hUsd: z.coerce + .number() + .min(0, "5小时消费上限不能为负数") + .max(10000, "5小时消费上限不能超过10000美元") + .nullable() + .optional(), + limitWeeklyUsd: z.coerce + .number() + .min(0, "周消费上限不能为负数") + .max(50000, "周消费上限不能超过50000美元") + .nullable() + .optional(), + limitMonthlyUsd: z.coerce + .number() + .min(0, "月消费上限不能为负数") + .max(200000, "月消费上限不能超过200000美元") + .nullable() + .optional(), + limitConcurrentSessions: z.coerce + .number() + .int("并发Session上限必须是整数") + .min(0, "并发Session上限不能为负数") + .max(1000, "并发Session上限不能超过1000") + .optional(), }); /** diff --git a/src/repository/_shared/transformers.ts b/src/repository/_shared/transformers.ts index 4451b597f..51a63d7d1 100644 --- a/src/repository/_shared/transformers.ts +++ b/src/repository/_shared/transformers.ts @@ -12,9 +12,21 @@ export function toUser(dbUser: any): User { ...dbUser, description: dbUser?.description || "", role: (dbUser?.role as User["role"]) || "user", + + // 兼容字段 rpm: dbUser?.rpm || 60, dailyQuota: dbUser?.dailyQuota ? parseFloat(dbUser.dailyQuota) : 0, + + // 统一的限额配置 + limit5hUsd: dbUser?.limit5hUsd ? parseFloat(dbUser.limit5hUsd) : null, + limitWeeklyUsd: dbUser?.limitWeeklyUsd ? parseFloat(dbUser.limitWeeklyUsd) : null, + limitMonthlyUsd: dbUser?.limitMonthlyUsd ? parseFloat(dbUser.limitMonthlyUsd) : null, + limitConcurrentSessions: dbUser?.limitConcurrentSessions ?? 0, + + // 分组和标签 providerGroup: dbUser?.providerGroup ?? null, + tags: dbUser?.tags ?? null, + createdAt: dbUser?.createdAt ? new Date(dbUser.createdAt) : new Date(), updatedAt: dbUser?.updatedAt ? new Date(dbUser.updatedAt) : new Date(), }; diff --git a/src/repository/user.ts b/src/repository/user.ts index 473e38223..9a1b6bcfb 100644 --- a/src/repository/user.ts +++ b/src/repository/user.ts @@ -12,7 +12,12 @@ export async function createUser(userData: CreateUserData): Promise { description: userData.description, rpmLimit: userData.rpm, dailyLimitUsd: userData.dailyQuota?.toString(), + limit5hUsd: userData.limit5hUsd?.toString(), + limitWeeklyUsd: userData.limitWeeklyUsd?.toString(), + limitMonthlyUsd: userData.limitMonthlyUsd?.toString(), + limitConcurrentSessions: userData.limitConcurrentSessions, providerGroup: userData.providerGroup, + tags: userData.tags, }; const [user] = await db.insert(users).values(dbData).returning({ @@ -22,7 +27,12 @@ export async function createUser(userData: CreateUserData): Promise { role: users.role, rpm: users.rpmLimit, dailyQuota: users.dailyLimitUsd, + limit5hUsd: users.limit5hUsd, + limitWeeklyUsd: users.limitWeeklyUsd, + limitMonthlyUsd: users.limitMonthlyUsd, + limitConcurrentSessions: users.limitConcurrentSessions, providerGroup: users.providerGroup, + tags: users.tags, createdAt: users.createdAt, updatedAt: users.updatedAt, deletedAt: users.deletedAt, @@ -40,7 +50,12 @@ export async function findUserList(limit: number = 50, offset: number = 0): Prom role: users.role, rpm: users.rpmLimit, dailyQuota: users.dailyLimitUsd, + limit5hUsd: users.limit5hUsd, + limitWeeklyUsd: users.limitWeeklyUsd, + limitMonthlyUsd: users.limitMonthlyUsd, + limitConcurrentSessions: users.limitConcurrentSessions, providerGroup: users.providerGroup, + tags: users.tags, createdAt: users.createdAt, updatedAt: users.updatedAt, deletedAt: users.deletedAt, @@ -63,7 +78,12 @@ export async function findUserById(id: number): Promise { role: users.role, rpm: users.rpmLimit, dailyQuota: users.dailyLimitUsd, + limit5hUsd: users.limit5hUsd, + limitWeeklyUsd: users.limitWeeklyUsd, + limitMonthlyUsd: users.limitMonthlyUsd, + limitConcurrentSessions: users.limitConcurrentSessions, providerGroup: users.providerGroup, + tags: users.tags, createdAt: users.createdAt, updatedAt: users.updatedAt, deletedAt: users.deletedAt, @@ -86,7 +106,12 @@ export async function updateUser(id: number, userData: UpdateUserData): Promise< description?: string; rpmLimit?: number; dailyLimitUsd?: string; + limit5hUsd?: string | null; + limitWeeklyUsd?: string | null; + limitMonthlyUsd?: string | null; + limitConcurrentSessions?: number; providerGroup?: string | null; + tags?: string | null; updatedAt?: Date; } @@ -97,7 +122,16 @@ export async function updateUser(id: number, userData: UpdateUserData): Promise< if (userData.description !== undefined) dbData.description = userData.description; if (userData.rpm !== undefined) dbData.rpmLimit = userData.rpm; if (userData.dailyQuota !== undefined) dbData.dailyLimitUsd = userData.dailyQuota.toString(); + if (userData.limit5hUsd !== undefined) + dbData.limit5hUsd = userData.limit5hUsd?.toString() ?? null; + if (userData.limitWeeklyUsd !== undefined) + dbData.limitWeeklyUsd = userData.limitWeeklyUsd?.toString() ?? null; + if (userData.limitMonthlyUsd !== undefined) + dbData.limitMonthlyUsd = userData.limitMonthlyUsd?.toString() ?? null; + if (userData.limitConcurrentSessions !== undefined) + dbData.limitConcurrentSessions = userData.limitConcurrentSessions; if (userData.providerGroup !== undefined) dbData.providerGroup = userData.providerGroup; + if (userData.tags !== undefined) dbData.tags = userData.tags; const [user] = await db .update(users) @@ -110,7 +144,12 @@ export async function updateUser(id: number, userData: UpdateUserData): Promise< role: users.role, rpm: users.rpmLimit, dailyQuota: users.dailyLimitUsd, + limit5hUsd: users.limit5hUsd, + limitWeeklyUsd: users.limitWeeklyUsd, + limitMonthlyUsd: users.limitMonthlyUsd, + limitConcurrentSessions: users.limitConcurrentSessions, providerGroup: users.providerGroup, + tags: users.tags, createdAt: users.createdAt, updatedAt: users.updatedAt, deletedAt: users.deletedAt, diff --git a/src/types/user.ts b/src/types/user.ts index 5f09a2329..9e4560371 100644 --- a/src/types/user.ts +++ b/src/types/user.ts @@ -6,9 +6,21 @@ export interface User { name: string; description: string; role: "admin" | "user"; + + // 兼容字段(保留向后兼容) rpm: number; // 每分钟请求数限制 dailyQuota: number; // 每日额度限制(美元) + + // 统一的限额配置(与 keys 对齐) + limit5hUsd?: number | null; // 5小时消费上限(美元) + limitWeeklyUsd?: number | null; // 周消费上限(美元) + limitMonthlyUsd?: number | null; // 月消费上限(美元) + limitConcurrentSessions?: number; // 并发 Session 上限 + + // 分组和标签 providerGroup: string | null; // 供应商分组 + tags?: string | null; // 逗号分隔的标签列表 + createdAt: Date; updatedAt: Date; deletedAt?: Date; @@ -20,9 +32,20 @@ export interface User { export interface CreateUserData { name: string; description: string; + + // 兼容字段 rpm?: number; // 可选,有默认值 dailyQuota?: number; // 可选,有默认值 - providerGroup?: string | null; // 可选,供应商分组 + + // 统一的限额配置 + limit5hUsd?: number | null; + limitWeeklyUsd?: number | null; + limitMonthlyUsd?: number | null; + limitConcurrentSessions?: number; + + // 分组和标签 + providerGroup?: string | null; + tags?: string | null; } /** @@ -31,9 +54,20 @@ export interface CreateUserData { export interface UpdateUserData { name?: string; description?: string; + + // 兼容字段 rpm?: number; dailyQuota?: number; - providerGroup?: string | null; // 可选,供应商分组 + + // 统一的限额配置 + limit5hUsd?: number | null; + limitWeeklyUsd?: number | null; + limitMonthlyUsd?: number | null; + limitConcurrentSessions?: number; + + // 分组和标签 + providerGroup?: string | null; + tags?: string | null; } /** @@ -75,9 +109,21 @@ export interface UserDisplay { name: string; note?: string; role: "admin" | "user"; + + // 兼容字段 rpm: number; dailyQuota: number; + + // 统一的限额配置 + limit5hUsd?: number | null; + limitWeeklyUsd?: number | null; + limitMonthlyUsd?: number | null; + limitConcurrentSessions?: number; + + // 分组和标签 providerGroup?: string | null; + tags?: string | null; + keys: UserKeyDisplay[]; }