refactor: restructure API pagination metadata for better frontend usability

- Create PaginationMetadata model with computed derived fields (total_pages, has_next_page, has_prev_page)
- Update PaginatedListResponse to embed pagination metadata in a separate 'pagination' object
- Add create_pagination_metadata() factory function in utils/pagination.py for consistent computation
- Update all paginated service functions to use new structure:
  - history_service.list_history()
  - blocklist_service.get_import_logs()
  - jail_service.get_jail_banned_ips()
  - ban_mappers.map_domain_dashboard_ban_list_to_response()
- Update response model docstrings with new structure examples
- Update Backend-Development.md documentation with new pagination patterns
- Update test fixtures to work with new response structure

Response shape changes from:
  {"items": [...], "total": 100, "page": 1, "page_size": 50}
To:
  {"items": [...], "pagination": {"page": 1, "page_size": 50, "total": 100, "total_pages": 2, "has_next_page": true, "has_prev_page": false}}

Benefits:
- Frontend receives all pagination state needed for UI controls
- No need for frontend to calculate total_pages or page navigation logic
- Consolidated pagination metadata reduces field sprawl
- OpenAPI schema automatically reflects changes

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-04-30 22:24:42 +02:00
parent 05c3b564ae
commit 73021429f7
9 changed files with 156 additions and 96 deletions

View File

@@ -16,9 +16,14 @@ Response Patterns:
# Returns:
{
"items": [...],
"total": 100,
"page": 1,
"page_size": 20
"pagination": {
"page": 1,
"page_size": 20,
"total": 100,
"total_pages": 5,
"has_next_page": true,
"has_prev_page": false
}
}
```
@@ -116,6 +121,40 @@ class BanGuiBaseModel(BaseModel):
model_config = ConfigDict(strict=True)
class PaginationMetadata(BanGuiBaseModel):
"""Pagination metadata embedded in paginated list responses.
Contains page information and computed fields to support frontend pagination controls.
Fields:
page: Current page number (1-based).
page_size: Number of items per page.
total: Total number of items matching the query (across all pages).
total_pages: Computed total number of pages.
has_next_page: Whether there is a next page after this one.
has_prev_page: Whether there is a previous page before this one.
Example:
```python
pagination = PaginationMetadata(
page=2,
page_size=50,
total=150,
total_pages=3,
has_next_page=True,
has_prev_page=True
)
```
"""
page: int = Field(..., ge=1, description="Current page number (1-based).")
page_size: int = Field(..., ge=1, description="Number of items per page.")
total: int = Field(..., ge=0, description="Total number of items matching the query.")
total_pages: int = Field(..., ge=1, description="Computed total number of pages.")
has_next_page: bool = Field(..., description="Whether there is a next page after this one.")
has_prev_page: bool = Field(..., description="Whether there is a previous page before this one.")
class PaginatedListResponse(BanGuiBaseModel, Generic[T]):
"""Standardized paginated list response.
@@ -124,9 +163,7 @@ class PaginatedListResponse(BanGuiBaseModel, Generic[T]):
Fields:
items: The data items for the current page.
total: Total number of items matching the query (across all pages).
page: Current page number (1-based).
page_size: Number of items per page.
pagination: Pagination metadata with computed derived fields.
Example:
```python
@@ -136,17 +173,20 @@ class PaginatedListResponse(BanGuiBaseModel, Generic[T]):
# Returns:
{
"items": [...],
"total": 150,
"page": 2,
"page_size": 50
"pagination": {
"page": 2,
"page_size": 50,
"total": 150,
"total_pages": 3,
"has_next_page": true,
"has_prev_page": true
}
}
```
"""
items: list[T] = Field(default_factory=list, description="Data items for the current page.")
total: int = Field(..., ge=0, description="Total number of items matching the query.")
page: int = Field(..., ge=1, description="Current page number (1-based).")
page_size: int = Field(..., ge=1, description="Number of items per page.")
pagination: PaginationMetadata = Field(..., description="Pagination metadata with computed derived fields.")
class CollectionResponse(BanGuiBaseModel, Generic[T]):