Enhance user management, improve API structure, add database optimizations, and update Docker setup

- Introduced endpoints for user management, including CRUD operations, pagination, and password management.
- Added new schema validations for user updates, password strength, pagination, and standardized error responses.
- Integrated custom exception handling for a consistent API error experience.
- Refined CORS settings: restricted methods and allowed headers, added header exposure, and preflight caching.
- Optimized database: added indexes on `is_active` and `is_superuser` fields, updated column types, enforced constraints, and set defaults.
- Updated `Dockerfile` to improve security by using a non-root user and adding a health check for the application.
- Enhanced tests for database initialization, user operations, and exception handling to ensure better coverage.
This commit is contained in:
Felipe Cardoso
2025-10-30 15:43:52 +01:00
parent d83959963b
commit 2c600290a1
16 changed files with 1511 additions and 100 deletions

View File

@@ -0,0 +1,139 @@
"""
Common schemas used across the API for pagination, responses, etc.
"""
from typing import Generic, TypeVar, List, Optional
from pydantic import BaseModel, Field
from math import ceil
T = TypeVar('T')
class PaginationParams(BaseModel):
"""Parameters for pagination."""
page: int = Field(
default=1,
ge=1,
description="Page number (1-indexed)"
)
limit: int = Field(
default=20,
ge=1,
le=100,
description="Number of items per page (max 100)"
)
@property
def offset(self) -> int:
"""Calculate the offset for database queries."""
return (self.page - 1) * self.limit
@property
def skip(self) -> int:
"""Alias for offset (compatibility with existing code)."""
return self.offset
model_config = {
"json_schema_extra": {
"example": {
"page": 1,
"limit": 20
}
}
}
class PaginationMeta(BaseModel):
"""Metadata for paginated responses."""
total: int = Field(..., description="Total number of items")
page: int = Field(..., description="Current page number")
page_size: int = Field(..., description="Number of items in current page")
total_pages: int = Field(..., description="Total number of pages")
has_next: bool = Field(..., description="Whether there is a next page")
has_prev: bool = Field(..., description="Whether there is a previous page")
model_config = {
"json_schema_extra": {
"example": {
"total": 150,
"page": 1,
"page_size": 20,
"total_pages": 8,
"has_next": True,
"has_prev": False
}
}
}
class PaginatedResponse(BaseModel, Generic[T]):
"""Generic paginated response wrapper."""
data: List[T] = Field(..., description="List of items")
pagination: PaginationMeta = Field(..., description="Pagination metadata")
model_config = {
"json_schema_extra": {
"example": {
"data": [
{"id": "123", "name": "Example Item"}
],
"pagination": {
"total": 150,
"page": 1,
"page_size": 20,
"total_pages": 8,
"has_next": True,
"has_prev": False
}
}
}
}
class MessageResponse(BaseModel):
"""Simple message response."""
success: bool = Field(default=True, description="Operation success status")
message: str = Field(..., description="Human-readable message")
model_config = {
"json_schema_extra": {
"example": {
"success": True,
"message": "Operation completed successfully"
}
}
}
def create_pagination_meta(
total: int,
page: int,
limit: int,
items_count: int
) -> PaginationMeta:
"""
Helper function to create pagination metadata.
Args:
total: Total number of items
page: Current page number
limit: Items per page
items_count: Number of items in current page
Returns:
PaginationMeta object with calculated values
"""
total_pages = ceil(total / limit) if limit > 0 else 0
return PaginationMeta(
total=total,
page=page,
page_size=items_count,
total_pages=total_pages,
has_next=page < total_pages,
has_prev=page > 1
)