from pydantic import BaseModel, Field, validator from typing import Optional, List class TicketExchangeRequest(BaseModel): app_id: str target_app_id: str user_mobile: str timestamp: int sign: str class TicketExchangeResponse(BaseModel): ticket: str redirect_url: str class TicketValidateRequest(BaseModel): ticket: str app_id: str # The app trying to validate sign: str timestamp: int class TicketValidateResponse(BaseModel): valid: bool user_id: Optional[int] = None mobile: Optional[str] = None mapped_key: Optional[str] = None mapped_email: Optional[str] = None class PasswordLoginRequest(BaseModel): app_id: Optional[str] = Field( None, description="应用ID。不提供时为平台登录(返回access_token),提供时为应用SSO登录(返回ticket)" ) identifier: str = Field( ..., description="用户标识:手机号、映射key或映射email" ) password: str = Field( ..., description="用户密码" ) remember_me: bool = Field( False, description="是否记住登录(长期有效)" ) sign: Optional[str] = Field( None, description="签名(可选)。如果提供,必须同时提供timestamp。用于服务端安全调用" ) timestamp: Optional[int] = Field( None, description="时间戳(可选)。如果提供,必须同时提供sign" ) @validator('timestamp') def validate_sign_timestamp(cls, v, values): """验证 sign 和 timestamp 必须同时提供或同时不提供""" sign = values.get('sign') if (sign and not v) or (not sign and v): raise ValueError('sign 和 timestamp 必须同时提供或同时不提供') return v class PasswordLoginResponse(BaseModel): ticket: Optional[str] = Field( None, description="票据。当提供app_id时返回,用于应用SSO登录" ) access_token: Optional[str] = Field( None, description="访问令牌。当未提供app_id时返回,用于平台登录" ) token_type: Optional[str] = Field( None, description="令牌类型,固定为 'bearer'" ) role: Optional[str] = Field( None, description="用户角色:SUPER_ADMIN / DEVELOPER / ORDINARY_USER" ) class SmsLoginRequest(BaseModel): mobile: str = Field(..., description="手机号") code: str = Field(..., description="验证码") remember_me: bool = Field( False, description="记住我:为 true 时 JWT 使用较长有效期(与密码登录一致)", ) app_id: Optional[str] = Field( None, description="应用ID。不提供时为平台登录(返回access_token),提供时为应用SSO登录(返回ticket)" ) sign: Optional[str] = Field( None, description="签名(可选)。如果提供,必须同时提供timestamp。用于服务端安全调用" ) timestamp: Optional[int] = Field( None, description="时间戳(可选)。如果提供,必须同时提供sign" ) @validator('timestamp') def validate_sign_timestamp(cls, v, values): """验证 sign 和 timestamp 必须同时提供或同时不提供""" sign = values.get('sign') if (sign and not v) or (not sign and v): raise ValueError('sign 和 timestamp 必须同时提供或同时不提供') return v class UserRegisterRequest(BaseModel): mobile: str password: str name: str # Added field app_id: Optional[str] = None # Optional, if registering via an app context class AdminPasswordResetRequest(BaseModel): user_id: int admin_password: str class AdminPasswordResetResponse(BaseModel): new_password: str class ChangePasswordRequest(BaseModel): old_password: str new_password: str class UserMappingResponse(BaseModel): app_name: str app_id: str protocol_type: str mapped_key: Optional[str] = None mapped_email: Optional[str] = None is_active: bool class MyMappingsResponse(BaseModel): total: int items: List[UserMappingResponse] class UserPromoteRequest(BaseModel): user_id: int new_role: str class SsoLoginRequest(BaseModel): app_id: str = Field( ..., description="应用ID(必填)。只支持 SIMPLE_API 类型的应用" ) username: Optional[str] = Field( None, description="用户名(条件必填)。如果用户未登录UAP平台,则必须提供 username 和 password" ) password: Optional[str] = Field( None, description="密码(条件必填)。如果用户未登录UAP平台,则必须提供 username 和 password" ) class Config: json_schema_extra = { "examples": [ { "description": "场景1:用户已登录UAP(使用会话)", "value": { "app_id": "my_app_123" } }, { "description": "场景2:用户未登录(使用凭据)", "value": { "app_id": "my_app_123", "username": "13800138000", "password": "password123" } } ] } class SsoLoginResponse(BaseModel): redirect_url: str = Field( ..., description="带票据的重定向URL,格式:{应用回调URL}?ticket={票据}。前端应直接跳转到此URL" ) class LaunchpadAppResponse(BaseModel): """快捷导航应用响应模型(包含分类和描述)""" app_name: str app_id: str protocol_type: str mapped_key: Optional[str] = None mapped_email: Optional[str] = None is_active: bool description: Optional[str] = None # 应用描述 category_id: Optional[int] = None # 分类ID category_name: Optional[str] = None # 分类名称 class LaunchpadAppsResponse(BaseModel): """快捷导航应用列表响应""" total: int # 总数量(方便前端使用) items: List[LaunchpadAppResponse]