from datetime import datetime from pydantic import BaseModel, Field, field_validator import re class UserBase(BaseModel): nickname: str = Field(..., min_length=2, max_length=50) class UserRegister(UserBase): login: str = Field(..., min_length=3, max_length=50) password: str = Field(..., min_length=6, max_length=100) @field_validator("login") @classmethod def validate_login(cls, v: str) -> str: if not re.match(r"^[a-zA-Z0-9_]+$", v): raise ValueError("Login can only contain letters, numbers, and underscores") return v.lower() class UserLogin(BaseModel): login: str password: str class UserUpdate(BaseModel): nickname: str | None = Field(None, min_length=2, max_length=50) class ShopItemPublic(BaseModel): """Minimal shop item info for public display""" id: int code: str name: str item_type: str rarity: str asset_data: dict | None = None class Config: from_attributes = True class UserPublic(UserBase): """Public user info visible to other users - minimal data""" id: int avatar_url: str | None = None role: str = "user" telegram_avatar_url: str | None = None # Only TG avatar is public created_at: datetime # Shop: equipped cosmetics (visible to others) equipped_frame: ShopItemPublic | None = None equipped_title: ShopItemPublic | None = None equipped_name_color: ShopItemPublic | None = None equipped_background: ShopItemPublic | None = None class Config: from_attributes = True class UserPrivate(UserPublic): """Full user info visible only to the user themselves""" login: str telegram_id: int | None = None telegram_username: str | None = None telegram_first_name: str | None = None telegram_last_name: str | None = None # Notification settings notify_events: bool = True notify_disputes: bool = True notify_moderation: bool = True # Shop: coins balance (only visible to self) coins_balance: int = 0 class TokenResponse(BaseModel): access_token: str token_type: str = "bearer" user: UserPrivate class TelegramLink(BaseModel): telegram_id: int telegram_username: str | None = None class PasswordChange(BaseModel): current_password: str = Field(..., min_length=6) new_password: str = Field(..., min_length=6, max_length=100) class UserStats(BaseModel): """Статистика пользователя по марафонам""" marathons_count: int wins_count: int completed_assignments: int total_points_earned: int class UserProfilePublic(BaseModel): """Публичный профиль пользователя со статистикой""" id: int nickname: str avatar_url: str | None = None telegram_avatar_url: str | None = None role: str = "user" created_at: datetime stats: UserStats # Equipped cosmetics equipped_frame: ShopItemPublic | None = None equipped_title: ShopItemPublic | None = None equipped_name_color: ShopItemPublic | None = None equipped_background: ShopItemPublic | None = None class Config: from_attributes = True class NotificationSettings(BaseModel): """Notification settings for Telegram bot""" notify_events: bool = True notify_disputes: bool = True notify_moderation: bool = True class Config: from_attributes = True class NotificationSettingsUpdate(BaseModel): """Update notification settings""" notify_events: bool | None = None notify_disputes: bool | None = None notify_moderation: bool | None = None