from __future__ import annotations import re from datetime import datetime from typing import Any, Optional from pydantic import BaseModel, EmailStr, Field, field_validator def _validate_password(v: str) -> str: if len(v) < 8: raise ValueError("Password must be at least 8 characters") if len(v) > 128: raise ValueError("Password must not exceed 128 characters") 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 RegisterSchema(BaseModel): email: EmailStr password: str username: Optional[str] = Field(None, min_length=2, max_length=50, pattern=r"^[a-zA-Z0-9_]+$") full_name: Optional[str] = Field(None, min_length=1, max_length=100) _validate_password = field_validator("password")(_validate_password) class LoginSchema(BaseModel): email: EmailStr password: str class TokenData(BaseModel): access_token: str refresh_token: str token_type: str = "bearer" class TokenRefreshSchema(BaseModel): refresh_token: str class UserProfile(BaseModel): id: str email: str username: Optional[str] = None full_name: Optional[str] = None is_active: bool is_verified: bool roles: list[str] created_at: datetime @field_validator("roles", mode="before") @classmethod def _extract_role_names(cls, v: Any) -> list[str]: if not v: return [] if isinstance(v[0], str): return v return [r.name for r in v] model_config = {"from_attributes": True} class UpdateProfileSchema(BaseModel): username: Optional[str] = Field(None, min_length=2, max_length=50, pattern=r"^[a-zA-Z0-9_]+$") full_name: Optional[str] = Field(None, min_length=1, max_length=100) class ChangePasswordSchema(BaseModel): current_password: str new_password: str _validate_new_password = field_validator("new_password")(_validate_password) class ForgotPasswordSchema(BaseModel): email: EmailStr class ResetPasswordSchema(BaseModel): token: str new_password: str _validate_new_password = field_validator("new_password")(_validate_password) class SessionOut(BaseModel): id: str device_info: Optional[str] = None ip_address: Optional[str] = None created_at: datetime expires_at: datetime model_config = {"from_attributes": True} class MessageResponse(BaseModel): message: str class UserSchemaField(BaseModel): field: str type: str required: bool description: str constraints: Optional[str] = None class UserSchemaResponse(BaseModel): table_name: str = "users" columns: list[UserSchemaField] # --- Wrapped API response schemas --- class ApiResponse(BaseModel): success: bool = True class DataResponse(ApiResponse): data: dict application_id: Optional[str] = None warning: Optional[dict] = None class TokenResponse(ApiResponse): data: TokenData application_id: Optional[str] = None warning: Optional[dict] = None class ProfileResponse(ApiResponse): data: UserProfile application_id: Optional[str] = None warning: Optional[dict] = None class MessageDataResponse(ApiResponse): data: MessageResponse application_id: Optional[str] = None warning: Optional[dict] = None class SchemaResponse(ApiResponse): data: UserSchemaResponse application_id: Optional[str] = None warning: Optional[dict] = None class SessionListResponse(ApiResponse): data: list[SessionOut] application_id: Optional[str] = None warning: Optional[dict] = None