from datetime import datetime from typing import Optional from pydantic import BaseModel, EmailStr, Field, field_validator, model_validator, ConfigDict import re class UserBase(BaseModel): username: str = Field(..., min_length=3, max_length=50) email: EmailStr @field_validator('username') @classmethod def validate_username(cls, v: str) -> str: if not re.match(r'^[a-zA-Z0-9_-]+$', v): raise ValueError('Username must contain only alphanumeric characters, underscores, and dashes') return v class UserCreate(UserBase): password: str = Field(..., min_length=8, max_length=128) @field_validator('password') @classmethod def validate_password_strength(cls, v: str) -> str: if not re.search(r'[A-Z]', v): raise ValueError('Password must contain at least one uppercase letter') if not re.search(r'[a-z]', v): raise ValueError('Password must contain at least one lowercase letter') if not re.search(r'\d', v): raise ValueError('Password must contain at least one digit') return v class User(UserBase): id: int is_active: bool is_superuser: bool created_at: datetime model_config = ConfigDict(from_attributes=True) class UserCreateByAdmin(UserBase): password: str = Field(..., min_length=8, max_length=128) is_superuser: bool = False @field_validator('password') @classmethod def validate_password_strength(cls, v: str) -> str: if not re.search(r'[A-Z]', v): raise ValueError('Password must contain at least one uppercase letter') if not re.search(r'[a-z]', v): raise ValueError('Password must contain at least one lowercase letter') if not re.search(r'\d', v): raise ValueError('Password must contain at least one digit') return v class UserUpdate(BaseModel): username: Optional[str] = Field(None, min_length=3, max_length=50) email: Optional[EmailStr] = None password: Optional[str] = Field(None, min_length=8, max_length=128) is_active: Optional[bool] = None is_superuser: Optional[bool] = None @field_validator('username') @classmethod def validate_username(cls, v: Optional[str]) -> Optional[str]: if v is not None and not re.match(r'^[a-zA-Z0-9_-]+$', v): raise ValueError('Username must contain only alphanumeric characters, underscores, and dashes') return v @field_validator('password') @classmethod def validate_password_strength(cls, v: Optional[str]) -> Optional[str]: if v is not None: if not re.search(r'[A-Z]', v): raise ValueError('Password must contain at least one uppercase letter') if not re.search(r'[a-z]', v): raise ValueError('Password must contain at least one lowercase letter') if not re.search(r'\d', v): raise ValueError('Password must contain at least one digit') return v class UserListResponse(BaseModel): users: list[User] total: int skip: int limit: int class Token(BaseModel): access_token: str token_type: str class TokenData(BaseModel): username: Optional[str] = None class PasswordChange(BaseModel): current_password: str = Field(..., min_length=1) new_password: str = Field(..., min_length=8, max_length=128) confirm_password: str = Field(..., min_length=8, max_length=128) @field_validator('new_password') @classmethod def validate_password_strength(cls, v: str) -> str: if not re.search(r'[A-Z]', v): raise ValueError('Password must contain at least one uppercase letter') if not re.search(r'[a-z]', v): raise ValueError('Password must contain at least one lowercase letter') if not re.search(r'\d', v): raise ValueError('Password must contain at least one digit') return v @model_validator(mode='after') def passwords_match(self) -> 'PasswordChange': if self.new_password != self.confirm_password: raise ValueError('Passwords do not match') return self class AliyunKeyUpdate(BaseModel): api_key: str = Field(..., min_length=1, max_length=500) class AliyunKeyVerifyResponse(BaseModel): valid: bool message: str class UserPreferences(BaseModel): default_backend: str = Field(default="aliyun", pattern="^(local|aliyun)$") onboarding_completed: bool = Field(default=False) class UserPreferencesResponse(BaseModel): default_backend: str = Field(default="aliyun", pattern="^(local|aliyun)$") onboarding_completed: bool = Field(default=False) available_backends: list[str] = Field(default=["aliyun"]) class SystemSettingsUpdate(BaseModel): local_model_enabled: bool class SystemSettingsResponse(BaseModel): local_model_enabled: bool