from typing import Literal, Optional from pydantic import BaseModel, Field LanguagePreference = Literal["zh", "en"] class RegisterRequest(BaseModel): phone: str = Field(..., min_length=11, max_length=11, description="手机号(11位)") password: str = Field(..., min_length=6, description="密码(至少6位)") nickname: str = Field(..., min_length=1, max_length=50, description="昵称") email: Optional[str] = Field(None, description="邮箱(可选)") agreed_to_terms: bool = Field(..., description="是否同意用户协议和隐私政策") language: Optional[LanguagePreference] = Field( None, description="device language at signup; only used when creating a new user", ) class LoginRequest(BaseModel): phone: str = Field(..., min_length=11, max_length=11, description="手机号(11位)") password: str = Field(..., min_length=1, description="密码") agreed_to_terms: bool = Field(..., description="是否同意用户协议和隐私政策") class TokenResponse(BaseModel): access_token: str refresh_token: str token_type: str = "bearer" class RefreshTokenRequest(BaseModel): refresh_token: str = Field(..., description="刷新令牌") class UserResponse(BaseModel): id: str phone: str email: Optional[str] = None nickname: str avatar_url: Optional[str] = None subscription_type: str created_at: str language_preference: LanguagePreference = "zh" class SendSmsRequest(BaseModel): phone: str = Field(..., min_length=11, max_length=11, description="手机号(11位)") purpose: str = Field( ..., description="用途:register/login/reset_password/change_phone" ) class SendSmsResponse(BaseModel): success: bool message: str expires_in: Optional[int] = None class SmsLoginRequest(BaseModel): phone: str = Field(..., min_length=11, max_length=11, description="手机号(11位)") code: str = Field(..., min_length=6, max_length=6, description="验证码(6位)") agreed_to_terms: bool = Field(..., description="是否同意用户协议和隐私政策") nickname: Optional[str] = Field( None, max_length=50, description="昵称(注册时必填,登录时可选)" ) language: Optional[LanguagePreference] = Field( None, description="device language at signup; only used when creating a new user", ) class MockSmsLoginRequest(BaseModel): """开发/评测专用:与 MOCK_SMS_LOGIN_ENABLED 联用,跳过短信校验。""" phone: str = Field(..., min_length=11, max_length=11, description="手机号(11位)") agreed_to_terms: bool = Field(..., description="是否同意用户协议和隐私政策") nickname: Optional[str] = Field( None, max_length=50, description="新用户昵称(可选)" ) language: Optional[LanguagePreference] = Field( None, description="device language at signup; only used when creating a new user", ) class SmsRegisterRequest(BaseModel): phone: str = Field(..., min_length=11, max_length=11, description="手机号(11位)") code: str = Field(..., min_length=6, max_length=6, description="验证码(6位)") password: str = Field(..., min_length=6, description="密码(至少6位)") nickname: str = Field(..., min_length=1, max_length=50, description="昵称") email: Optional[str] = Field(None, description="邮箱(可选)") agreed_to_terms: bool = Field(..., description="是否同意用户协议和隐私政策") language: Optional[LanguagePreference] = Field( None, description="device language at signup; only used when creating a new user", ) class ResetPasswordRequest(BaseModel): phone: str = Field(..., min_length=11, max_length=11, description="手机号(11位)") code: str = Field(..., min_length=6, max_length=6, description="验证码(6位)") new_password: str = Field(..., min_length=6, description="新密码(至少6位)") class ChangePasswordRequest(BaseModel): old_password: str = Field(..., min_length=1, description="旧密码") new_password: str = Field(..., min_length=6, description="新密码(至少6位)") class ChangePhoneRequest(BaseModel): new_phone: str = Field( ..., min_length=11, max_length=11, description="新手机号(11位)" ) code: str = Field(..., min_length=6, max_length=6, description="验证码(6位)") class UpdateNicknameRequest(BaseModel): nickname: str = Field( ..., min_length=1, max_length=50, description="昵称(1-50个字符)" ) class AvatarUploadResponse(BaseModel): avatar_url: str class SetAvatarPresetRequest(BaseModel): preset_id: str = Field( ..., min_length=2, max_length=2, pattern=r"^\d{2}$", description="预设编号,如 01–08", ) class AvatarPresetItem(BaseModel): id: str url: str