Added frontend. Working on user management
This commit is contained in:
179
src/file-processor/app/models/user.py
Normal file
179
src/file-processor/app/models/user.py
Normal file
@@ -0,0 +1,179 @@
|
||||
"""
|
||||
User models and validation for the MyDocManager application.
|
||||
|
||||
Contains Pydantic models for user creation, updates, database representation,
|
||||
and API responses with proper validation and type safety.
|
||||
"""
|
||||
|
||||
import re
|
||||
from datetime import datetime
|
||||
from typing import Optional, Any
|
||||
from bson import ObjectId
|
||||
from pydantic import BaseModel, Field, field_validator, EmailStr
|
||||
from pydantic_core import core_schema
|
||||
|
||||
from app.models.auth import UserRole
|
||||
|
||||
|
||||
class PyObjectId(ObjectId):
|
||||
"""Custom ObjectId type for Pydantic v2 compatibility."""
|
||||
|
||||
@classmethod
|
||||
def __get_pydantic_core_schema__(
|
||||
cls, source_type: Any, handler
|
||||
) -> core_schema.CoreSchema:
|
||||
return core_schema.json_or_python_schema(
|
||||
json_schema=core_schema.str_schema(),
|
||||
python_schema=core_schema.union_schema([
|
||||
core_schema.is_instance_schema(ObjectId),
|
||||
core_schema.chain_schema([
|
||||
core_schema.str_schema(),
|
||||
core_schema.no_info_plain_validator_function(cls.validate),
|
||||
])
|
||||
]),
|
||||
serialization=core_schema.plain_serializer_function_ser_schema(
|
||||
lambda x: str(x)
|
||||
),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def validate(cls, v):
|
||||
if not ObjectId.is_valid(v):
|
||||
raise ValueError("Invalid ObjectId")
|
||||
return ObjectId(v)
|
||||
|
||||
|
||||
def validate_password_strength(password: str) -> str:
|
||||
"""
|
||||
Validate password meets security requirements.
|
||||
|
||||
Requirements:
|
||||
- At least 8 characters long
|
||||
- Contains at least one uppercase letter
|
||||
- Contains at least one lowercase letter
|
||||
- Contains at least one digit
|
||||
- Contains at least one special character
|
||||
|
||||
Args:
|
||||
password: The password string to validate
|
||||
|
||||
Returns:
|
||||
str: The validated password
|
||||
|
||||
Raises:
|
||||
ValueError: If password doesn't meet requirements
|
||||
"""
|
||||
if len(password) < 8:
|
||||
raise ValueError("Password must be at least 8 characters long")
|
||||
|
||||
if not re.search(r'[A-Z]', password):
|
||||
raise ValueError("Password must contain at least one uppercase letter")
|
||||
|
||||
if not re.search(r'[a-z]', password):
|
||||
raise ValueError("Password must contain at least one lowercase letter")
|
||||
|
||||
if not re.search(r'\d', password):
|
||||
raise ValueError("Password must contain at least one digit")
|
||||
|
||||
if not re.search(r'[!@#$%^&*()_+\-=\[\]{};:"\\|,.<>\/?]', password):
|
||||
raise ValueError("Password must contain at least one special character")
|
||||
|
||||
return password
|
||||
|
||||
|
||||
def validate_username_not_empty(username: str) -> str:
|
||||
"""
|
||||
Validate username is not empty or whitespace only.
|
||||
|
||||
Args:
|
||||
username: The username string to validate
|
||||
|
||||
Returns:
|
||||
str: The validated username
|
||||
|
||||
Raises:
|
||||
ValueError: If username is empty or whitespace only
|
||||
"""
|
||||
if not username or not username.strip():
|
||||
raise ValueError("Username cannot be empty or whitespace only")
|
||||
|
||||
return username.strip()
|
||||
|
||||
|
||||
class UserCreate(BaseModel):
|
||||
"""Model for creating a new user."""
|
||||
|
||||
username: str
|
||||
email: EmailStr
|
||||
password: str
|
||||
role: UserRole = UserRole.USER
|
||||
|
||||
@field_validator('username')
|
||||
@classmethod
|
||||
def validate_username(cls, v):
|
||||
return validate_username_not_empty(v)
|
||||
|
||||
@field_validator('password')
|
||||
@classmethod
|
||||
def validate_password(cls, v):
|
||||
return validate_password_strength(v)
|
||||
|
||||
|
||||
class UserUpdate(BaseModel):
|
||||
"""Model for updating an existing user."""
|
||||
|
||||
username: Optional[str] = None
|
||||
email: Optional[EmailStr] = None
|
||||
password: Optional[str] = None
|
||||
role: Optional[UserRole] = None
|
||||
|
||||
@field_validator('username')
|
||||
@classmethod
|
||||
def validate_username(cls, v):
|
||||
if v is not None:
|
||||
return validate_username_not_empty(v)
|
||||
return v
|
||||
|
||||
@field_validator('password')
|
||||
@classmethod
|
||||
def validate_password(cls, v):
|
||||
if v is not None:
|
||||
return validate_password_strength(v)
|
||||
return v
|
||||
|
||||
|
||||
class UserInDB(BaseModel):
|
||||
"""Model for user data stored in database."""
|
||||
|
||||
id: PyObjectId = Field(default_factory=PyObjectId, alias="_id")
|
||||
username: str
|
||||
email: str
|
||||
password_hash: str
|
||||
role: UserRole
|
||||
is_active: bool = True
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
model_config = {
|
||||
"populate_by_name": True,
|
||||
"arbitrary_types_allowed": True,
|
||||
"json_encoders": {ObjectId: str}
|
||||
}
|
||||
|
||||
|
||||
class UserResponse(BaseModel):
|
||||
"""Model for user data in API responses (excludes password_hash)."""
|
||||
|
||||
id: PyObjectId = Field(alias="_id")
|
||||
username: str
|
||||
email: str
|
||||
role: UserRole
|
||||
is_active: bool
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
model_config = {
|
||||
"populate_by_name": True,
|
||||
"arbitrary_types_allowed": True,
|
||||
"json_encoders": {ObjectId: str}
|
||||
}
|
||||
Reference in New Issue
Block a user