feat: Add pagination, search, and filter functionality to user retrieval endpoint

This commit is contained in:
JSC
2025-08-17 11:44:15 +02:00
parent 99c757a073
commit e6f796a3c9
2 changed files with 130 additions and 9 deletions

View File

@@ -1,7 +1,9 @@
"""User repository."""
from typing import Any
from enum import Enum
from sqlalchemy import func
from sqlalchemy.orm import selectinload
from sqlmodel import select
from sqlmodel.ext.asyncio.session import AsyncSession
@@ -14,6 +16,28 @@ from app.repositories.base import BaseRepository
logger = get_logger(__name__)
class UserSortField(str, Enum):
"""User sort fields."""
NAME = "name"
EMAIL = "email"
ROLE = "role"
CREDITS = "credits"
CREATED_AT = "created_at"
class SortOrder(str, Enum):
"""Sort order."""
ASC = "asc"
DESC = "desc"
class UserStatus(str, Enum):
"""User status filter."""
ALL = "all"
ACTIVE = "active"
INACTIVE = "inactive"
class UserRepository(BaseRepository[User]):
"""Repository for user operations."""
@@ -40,6 +64,83 @@ class UserRepository(BaseRepository[User]):
logger.exception("Failed to get all users with plan")
raise
async def get_all_with_plan_paginated(
self,
page: int = 1,
limit: int = 50,
search: str | None = None,
sort_by: UserSortField = UserSortField.NAME,
sort_order: SortOrder = SortOrder.ASC,
status_filter: UserStatus = UserStatus.ALL,
) -> tuple[list[User], int]:
"""Get all users with plan relationship loaded and return total count."""
try:
# Calculate offset
offset = (page - 1) * limit
# Build base query
base_query = select(User).options(selectinload(User.plan))
count_query = select(func.count(User.id))
# Apply search filter
if search and search.strip():
search_pattern = f"%{search.strip().lower()}%"
search_condition = (
func.lower(User.name).like(search_pattern) |
func.lower(User.email).like(search_pattern)
)
base_query = base_query.where(search_condition)
count_query = count_query.where(search_condition)
# Apply status filter
if status_filter == UserStatus.ACTIVE:
base_query = base_query.where(User.is_active == True) # noqa: E712
count_query = count_query.where(User.is_active == True) # noqa: E712
elif status_filter == UserStatus.INACTIVE:
base_query = base_query.where(User.is_active == False) # noqa: E712
count_query = count_query.where(User.is_active == False) # noqa: E712
# Apply sorting
if sort_by == UserSortField.EMAIL:
if sort_order == SortOrder.DESC:
base_query = base_query.order_by(User.email.desc())
else:
base_query = base_query.order_by(User.email.asc())
elif sort_by == UserSortField.ROLE:
if sort_order == SortOrder.DESC:
base_query = base_query.order_by(User.role.desc())
else:
base_query = base_query.order_by(User.role.asc())
elif sort_by == UserSortField.CREDITS:
if sort_order == SortOrder.DESC:
base_query = base_query.order_by(User.credits.desc())
else:
base_query = base_query.order_by(User.credits.asc())
elif sort_by == UserSortField.CREATED_AT:
if sort_order == SortOrder.DESC:
base_query = base_query.order_by(User.created_at.desc())
else:
base_query = base_query.order_by(User.created_at.asc())
else: # Default to name
if sort_order == SortOrder.DESC:
base_query = base_query.order_by(User.name.desc())
else:
base_query = base_query.order_by(User.name.asc())
# Get total count
count_result = await self.session.exec(count_query)
total_count = count_result.one()
# Apply pagination and get results
paginated_query = base_query.limit(limit).offset(offset)
result = await self.session.exec(paginated_query)
users = list(result.all())
return users, total_count
except Exception:
logger.exception("Failed to get paginated users with plan")
raise
async def get_by_id_with_plan(self, entity_id: int) -> User | None:
"""Get a user by ID with plan relationship loaded."""
try: