diff --git a/.gitignore b/.gitignore index 6568294..7283f0b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,11 @@ +# Python +__pycache__/ +*.py[cod] +*.pyo +.Python +*.egg-info/ +.eggs/ + # Binaries /bin/ *.exe diff --git a/backend/app/routes.py b/backend/app/routes.py index f151343..dc65ccd 100644 --- a/backend/app/routes.py +++ b/backend/app/routes.py @@ -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) diff --git a/backend/app/schemas.py b/backend/app/schemas.py index dc3ea14..a668966 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -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):