from __future__ import annotations from datetime import datetime from typing import Annotated, Literal from pydantic import BaseModel, ConfigDict, StrictStr, StringConstraints, field_validator from app.models.user import RoleEnum UsernameField = Annotated[ StrictStr, StringConstraints( strip_whitespace=True, min_length=3, max_length=50, ), ] PasswordField = Annotated[ StrictStr, StringConstraints( min_length=8, max_length=72, ), ] StrongPasswordField = Annotated[ StrictStr, StringConstraints( min_length=12, max_length=72, ), ] class UserCreate(BaseModel): model_config = ConfigDict(extra="forbid") username: UsernameField password: StrongPasswordField @field_validator("username") @classmethod def normalize_username(cls, value: str) -> str: return value.lower() @field_validator("password") @classmethod def validate_password_strength(cls, value: str) -> str: has_upper = any(char.isupper() for char in value) has_lower = any(char.islower() for char in value) has_digit = any(char.isdigit() for char in value) has_special = any(not char.isalnum() for char in value) if not all((has_upper, has_lower, has_digit, has_special)): raise ValueError( "Password must include upper, lower, digit, and special characters" ) return value class LoginRequest(BaseModel): model_config = ConfigDict(extra="forbid") username: UsernameField password: PasswordField @field_validator("username") @classmethod def normalize_username(cls, value: str) -> str: return value.lower() class UserResponse(BaseModel): model_config = ConfigDict(from_attributes=True, extra="forbid") id: int username: str role: RoleEnum is_active: bool created_at: datetime class TokenResponse(BaseModel): model_config = ConfigDict(extra="forbid") access_token: str token_type: Literal["bearer"] = "bearer"