implement pagination on all endpoints
This commit is contained in:
parent
fe082026c1
commit
80d8a74b12
33
README.md
33
README.md
@ -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
|
||||||
|
|||||||
@ -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")
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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")
|
||||||
|
|||||||
@ -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")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user