""" Admin API schemas — Pydantic v2. All admin-facing request/response shapes live here to keep the admin router clean and testable in isolation. """ import re from datetime import datetime from typing import Optional, Literal from pydantic import BaseModel, ConfigDict, field_validator from app.schemas.auth import _validate_username, _validate_password_strength # --------------------------------------------------------------------------- # User list / detail # --------------------------------------------------------------------------- class UserListItem(BaseModel): id: int username: str role: str is_active: bool last_login_at: Optional[datetime] = None last_password_change_at: Optional[datetime] = None totp_enabled: bool mfa_enforce_pending: bool created_at: datetime model_config = ConfigDict(from_attributes=True) class UserListResponse(BaseModel): users: list[UserListItem] total: int class UserDetailResponse(UserListItem): active_sessions: int # --------------------------------------------------------------------------- # Mutating user requests # --------------------------------------------------------------------------- class CreateUserRequest(BaseModel): """Admin-created user — allows role selection (unlike public RegisterRequest).""" model_config = ConfigDict(extra="forbid") username: str password: str role: Literal["admin", "standard", "public_event_manager"] = "standard" @field_validator("username") @classmethod def validate_username(cls, v: str) -> str: return _validate_username(v) @field_validator("password") @classmethod def validate_password(cls, v: str) -> str: return _validate_password_strength(v) class UpdateUserRoleRequest(BaseModel): model_config = ConfigDict(extra="forbid") role: Literal["admin", "standard", "public_event_manager"] class ToggleActiveRequest(BaseModel): model_config = ConfigDict(extra="forbid") is_active: bool class ToggleMfaEnforceRequest(BaseModel): model_config = ConfigDict(extra="forbid") enforce: bool # --------------------------------------------------------------------------- # System config # --------------------------------------------------------------------------- class SystemConfigResponse(BaseModel): allow_registration: bool enforce_mfa_new_users: bool model_config = ConfigDict(from_attributes=True) class SystemConfigUpdate(BaseModel): model_config = ConfigDict(extra="forbid") allow_registration: Optional[bool] = None enforce_mfa_new_users: Optional[bool] = None # --------------------------------------------------------------------------- # Admin dashboard # --------------------------------------------------------------------------- class RecentLoginItem(BaseModel): username: str last_login_at: Optional[datetime] = None model_config = ConfigDict(from_attributes=True) class RecentAuditItem(BaseModel): action: str actor_username: Optional[str] = None target_username: Optional[str] = None created_at: datetime model_config = ConfigDict(from_attributes=True) class AdminDashboardResponse(BaseModel): total_users: int active_users: int admin_count: int active_sessions: int mfa_adoption_rate: float recent_logins: list[RecentLoginItem] recent_audit_entries: list[RecentAuditItem] # --------------------------------------------------------------------------- # Password reset # --------------------------------------------------------------------------- class ResetPasswordResponse(BaseModel): message: str temporary_password: str # --------------------------------------------------------------------------- # Audit log # --------------------------------------------------------------------------- class AuditLogEntry(BaseModel): id: int actor_username: Optional[str] = None target_username: Optional[str] = None action: str detail: Optional[str] = None ip_address: Optional[str] = None created_at: datetime class AuditLogResponse(BaseModel): entries: list[AuditLogEntry] total: int