Files
enclava/backend/app/schemas/user.py
2025-11-20 11:11:18 +01:00

261 lines
6.7 KiB
Python

"""
User Management Schemas
Pydantic models for user management API
"""
from typing import Optional, List, Dict, Any
from datetime import datetime
from pydantic import BaseModel, EmailStr, validator
class UserBase(BaseModel):
"""Base user schema"""
email: EmailStr
username: str
full_name: Optional[str] = None
is_active: bool = True
is_verified: bool = False
@validator("username")
def validate_username(cls, v):
if len(v) < 3:
raise ValueError("Username must be at least 3 characters long")
if len(v) > 50:
raise ValueError("Username must be less than 50 characters long")
return v
@validator("email")
def validate_email(cls, v):
if len(v) > 255:
raise ValueError("Email must be less than 255 characters long")
return v
class UserCreate(UserBase):
"""Schema for creating a user"""
password: str
role_id: Optional[int] = None
custom_permissions: Dict[str, Any] = {}
@validator("password")
def validate_password(cls, v):
if len(v) < 8:
raise ValueError("Password must be at least 8 characters long")
return v
class UserUpdate(BaseModel):
"""Schema for updating a user"""
email: Optional[EmailStr] = None
username: Optional[str] = None
full_name: Optional[str] = None
role_id: Optional[int] = None
custom_permissions: Optional[Dict[str, Any]] = None
is_active: Optional[bool] = None
is_verified: Optional[bool] = None
@validator("username")
def validate_username(cls, v):
if v is not None:
if len(v) < 3:
raise ValueError("Username must be at least 3 characters long")
if len(v) > 50:
raise ValueError("Username must be less than 50 characters long")
return v
class PasswordChange(BaseModel):
"""Schema for changing password"""
current_password: Optional[str] = None
new_password: str
confirm_password: str
@validator("new_password")
def validate_new_password(cls, v):
if len(v) < 8:
raise ValueError("Password must be at least 8 characters long")
return v
@validator("confirm_password")
def passwords_match(cls, v, values):
if "new_password" in values and v != values["new_password"]:
raise ValueError("Passwords do not match")
return v
class RoleInfo(BaseModel):
"""Role information schema"""
id: int
name: str
display_name: str
level: str
permissions: Dict[str, Any]
class Config:
from_attributes = True
class UserResponse(BaseModel):
"""User response schema"""
id: int
email: str
username: str
full_name: Optional[str]
is_active: bool
is_verified: bool
is_superuser: bool
role_id: Optional[int]
role: Optional[RoleInfo]
custom_permissions: Dict[str, Any]
account_locked: Optional[bool] = False
account_locked_until: Optional[datetime]
failed_login_attempts: Optional[int] = 0
last_failed_login: Optional[datetime]
avatar_url: Optional[str]
bio: Optional[str]
company: Optional[str]
website: Optional[str]
created_at: Optional[datetime]
updated_at: Optional[datetime]
last_login: Optional[datetime]
preferences: Dict[str, Any]
notification_settings: Dict[str, Any]
class Config:
from_attributes = True
@classmethod
def from_orm(cls, obj):
"""Create response from ORM object with proper role handling"""
data = obj.to_dict()
if obj.role:
data["role"] = RoleInfo.from_orm(obj.role)
return cls(**data)
class UserListResponse(BaseModel):
"""User list response schema"""
users: List[UserResponse]
total: int
skip: int
limit: int
class AccountLockResponse(BaseModel):
"""Account lock response schema"""
user_id: int
is_locked: bool
locked_until: Optional[datetime]
message: str
class UserProfileUpdate(BaseModel):
"""Schema for user profile updates (limited fields)"""
full_name: Optional[str] = None
avatar_url: Optional[str] = None
bio: Optional[str] = None
company: Optional[str] = None
website: Optional[str] = None
preferences: Optional[Dict[str, Any]] = None
notification_settings: Optional[Dict[str, Any]] = None
class UserPreferences(BaseModel):
"""User preferences schema"""
theme: Optional[str] = "light"
language: Optional[str] = "en"
timezone: Optional[str] = "UTC"
email_notifications: Optional[bool] = True
security_alerts: Optional[bool] = True
system_updates: Optional[bool] = True
class UserSearchFilter(BaseModel):
"""User search filter schema"""
search: Optional[str] = None
role_id: Optional[int] = None
is_active: Optional[bool] = None
is_verified: Optional[bool] = None
created_after: Optional[datetime] = None
created_before: Optional[datetime] = None
last_login_after: Optional[datetime] = None
last_login_before: Optional[datetime] = None
class UserBulkAction(BaseModel):
"""Schema for bulk user actions"""
user_ids: List[int]
action: str # activate, deactivate, lock, unlock, assign_role, remove_role
action_data: Optional[Dict[str, Any]] = None
@validator("action")
def validate_action(cls, v):
valid_actions = [
"activate",
"deactivate",
"lock",
"unlock",
"assign_role",
"remove_role",
]
if v not in valid_actions:
raise ValueError(f'Action must be one of: {", ".join(valid_actions)}')
return v
@validator("user_ids")
def validate_user_ids(cls, v):
if not v:
raise ValueError("At least one user ID must be provided")
if len(v) > 100:
raise ValueError(
"Cannot perform bulk action on more than 100 users at once"
)
return v
class UserStatistics(BaseModel):
"""User statistics schema"""
total_users: int
active_users: int
verified_users: int
locked_users: int
users_by_role: Dict[str, int]
recent_registrations: int
registrations_by_month: Dict[str, int]
class UserActivity(BaseModel):
"""User activity schema"""
user_id: int
action: str
resource_type: Optional[str] = None
resource_id: Optional[int] = None
details: Optional[Dict[str, Any]] = None
timestamp: datetime
ip_address: Optional[str] = None
user_agent: Optional[str] = None
class UserActivityFilter(BaseModel):
"""User activity filter schema"""
user_id: Optional[int] = None
action: Optional[str] = None
resource_type: Optional[str] = None
date_from: Optional[datetime] = None
date_to: Optional[datetime] = None
ip_address: Optional[str] = None