from pydantic import BaseModel, EmailStr, Field, model_validator from typing import Optional, List, Any from datetime import datetime from app.models.models import KeyMembershipType, AccountStatus, KeyStatus # ========== 账号相关 ========== class AccountBase(BaseModel): """账号基础信息 (用于创建/更新)""" email: str token: Optional[str] = Field(None, description="兼容旧字段: user_id::jwt") access_token: Optional[str] = Field(None, description="Access Token") refresh_token: Optional[str] = Field(None, description="Refresh Token") workos_session_token: Optional[str] = Field(None, description="WorkosCursorSessionToken") password: Optional[str] = None remark: Optional[str] = None class AccountCreate(AccountBase): """创建账号 (兼容旧字段)""" @model_validator(mode='before') @classmethod def ensure_token(cls, data: Any) -> Any: """确保至少提供一个 Token""" if isinstance(data, dict): if not data.get('token'): for field in ("workos_session_token", "access_token"): if data.get(field): data['token'] = data[field] break return data class AccountUpdate(BaseModel): email: Optional[str] = None token: Optional[str] = None access_token: Optional[str] = None refresh_token: Optional[str] = None workos_session_token: Optional[str] = None password: Optional[str] = None status: Optional[AccountStatus] = None remark: Optional[str] = None class AccountResponse(BaseModel): """账号响应 (匹配 CursorAccount 模型)""" id: int email: str token: str access_token: Optional[str] = None refresh_token: Optional[str] = None workos_session_token: Optional[str] = None password: Optional[str] = None status: AccountStatus account_type: Optional[str] = None membership_type: Optional[str] = None trial_days_remaining: int = 0 usage_limit: int = 0 usage_used: int = 0 usage_remaining: int = 0 usage_percent: float = 0 total_requests: int = 0 locked_by_key_id: Optional[int] = None last_analyzed_at: Optional[datetime] = None remark: Optional[str] = None created_at: datetime updated_at: datetime class Config: from_attributes = True class AccountImport(BaseModel): """批量导入账号""" accounts: List[AccountCreate] # ========== 外部系统批量上传 ========== class ExternalAccountItem(BaseModel): """外部系统上传的账号项""" email: str access_token: str refresh_token: Optional[str] = None workos_session_token: Optional[str] = None membership_type: Optional[str] = "free" # Cursor账号类型: free/free_trial/pro/business remark: Optional[str] = None class ExternalBatchUpload(BaseModel): """外部系统批量上传请求""" accounts: List[ExternalAccountItem] update_existing: bool = True # 是否更新已存在的账号 class ExternalBatchResponse(BaseModel): """外部系统批量上传响应""" success: bool total: int created: int updated: int failed: int errors: List[str] = [] # ========== 激活码相关 ========== class KeyBase(BaseModel): membership_type: KeyMembershipType = KeyMembershipType.PRO # pro=高级模型, auto=无限换号 quota: int = 500 # 总额度 (仅Pro有效) valid_days: int = 30 # 有效天数,0表示永久 (仅Auto有效) max_devices: int = 2 # 最大设备数 remark: Optional[str] = None class KeyCreate(KeyBase): key: Optional[str] = None # 不提供则自动生成 count: int = 1 # 批量生成数量 class KeyUpdate(BaseModel): membership_type: Optional[KeyMembershipType] = None quota: Optional[int] = None valid_days: Optional[int] = None max_devices: Optional[int] = None status: Optional[KeyStatus] = None remark: Optional[str] = None class KeyAddQuota(BaseModel): """叠加额度(只加额度不加时间)""" add_quota: int class KeyResponse(BaseModel): id: int key: str status: KeyStatus membership_type: KeyMembershipType quota: int quota_used: int quota_remaining: Optional[int] = None # 剩余额度(计算字段) valid_days: int = 30 # 有效天数 (映射自 duration_days) first_activated_at: Optional[datetime] = None expire_at: Optional[datetime] = None max_devices: int switch_count: int = 0 last_switch_at: Optional[datetime] = None current_account_id: Optional[int] = None remark: Optional[str] = None created_at: datetime updated_at: datetime class Config: from_attributes = True # ========== 客户端 API 相关 ========== class VerifyKeyRequest(BaseModel): key: str device_id: Optional[str] = None # 设备标识 class VerifyKeyResponse(BaseModel): success: bool message: Optional[str] = None error: Optional[str] = None # 成功时返回 email: Optional[str] = None accessToken: Optional[str] = None refreshToken: Optional[str] = None WorkosCursorSessionToken: Optional[str] = None membership_type: Optional[str] = None # 额度信息 quota: Optional[int] = None # 总额度 quotaUsed: Optional[int] = None # 已用额度 quotaRemaining: Optional[int] = None # 剩余额度 quotaCost: Optional[int] = None # 每次换号消耗 expireDate: Optional[str] = None class SwitchAccountRequest(BaseModel): key: str device_id: Optional[str] = None # 设备标识 class SwitchAccountResponse(BaseModel): success: bool message: Optional[str] = None error: Optional[str] = None email: Optional[str] = None accessToken: Optional[str] = None refreshToken: Optional[str] = None WorkosCursorSessionToken: Optional[str] = None membership_type: Optional[str] = None # 额度信息 quotaUsed: Optional[int] = None quotaRemaining: Optional[int] = None # ========== 统计相关 ========== class DashboardStats(BaseModel): total_accounts: int active_accounts: int pro_accounts: int total_keys: int active_keys: int today_usage: int # ========== 认证相关 ========== class Token(BaseModel): access_token: str token_type: str = "bearer" class LoginRequest(BaseModel): username: str password: str # ========== 全局设置相关 ========== class GlobalSettingsResponse(BaseModel): """全局设置响应""" # ===== 密钥策略 ===== key_max_devices: int = 2 # 主密钥最大设备数 auto_merge_enabled: bool = True # 是否启用同类型密钥自动合并 # ===== 自动检测开关 ===== auto_analyze_enabled: bool = False # 是否启用自动账号分析 auto_switch_enabled: bool = True # 是否启用自动换号 # ===== 账号分析设置 ===== account_analyze_interval: int = 300 # 账号分析间隔(秒) account_analyze_batch_size: int = 10 # 每批分析账号数量 # ===== 换号阈值 ===== auto_switch_threshold: int = 98 # Auto池自动换号阈值(用量百分比) pro_switch_threshold: int = 98 # Pro池自动换号阈值(用量百分比) # ===== 换号限制 ===== max_switch_per_day: int = 50 # 每日最大换号次数 auto_daily_switches: int = 999 # Auto密钥每日换号次数限制 auto_switch_interval: int = 0 # Auto密钥换号冷却时间(分钟), 0表示无限制 pro_quota_per_switch: int = 1 # Pro密钥每次换号消耗积分 class GlobalSettingsUpdate(BaseModel): """更新全局设置""" # ===== 密钥策略 ===== key_max_devices: Optional[int] = None auto_merge_enabled: Optional[bool] = None # ===== 自动检测开关 ===== auto_analyze_enabled: Optional[bool] = None auto_switch_enabled: Optional[bool] = None # ===== 账号分析设置 ===== account_analyze_interval: Optional[int] = None account_analyze_batch_size: Optional[int] = None # ===== 换号阈值 ===== auto_switch_threshold: Optional[int] = None pro_switch_threshold: Optional[int] = None # ===== 换号限制 ===== max_switch_per_day: Optional[int] = None auto_daily_switches: Optional[int] = None auto_switch_interval: Optional[int] = None pro_quota_per_switch: Optional[int] = None # ========== 批量操作相关 ========== class BatchExtendRequest(BaseModel): """批量延长请求""" key_ids: List[int] # 激活码ID列表 extend_days: int = 0 # 延长天数(Auto和Pro都可用) add_quota: int = 0 # 增加额度(仅Pro有效) class BatchExtendResponse(BaseModel): """批量延长响应""" success: int failed: int errors: List[str] = [] # ========== 公告相关 ========== class AnnouncementCreate(BaseModel): """创建公告""" title: str content: str type: str = "info" # info/warning/error/success is_active: bool = True class AnnouncementUpdate(BaseModel): """更新公告""" title: Optional[str] = None content: Optional[str] = None type: Optional[str] = None is_active: Optional[bool] = None