implement pagination on all endpoints

This commit is contained in:
johnpccd 2025-05-25 22:20:20 +02:00
parent fe082026c1
commit 80d8a74b12
5 changed files with 76 additions and 25 deletions

View File

@ -234,7 +234,11 @@ The API provides the following main endpoints with their authentication and pagi
#### Team Management #### Team Management
- `/api/v1/teams/*` - **Complete team management (no authentication required)** - `/api/v1/teams/*` - **Complete team management (no authentication required)**
- `POST /api/v1/teams` - Create new team - `POST /api/v1/teams` - Create new team
- `GET /api/v1/teams` - List all teams (no pagination - returns all teams) - `GET /api/v1/teams` - List all teams with **pagination support**
- **Query Parameters:**
- `skip` (default: 0, min: 0) - Number of items to skip
- `limit` (default: 50, min: 1, max: 100) - Number of items per page
- **Response includes:** `teams`, `total`, `skip`, `limit`
- `GET /api/v1/teams/{team_id}` - Get team by ID - `GET /api/v1/teams/{team_id}` - Get team by ID
- `PUT /api/v1/teams/{team_id}` - Update team - `PUT /api/v1/teams/{team_id}` - Update team
- `DELETE /api/v1/teams/{team_id}` - Delete team - `DELETE /api/v1/teams/{team_id}` - Delete team
@ -242,7 +246,12 @@ The API provides the following main endpoints with their authentication and pagi
#### User Management #### User Management
- `/api/v1/users/*` - **Complete user management (no authentication required)** - `/api/v1/users/*` - **Complete user management (no authentication required)**
- `POST /api/v1/users` - Create new user (requires `team_id`) - `POST /api/v1/users` - Create new user (requires `team_id`)
- `GET /api/v1/users` - List users (no pagination - returns all users, optionally filtered by team) - `GET /api/v1/users` - List users with **pagination support**
- **Query Parameters:**
- `skip` (default: 0, min: 0) - Number of items to skip
- `limit` (default: 50, min: 1, max: 100) - Number of items per page
- `team_id` (optional) - Filter by team
- **Response includes:** `users`, `total`, `skip`, `limit`
- `GET /api/v1/users/{user_id}` - Get user by ID - `GET /api/v1/users/{user_id}` - Get user by ID
- `PUT /api/v1/users/{user_id}` - Update user - `PUT /api/v1/users/{user_id}` - Update user
- `DELETE /api/v1/users/{user_id}` - Delete user - `DELETE /api/v1/users/{user_id}` - Delete user
@ -252,7 +261,11 @@ The API provides the following main endpoints with their authentication and pagi
### 🔐 **Protected Endpoints (API Key Authentication Required)** ### 🔐 **Protected Endpoints (API Key Authentication Required)**
#### API Key Management (Authenticated) #### API Key Management (Authenticated)
- `/api/v1/auth/api-keys` (GET) - List API keys for current user - `/api/v1/auth/api-keys` (GET) - List API keys for current user with **pagination support**
- **Query Parameters:**
- `skip` (default: 0, min: 0) - Number of items to skip
- `limit` (default: 50, min: 1, max: 100) - Number of items per page
- **Response includes:** `api_keys`, `total`, `skip`, `limit`
- `/api/v1/auth/api-keys/{key_id}` (DELETE) - Revoke API key - `/api/v1/auth/api-keys/{key_id}` (DELETE) - Revoke API key
- `/api/v1/auth/admin/api-keys/{user_id}` (POST) - Create API key for another user (admin only) - `/api/v1/auth/admin/api-keys/{user_id}` (POST) - Create API key for another user (admin only)
- `/api/v1/auth/verify` - Verify current authentication - `/api/v1/auth/verify` - Verify current authentication
@ -271,10 +284,11 @@ The API provides the following main endpoints with their authentication and pagi
- `GET /api/v1/search` - Search images with **pagination support** - `GET /api/v1/search` - Search images with **pagination support**
- **Query Parameters:** - **Query Parameters:**
- `q` (required) - Search query - `q` (required) - Search query
- `skip` (default: 0, min: 0) - Number of items to skip
- `limit` (default: 10, min: 1, max: 50) - Number of results - `limit` (default: 10, min: 1, max: 50) - Number of results
- `similarity_threshold` (default: 0.7, min: 0.0, max: 1.0) - Similarity threshold - `similarity_threshold` (default: 0.7, min: 0.0, max: 1.0) - Similarity threshold
- `collection_id` (optional) - Filter by collection - `collection_id` (optional) - Filter by collection
- **Response includes:** `results`, `total`, `limit`, `similarity_threshold`, `query` - **Response includes:** `results`, `total`, `skip`, `limit`, `similarity_threshold`, `query`
- `POST /api/v1/search` - Advanced search with same pagination - `POST /api/v1/search` - Advanced search with same pagination
### 🔑 **Authentication Model** ### 🔑 **Authentication Model**
@ -289,14 +303,14 @@ A **hybrid authentication model**:
| Endpoint Category | Authentication | Pagination Status | Notes | | Endpoint Category | Authentication | Pagination Status | Notes |
|------------------|----------------|------------------|-------| |------------------|----------------|------------------|-------|
| **Users Management** | 🔓 **Public** | **Not Implemented** | Complete CRUD operations, no auth required | | **Users Management** | 🔓 **Public** | **Fully Implemented** | `skip`, `limit`, `total` with team filtering |
| **Teams Management** | 🔓 **Public** | **Not Implemented** | Complete CRUD operations, no auth required | | **Teams Management** | 🔓 **Public** | **Fully Implemented** | `skip`, `limit`, `total` with proper validation |
| **API Key Creation** | 🔓 **Public** | N/A | Requires `user_id` and `team_id` parameters | | **API Key Creation** | 🔓 **Public** | N/A | Requires `user_id` and `team_id` parameters |
| **Images API** | 🔐 **Protected** | ✅ **Fully Implemented** | `skip`, `limit`, `total` with proper validation | | **Images API** | 🔐 **Protected** | ✅ **Fully Implemented** | `skip`, `limit`, `total` with proper validation |
| **Search API** | 🔐 **Protected** | ✅ **Fully Implemented** | `limit`, `total` with similarity scoring | | **Search API** | 🔐 **Protected** | ✅ **Fully Implemented** | `skip`, `limit`, `total` with similarity scoring |
| **API Key Management** | 🔐 **Protected** | **Not Implemented** | List/revoke existing keys (small datasets) | | **API Key Management** | 🔐 **Protected** | **Fully Implemented** | `skip`, `limit`, `total` for user's API keys |
**Note:** Public endpoints (users, teams) don't implement pagination as they typically return small datasets and are designed for management use cases where full data visibility is preferred. **Note:** All endpoints now implement consistent pagination with `skip` and `limit` parameters for optimal performance and user experience.
Refer to the Swagger UI documentation at `/docs` for detailed endpoint information. Refer to the Swagger UI documentation at `/docs` for detailed endpoint information.
@ -437,7 +451,6 @@ This modular architecture provides several benefits:
### Medium Priority ### Medium Priority
- [ ] Implement caching layer for frequently accessed embeddings - [ ] Implement caching layer for frequently accessed embeddings
- [ ] Implement caching for frequently accessed data - [ ] Implement caching for frequently accessed data
- [ ] Consider adding pagination to admin endpoints (users, teams, API keys) if datasets grow large
### Low Priority ### Low Priority
- [ ] Move all auth logic to auth module - [ ] Move all auth logic to auth module

View File

@ -98,25 +98,43 @@ async def create_api_key_for_user(
async def list_api_keys( async def list_api_keys(
request: Request, request: Request,
auth_service: AuthServiceDep, auth_service: AuthServiceDep,
current_user: UserModel = Depends(get_current_user) current_user: UserModel = Depends(get_current_user),
skip: int = Query(0, ge=0, description="Number of records to skip for pagination"),
limit: int = Query(50, ge=1, le=100, description="Maximum number of records to return (1-100)")
): ):
""" """
List API keys for the current authenticated user List API keys for the current authenticated user
Returns all active and inactive API keys belonging to the authenticated user. Returns a paginated list of all active and inactive API keys belonging
to the authenticated user.
Args:
skip: Number of records to skip for pagination (default: 0)
limit: Maximum number of records to return, 1-100 (default: 50)
current_user: The authenticated user
auth_service: Injected auth service
Returns:
ApiKeyListResponse: Paginated list of API keys with total count
Raises:
400: Invalid pagination parameters
500: Internal server error
""" """
auth_context = create_auth_context( auth_context = create_auth_context(
user=current_user, user=current_user,
resource_type="api_key", resource_type="api_key",
action="list", action="list",
skip=skip,
limit=limit,
path=request.url.path, path=request.url.path,
method=request.method method=request.method
) )
log_authorization_context(auth_context, success=True) log_authorization_context(auth_context, success=True)
try: try:
response = await auth_service.list_user_api_keys(current_user) response = await auth_service.list_user_api_keys(current_user, skip, limit)
logger.info(f"Listed {response.total} API keys for user {current_user.id}") logger.info(f"Listed {len(response.api_keys)} API keys (total: {response.total}) for user {current_user.id}")
return response return response
except Exception as e: except Exception as e:
raise handle_service_error(e, "API key listing") raise handle_service_error(e, "API key listing")

View File

@ -24,6 +24,7 @@ async def search_images(
request: Request, request: Request,
search_service: SearchServiceDep, search_service: SearchServiceDep,
q: str = Query(..., description="Search query for semantic image search"), q: str = Query(..., description="Search query for semantic image search"),
skip: int = Query(0, ge=0, description="Number of records to skip for pagination"),
limit: int = Query(10, ge=1, le=50, description="Number of results to return (1-50)"), limit: int = Query(10, ge=1, le=50, description="Number of results to return (1-50)"),
similarity_threshold: float = Query(0.65, ge=0.0, le=1.0, description="Similarity threshold (0.0-1.0)"), similarity_threshold: float = Query(0.65, ge=0.0, le=1.0, description="Similarity threshold (0.0-1.0)"),
collection_id: Optional[str] = Query(None, description="Filter results by collection ID"), collection_id: Optional[str] = Query(None, description="Filter results by collection ID"),
@ -38,6 +39,7 @@ async def search_images(
Args: Args:
q: The search query text to find similar images q: The search query text to find similar images
skip: Number of records to skip for pagination (default: 0)
limit: Maximum number of results to return (1-50, default: 10) limit: Maximum number of results to return (1-50, default: 10)
similarity_threshold: Minimum similarity score (0.0-1.0, default: 0.65) similarity_threshold: Minimum similarity score (0.0-1.0, default: 0.65)
collection_id: Optional filter to search within a specific collection collection_id: Optional filter to search within a specific collection
@ -56,6 +58,7 @@ async def search_images(
resource_type="image", resource_type="image",
action="search", action="search",
query=q, query=q,
skip=skip,
limit=limit, limit=limit,
similarity_threshold=similarity_threshold, similarity_threshold=similarity_threshold,
collection_id=collection_id, collection_id=collection_id,
@ -70,6 +73,7 @@ async def search_images(
query=q, query=q,
user=current_user, user=current_user,
request=request, request=request,
skip=skip,
limit=limit, limit=limit,
similarity_threshold=similarity_threshold, similarity_threshold=similarity_threshold,
collection_id=collection_id collection_id=collection_id

View File

@ -1,5 +1,5 @@
import logging import logging
from fastapi import APIRouter, Depends, HTTPException, Request, status from fastapi import APIRouter, Depends, HTTPException, Request, status, Query
from bson import ObjectId from bson import ObjectId
from src.dependencies import TeamServiceDep from src.dependencies import TeamServiceDep
@ -55,32 +55,42 @@ async def create_team(
@router.get("", response_model=TeamListResponse) @router.get("", response_model=TeamListResponse)
async def list_teams( async def list_teams(
request: Request, request: Request,
team_service: TeamServiceDep team_service: TeamServiceDep,
skip: int = Query(0, ge=0, description="Number of records to skip for pagination"),
limit: int = Query(50, ge=1, le=100, description="Maximum number of records to return (1-100)")
): ):
""" """
List all teams List all teams
Retrieves a complete list of all teams in the system with their Retrieves a paginated list of all teams in the system with their
basic information and member counts. basic information and member counts.
Args: Args:
skip: Number of records to skip for pagination (default: 0)
limit: Maximum number of records to return, 1-100 (default: 50)
team_service: Injected team service team_service: Injected team service
Returns: Returns:
TeamListResponse: List of all teams with total count TeamListResponse: Paginated list of teams with total count
Raises:
400: Invalid pagination parameters
500: Internal server error
""" """
auth_context = create_auth_context( auth_context = create_auth_context(
user=None, # No authentication required for listing teams user=None, # No authentication required for listing teams
resource_type="team", resource_type="team",
action="list", action="list",
skip=skip,
limit=limit,
path=request.url.path, path=request.url.path,
method=request.method method=request.method
) )
log_authorization_context(auth_context, success=True) log_authorization_context(auth_context, success=True)
try: try:
response = await team_service.list_teams() response = await team_service.list_teams(skip, limit)
logger.info(f"Listed {response.total} teams") logger.info(f"Listed {len(response.teams)} teams (total: {response.total})")
return response return response
except Exception as e: except Exception as e:
raise handle_service_error(e, "team listing") raise handle_service_error(e, "team listing")

View File

@ -147,29 +147,35 @@ async def create_user(
async def list_users( async def list_users(
request: Request, request: Request,
user_service: UserServiceDep, user_service: UserServiceDep,
skip: int = Query(0, ge=0, description="Number of records to skip for pagination"),
limit: int = Query(50, ge=1, le=100, description="Maximum number of records to return (1-100)"),
team_id: Optional[str] = Query(None, description="Filter users by team ID") team_id: Optional[str] = Query(None, description="Filter users by team ID")
): ):
""" """
List users with optional team filtering List users with optional team filtering
Retrieves a list of all users in the system. Can be filtered by team Retrieves a paginated list of all users in the system. Can be filtered by team
to show only users belonging to a specific team. to show only users belonging to a specific team.
Args: Args:
skip: Number of records to skip for pagination (default: 0)
limit: Maximum number of records to return, 1-100 (default: 50)
team_id: Optional team ID to filter users by team_id: Optional team ID to filter users by
user_service: Injected user service user_service: Injected user service
Returns: Returns:
UserListResponse: List of users with total count UserListResponse: Paginated list of users with total count
Raises: Raises:
400: Invalid team ID format 400: Invalid pagination parameters or team ID format
500: Internal server error 500: Internal server error
""" """
auth_context = create_auth_context( auth_context = create_auth_context(
user=None, # No authentication required for listing users user=None, # No authentication required for listing users
resource_type="user", resource_type="user",
action="list", action="list",
skip=skip,
limit=limit,
team_id=team_id, team_id=team_id,
path=request.url.path, path=request.url.path,
method=request.method method=request.method
@ -177,8 +183,8 @@ async def list_users(
log_authorization_context(auth_context, success=True) log_authorization_context(auth_context, success=True)
try: try:
response = await user_service.list_users(team_id) response = await user_service.list_users(skip, limit, team_id)
logger.info(f"Listed {response.total} users" + (f" for team {team_id}" if team_id else "")) logger.info(f"Listed {len(response.users)} users (total: {response.total})" + (f" for team {team_id}" if team_id else ""))
return response return response
except Exception as e: except Exception as e:
raise handle_service_error(e, "user listing") raise handle_service_error(e, "user listing")