from pydantic import BaseModel, Field, field_validator from datetime import datetime from typing import Optional class UserLogin(BaseModel): phone: str = Field(..., pattern=r"^1[3-9]\d{9}$", description="手机号") password: str = Field(..., min_length=6, max_length=50, description="密码") class UserRegister(BaseModel): phone: str = Field(..., pattern=r"^1[3-9]\d{9}$") password: str = Field(..., min_length=6, max_length=50) name: str = Field(..., min_length=2, max_length=50) subject: str = Field(default="", max_length=50) school: str = Field(default="", max_length=100) grade: str = Field(default="", max_length=50) class UserUpdate(BaseModel): name: Optional[str] = None avatar: Optional[str] = None subject: Optional[str] = None school: Optional[str] = None grade: Optional[str] = None class PasswordChange(BaseModel): old_password: str = Field(..., min_length=6, max_length=50, description="当前密码") new_password: str = Field(..., min_length=6, max_length=50, description="新密码") class UserOut(BaseModel): id: int phone: str name: str avatar: str = "" subject: str = "" school: str = "" grade: str = "" role: str = "teacher" credits: int = 0 total_credits_granted: int = 0 total_credits_used: int = 0 created_at: datetime | None = None @field_validator("avatar", "subject", "school", "grade", "name", "role", mode="before") @classmethod def _none_to_empty(cls, v): return "" if v is None else v model_config = {"from_attributes": True} class Token(BaseModel): access_token: str refresh_token: str token_type: str = "bearer" class TokenPayload(BaseModel): sub: int exp: datetime class ProfileRecentItem(BaseModel): id: int item_type: str title: str subtitle: str = "" status: str = "" updated_at: datetime | None = None class CreditTransactionOut(BaseModel): id: int amount: int action: str = "" description: str = "" balance_after: int = 0 created_at: datetime | None = None model_config = {"from_attributes": True} class CreditBenefitOut(BaseModel): credits: int = 0 daily_amount: int = 0 daily_claimed: bool = False next_claim_text: str = "" recent_checkins: list[CreditTransactionOut] = Field(default_factory=list) class ProfileStatsOut(BaseModel): credits: int = 0 total_credits_granted: int = 0 total_credits_used: int = 0 total_works: int = 0 draft_works: int = 0 published_works: int = 0 published_resources: int = 0 public_resources: int = 0 private_resources: int = 0 favorite_resources: int = 0 total_views: int = 0 total_downloads: int = 0 total_favorites: int = 0 classroom_activities: int = 0 active_classroom_activities: int = 0 classroom_responses: int = 0 community_posts: int = 0 community_favorites: int = 0 community_comments: int = 0 works_by_type: dict[str, int] = Field(default_factory=dict) resources_by_type: dict[str, int] = Field(default_factory=dict) recent_works: list[ProfileRecentItem] = Field(default_factory=list) recent_resources: list[ProfileRecentItem] = Field(default_factory=list) recent_interactions: list[ProfileRecentItem] = Field(default_factory=list) recent_credit_transactions: list[CreditTransactionOut] = Field(default_factory=list) class SendCodeRequest(BaseModel): phone: str = Field(..., pattern=r"^1[3-9]\d{9}$", description="手机号") class ResetPasswordRequest(BaseModel): phone: str = Field(..., pattern=r"^1[3-9]\d{9}$") code: str = Field(..., min_length=6, max_length=6, description="6位验证码") new_password: str = Field(..., min_length=6, max_length=50, description="新密码")