Merge branch 'feature/projects-api-pagination-search' into 'main'

Add pagination and search to projects API

See merge request esv/bsf/bsf-integration/orchard/orchard-mvp!1
This commit is contained in:
Mondo Diaz
2025-12-11 15:03:42 -06:00
3 changed files with 64 additions and 8 deletions

8
.gitignore vendored
View File

@@ -1,3 +1,11 @@
# Python
__pycache__/
*.py[cod]
*.pyo
.Python
*.egg-info/
.eggs/
# Binaries
/bin/
*.exe

View File

@@ -1,8 +1,9 @@
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form, Request
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form, Request, Query
from fastapi.responses import StreamingResponse
from sqlalchemy.orm import Session
from sqlalchemy import or_
from sqlalchemy import or_, func
from typing import List, Optional
import math
import re
from .database import get_db
@@ -16,6 +17,7 @@ from .schemas import (
UploadResponse,
ConsumerResponse,
HealthResponse,
PaginatedResponse, PaginationMeta,
)
router = APIRouter()
@@ -39,13 +41,44 @@ def health_check():
# Project routes
@router.get("/api/v1/projects", response_model=List[ProjectResponse])
def list_projects(request: Request, db: Session = Depends(get_db)):
@router.get("/api/v1/projects", response_model=PaginatedResponse[ProjectResponse])
def list_projects(
request: Request,
page: int = Query(default=1, ge=1, description="Page number"),
limit: int = Query(default=20, ge=1, le=100, description="Items per page"),
search: Optional[str] = Query(default=None, description="Search by project name"),
db: Session = Depends(get_db),
):
user_id = get_user_id(request)
projects = db.query(Project).filter(
# Base query - filter by access
query = db.query(Project).filter(
or_(Project.is_public == True, Project.created_by == user_id)
).order_by(Project.name).all()
return projects
)
# Apply search filter (case-insensitive)
if search:
query = query.filter(func.lower(Project.name).contains(search.lower()))
# Get total count before pagination
total = query.count()
# Apply pagination
offset = (page - 1) * limit
projects = query.order_by(Project.name).offset(offset).limit(limit).all()
# Calculate total pages
total_pages = math.ceil(total / limit) if total > 0 else 1
return PaginatedResponse(
items=projects,
pagination=PaginationMeta(
page=page,
limit=limit,
total=total,
total_pages=total_pages,
),
)
@router.post("/api/v1/projects", response_model=ProjectResponse)

View File

@@ -1,8 +1,23 @@
from datetime import datetime
from typing import Optional, List
from typing import Optional, List, Generic, TypeVar
from pydantic import BaseModel
from uuid import UUID
T = TypeVar("T")
# Pagination schemas
class PaginationMeta(BaseModel):
page: int
limit: int
total: int
total_pages: int
class PaginatedResponse(BaseModel, Generic[T]):
items: List[T]
pagination: PaginationMeta
# Project schemas
class ProjectCreate(BaseModel):