integrate sub2api as upstream for auth/keys/usage via FastAPI BFF

Preserve local user table for superDream-specific features while syncing
user lifecycle, API key CRUD and usage queries through sub2api. Admin token
handles reads and user lifecycle; per-user tokens (Fernet-encrypted in DB)
handle key writes that admin endpoints do not expose.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
xuyong
2026-04-17 21:23:08 +08:00
parent 20e842a60a
commit 35c0b7de16
30 changed files with 1707 additions and 803 deletions

View File

@@ -1,10 +1,12 @@
from decimal import Decimal
from pydantic import BaseModel, EmailStr
from datetime import datetime, date
from typing import Optional, List
from __future__ import annotations
from datetime import datetime
from typing import Any, Dict, List, Optional
from pydantic import BaseModel
# ── Auth ──
# ── Auth ──────────────────────────────────────────────────────────────
class RegisterRequest(BaseModel):
email: str
@@ -36,144 +38,59 @@ class ResetPasswordRequest(BaseModel):
class UserResponse(BaseModel):
"""Local user view. Balance mirrored from sub2api when available."""
id: str
email: str
balance: Decimal
status: str
created_at: datetime
sub2api_user_id: Optional[int] = None
balance: float = 0.0
class Config:
from_attributes = True
# ── API Key ──
# ── API Key (shapes match sub2api dto.APIKey 1:1) ─────────────────────
class CreateKeyRequest(BaseModel):
name: str = ""
class ApiKeyResponse(BaseModel):
id: str
name: str
key_prefix: str
key_suffix: str
status: str
created_at: datetime
class Config:
from_attributes = True
group_id: Optional[int] = None
custom_key: Optional[str] = None
ip_whitelist: List[str] = []
ip_blacklist: List[str] = []
quota: Optional[float] = None
expires_in_days: Optional[int] = None
rate_limit_5h: Optional[float] = None
rate_limit_1d: Optional[float] = None
rate_limit_7d: Optional[float] = None
class ApiKeyCreatedResponse(BaseModel):
id: str
name: str
key: str
key_prefix: str
key_suffix: str
created_at: datetime
class UpdateKeyRequest(BaseModel):
name: Optional[str] = None
group_id: Optional[int] = None
status: Optional[str] = None
ip_whitelist: Optional[List[str]] = None
ip_blacklist: Optional[List[str]] = None
quota: Optional[float] = None
expires_at: Optional[str] = None
reset_quota: Optional[bool] = None
rate_limit_5h: Optional[float] = None
rate_limit_1d: Optional[float] = None
rate_limit_7d: Optional[float] = None
reset_rate_limit_usage: Optional[bool] = None
# ── Wallet ──
# ── Usage ─────────────────────────────────────────────────────────────
class RedeemCodeRequest(BaseModel):
code: str
class DashboardAPIKeysUsageRequest(BaseModel):
api_key_ids: List[int]
class TransactionResponse(BaseModel):
id: str
type: str
amount: Decimal
balance_after: Decimal
reference_id: str
created_at: datetime
class Config:
from_attributes = True
class BalanceResponse(BaseModel):
balance: Decimal
# ── Models ──
class ModelPricingResponse(BaseModel):
id: int
model_name: str
provider: str
input_price_per_1k: Decimal
output_price_per_1k: Decimal
status: str
updated_at: datetime
class Config:
from_attributes = True
# ── Example (legacy) ──
class ExampleCreate(BaseModel):
name: str
description: str = ""
class ExampleResponse(BaseModel):
id: str
name: str
description: str
created_at: datetime
# ── Common ──
# ── Common ────────────────────────────────────────────────────────────
class MessageResponse(BaseModel):
message: str
# ── Usage ──
class UsageSummaryResponse(BaseModel):
today_tokens: int
today_cost: Decimal
month_tokens: int
month_cost: Decimal
total_requests: int
class DailyUsageResponse(BaseModel):
date: date
total_tokens: int
cost: Decimal
requests: int
class ModelUsageResponse(BaseModel):
model: str
total_tokens: int
cost: Decimal
requests: int
class KeyUsageResponse(BaseModel):
key_id: str
key_name: str
key_prefix: str
key_suffix: str
total_tokens: int
cost: Decimal
requests: int
class UsageLogResponse(BaseModel):
id: int
key_id: str
model: str
prompt_tokens: int
completion_tokens: int
total_tokens: int
cost: Decimal
request_time: datetime
status: str
class Config:
from_attributes = True
JSONDict = Dict[str, Any]