# auth.py - Keep security-critical validation import re from datetime import datetime from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator class UserLogin(BaseModel): email_or_username: str = Field(..., description="Username or email address") password: str = Field(..., description="Password") vendor_code: str | None = Field( None, description="Optional vendor code for context" ) @field_validator("email_or_username") @classmethod def validate_email_or_username(cls, v): return v.strip() class UserResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: int email: str username: str role: str is_active: bool preferred_language: str | None = None last_login: datetime | None = None created_at: datetime updated_at: datetime class LoginResponse(BaseModel): access_token: str token_type: str = "bearer" expires_in: int user: UserResponse class UserDetailResponse(UserResponse): """Extended user response with additional details.""" first_name: str | None = None last_name: str | None = None full_name: str | None = None is_email_verified: bool = False owned_companies_count: int = 0 vendor_memberships_count: int = 0 class UserUpdate(BaseModel): """Schema for updating user information.""" username: str | None = Field(None, min_length=3, max_length=50) email: EmailStr | None = None first_name: str | None = Field(None, max_length=100) last_name: str | None = Field(None, max_length=100) role: str | None = Field(None, pattern="^(admin|vendor)$") is_active: bool | None = None is_email_verified: bool | None = None preferred_language: str | None = Field( None, description="Preferred language (en, fr, de, lb)" ) @field_validator("username") @classmethod def validate_username(cls, v): if v and not re.match(r"^[a-zA-Z0-9_]+$", v): raise ValueError( "Username must contain only letters, numbers, or underscores" ) return v.lower().strip() if v else v class UserCreate(BaseModel): """Schema for creating a new user (admin only).""" email: EmailStr = Field(..., description="Valid email address") username: str = Field(..., min_length=3, max_length=50) password: str = Field(..., min_length=6, description="Password") first_name: str | None = Field(None, max_length=100) last_name: str | None = Field(None, max_length=100) role: str = Field(default="vendor", pattern="^(admin|vendor)$") preferred_language: str | None = Field( None, description="Preferred language (en, fr, de, lb)" ) @field_validator("username") @classmethod def validate_username(cls, v): if not re.match(r"^[a-zA-Z0-9_]+$", v): raise ValueError( "Username must contain only letters, numbers, or underscores" ) return v.lower().strip() class UserListResponse(BaseModel): """Schema for paginated user list.""" items: list[UserResponse] total: int page: int per_page: int pages: int class UserSearchItem(BaseModel): """Schema for a single user search result.""" id: int username: str email: str is_active: bool class UserSearchResponse(BaseModel): """Schema for user search results.""" users: list[UserSearchItem] class UserStatusToggleResponse(BaseModel): """Schema for user status toggle response.""" message: str is_active: bool class UserDeleteResponse(BaseModel): """Schema for user delete response.""" message: str class LogoutResponse(BaseModel): """Schema for logout response.""" message: str class PasswordResetRequestResponse(BaseModel): """Schema for password reset request response.""" message: str class PasswordResetResponse(BaseModel): """Schema for password reset response.""" message: str class VendorUserResponse(BaseModel): """Schema for vendor user info in auth context.""" id: int username: str email: str role: str is_active: bool model_config = {"from_attributes": True}