From 523934e5b49d8873365d9fdfc002b33f60f0f5e9 Mon Sep 17 00:00:00 2001 From: sharma-sugurthi Date: Sun, 1 Mar 2026 18:18:19 +0530 Subject: [PATCH 1/3] feat: Add API Pagination to Backend List Endpoints - Create pagination utility module (utils/pagination.py) - Add pagination support to all 7 list endpoints: - GET /users/ - GET /audience-insights/ - GET /sponsorships/ - GET /posts/ - GET /sponsorship-applications/ - GET /sponsorship-payments/ - GET /collaborations/ - Support skip/limit query parameters with validation - Return paginated response with metadata (data, skip, limit, count) --- Backend/app/routes/post.py | 95 +++++++++++++++++++++++++-------- Backend/app/utils/__init__.py | 0 Backend/app/utils/pagination.py | 32 +++++++++++ 3 files changed, 104 insertions(+), 23 deletions(-) create mode 100644 Backend/app/utils/__init__.py create mode 100644 Backend/app/utils/pagination.py diff --git a/Backend/app/routes/post.py b/Backend/app/routes/post.py index a90e313..bbf1828 100644 --- a/Backend/app/routes/post.py +++ b/Backend/app/routes/post.py @@ -1,4 +1,4 @@ -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.future import select from ..db.db import AsyncSessionLocal @@ -10,8 +10,8 @@ UserCreate, AudienceInsightsCreate, SponsorshipCreate, UserPostCreate, SponsorshipApplicationCreate, SponsorshipPaymentCreate, CollaborationCreate ) +from ..utils.pagination import get_pagination_params, paginate_query -from fastapi import APIRouter, HTTPException import os from supabase import create_client, Client from dotenv import load_dotenv @@ -53,9 +53,16 @@ async def create_user(user: UserCreate): return response @router.get("/users/") -async def get_users(): - result = supabase.table("users").select("*").execute() - return result +async def get_users( + skip: int = Query(0, ge=0, description="Number of records to skip"), + limit: int = Query(50, ge=1, le=100, description="Maximum number of records to return") +): + skip, limit = get_pagination_params(skip, limit) + start = skip + end = skip + limit - 1 + + result = supabase.table("users").select("*").range(start, end).execute() + return paginate_query(result, skip, limit) # ========== AUDIENCE INSIGHTS ROUTES ========== @router.post("/audience-insights/") @@ -78,9 +85,16 @@ async def create_audience_insights(insights: AudienceInsightsCreate): return response @router.get("/audience-insights/") -async def get_audience_insights(): - result = supabase.table("audience_insights").select("*").execute() - return result +async def get_audience_insights( + skip: int = Query(0, ge=0, description="Number of records to skip"), + limit: int = Query(50, ge=1, le=100, description="Maximum number of records to return") +): + skip, limit = get_pagination_params(skip, limit) + start = skip + end = skip + limit - 1 + + result = supabase.table("audience_insights").select("*").range(start, end).execute() + return paginate_query(result, skip, limit) # ========== SPONSORSHIP ROUTES ========== @router.post("/sponsorships/") @@ -103,9 +117,16 @@ async def create_sponsorship(sponsorship: SponsorshipCreate): return response @router.get("/sponsorships/") -async def get_sponsorships(): - result = supabase.table("sponsorships").select("*").execute() - return result +async def get_sponsorships( + skip: int = Query(0, ge=0, description="Number of records to skip"), + limit: int = Query(50, ge=1, le=100, description="Maximum number of records to return") +): + skip, limit = get_pagination_params(skip, limit) + start = skip + end = skip + limit - 1 + + result = supabase.table("sponsorships").select("*").range(start, end).execute() + return paginate_query(result, skip, limit) # ========== USER POST ROUTES ========== @router.post("/posts/") @@ -127,9 +148,16 @@ async def create_post(post: UserPostCreate): return response @router.get("/posts/") -async def get_posts(): - result = supabase.table("user_posts").select("*").execute() - return result +async def get_posts( + skip: int = Query(0, ge=0, description="Number of records to skip"), + limit: int = Query(50, ge=1, le=100, description="Maximum number of records to return") +): + skip, limit = get_pagination_params(skip, limit) + start = skip + end = skip + limit - 1 + + result = supabase.table("user_posts").select("*").range(start, end).execute() + return paginate_query(result, skip, limit) # ========== SPONSORSHIP APPLICATION ROUTES ========== @router.post("/sponsorship-applications/") @@ -150,9 +178,16 @@ async def create_sponsorship_application(application: SponsorshipApplicationCrea return response @router.get("/sponsorship-applications/") -async def get_sponsorship_applications(): - result = supabase.table("sponsorship_applications").select("*").execute() - return result +async def get_sponsorship_applications( + skip: int = Query(0, ge=0, description="Number of records to skip"), + limit: int = Query(50, ge=1, le=100, description="Maximum number of records to return") +): + skip, limit = get_pagination_params(skip, limit) + start = skip + end = skip + limit - 1 + + result = supabase.table("sponsorship_applications").select("*").range(start, end).execute() + return paginate_query(result, skip, limit) # ========== SPONSORSHIP PAYMENT ROUTES ========== @router.post("/sponsorship-payments/") @@ -172,9 +207,16 @@ async def create_sponsorship_payment(payment: SponsorshipPaymentCreate): return response @router.get("/sponsorship-payments/") -async def get_sponsorship_payments(): - result = supabase.table("sponsorship_payments").select("*").execute() - return result +async def get_sponsorship_payments( + skip: int = Query(0, ge=0, description="Number of records to skip"), + limit: int = Query(50, ge=1, le=100, description="Maximum number of records to return") +): + skip, limit = get_pagination_params(skip, limit) + start = skip + end = skip + limit - 1 + + result = supabase.table("sponsorship_payments").select("*").range(start, end).execute() + return paginate_query(result, skip, limit) # ========== COLLABORATION ROUTES ========== @router.post("/collaborations/") @@ -194,6 +236,13 @@ async def create_collaboration(collab: CollaborationCreate): return response @router.get("/collaborations/") -async def get_collaborations(): - result = supabase.table("collaborations").select("*").execute() - return result +async def get_collaborations( + skip: int = Query(0, ge=0, description="Number of records to skip"), + limit: int = Query(50, ge=1, le=100, description="Maximum number of records to return") +): + skip, limit = get_pagination_params(skip, limit) + start = skip + end = skip + limit - 1 + + result = supabase.table("collaborations").select("*").range(start, end).execute() + return paginate_query(result, skip, limit) diff --git a/Backend/app/utils/__init__.py b/Backend/app/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Backend/app/utils/pagination.py b/Backend/app/utils/pagination.py new file mode 100644 index 0000000..a5ba2ba --- /dev/null +++ b/Backend/app/utils/pagination.py @@ -0,0 +1,32 @@ +from typing import Dict, Any + + +DEFAULT_SKIP = 0 +DEFAULT_LIMIT = 50 +MAX_LIMIT = 100 + + +def get_pagination_params(skip: int = DEFAULT_SKIP, limit: int = DEFAULT_LIMIT) -> tuple: + """Validate and return pagination parameters.""" + if skip < 0: + skip = DEFAULT_SKIP + if limit < 1: + limit = DEFAULT_LIMIT + if limit > MAX_LIMIT: + limit = MAX_LIMIT + return skip, limit + + +def paginate_query(query_result, skip: int, limit: int) -> Dict[str, Any]: + """Format paginated response with metadata.""" + data = query_result.data if hasattr(query_result, 'data') else query_result + + if not isinstance(data, list): + data = [] + + return { + "data": data, + "skip": skip, + "limit": limit, + "count": len(data) + } From d0d260e6ddf7fb1595d5ffaf4ef021a9fdb0cc88 Mon Sep 17 00:00:00 2001 From: sharma-sugurthi Date: Sun, 1 Mar 2026 18:32:41 +0530 Subject: [PATCH 2/3] fix: Add ORDER BY and total_count for proper pagination - Add .order('id') to all paginated queries for deterministic ordering - Add count query to get total_count for pagination UX - Update paginate_query to include total_count in response Addresses CodeRabbit review feedback --- Backend/app/routes/post.py | 42 ++++++++++++++++++++++----------- Backend/app/utils/pagination.py | 5 ++-- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/Backend/app/routes/post.py b/Backend/app/routes/post.py index bbf1828..7e1cdc2 100644 --- a/Backend/app/routes/post.py +++ b/Backend/app/routes/post.py @@ -61,8 +61,10 @@ async def get_users( start = skip end = skip + limit - 1 - result = supabase.table("users").select("*").range(start, end).execute() - return paginate_query(result, skip, limit) + result = supabase.table("users").select("*").order("id").range(start, end).execute() + count_result = supabase.table("users").select("*", count="exact").execute() + total_count = count_result.count if hasattr(count_result, 'count') else len(count_result.data) + return paginate_query(result, skip, limit, total_count) # ========== AUDIENCE INSIGHTS ROUTES ========== @router.post("/audience-insights/") @@ -93,8 +95,10 @@ async def get_audience_insights( start = skip end = skip + limit - 1 - result = supabase.table("audience_insights").select("*").range(start, end).execute() - return paginate_query(result, skip, limit) + result = supabase.table("audience_insights").select("*").order("id").range(start, end).execute() + count_result = supabase.table("audience_insights").select("*", count="exact").execute() + total_count = count_result.count if hasattr(count_result, 'count') else len(count_result.data) + return paginate_query(result, skip, limit, total_count) # ========== SPONSORSHIP ROUTES ========== @router.post("/sponsorships/") @@ -125,8 +129,10 @@ async def get_sponsorships( start = skip end = skip + limit - 1 - result = supabase.table("sponsorships").select("*").range(start, end).execute() - return paginate_query(result, skip, limit) + result = supabase.table("sponsorships").select("*").order("id").range(start, end).execute() + count_result = supabase.table("sponsorships").select("*", count="exact").execute() + total_count = count_result.count if hasattr(count_result, 'count') else len(count_result.data) + return paginate_query(result, skip, limit, total_count) # ========== USER POST ROUTES ========== @router.post("/posts/") @@ -156,8 +162,10 @@ async def get_posts( start = skip end = skip + limit - 1 - result = supabase.table("user_posts").select("*").range(start, end).execute() - return paginate_query(result, skip, limit) + result = supabase.table("user_posts").select("*").order("id").range(start, end).execute() + count_result = supabase.table("user_posts").select("*", count="exact").execute() + total_count = count_result.count if hasattr(count_result, 'count') else len(count_result.data) + return paginate_query(result, skip, limit, total_count) # ========== SPONSORSHIP APPLICATION ROUTES ========== @router.post("/sponsorship-applications/") @@ -186,8 +194,10 @@ async def get_sponsorship_applications( start = skip end = skip + limit - 1 - result = supabase.table("sponsorship_applications").select("*").range(start, end).execute() - return paginate_query(result, skip, limit) + result = supabase.table("sponsorship_applications").select("*").order("id").range(start, end).execute() + count_result = supabase.table("sponsorship_applications").select("*", count="exact").execute() + total_count = count_result.count if hasattr(count_result, 'count') else len(count_result.data) + return paginate_query(result, skip, limit, total_count) # ========== SPONSORSHIP PAYMENT ROUTES ========== @router.post("/sponsorship-payments/") @@ -215,8 +225,10 @@ async def get_sponsorship_payments( start = skip end = skip + limit - 1 - result = supabase.table("sponsorship_payments").select("*").range(start, end).execute() - return paginate_query(result, skip, limit) + result = supabase.table("sponsorship_payments").select("*").order("id").range(start, end).execute() + count_result = supabase.table("sponsorship_payments").select("*", count="exact").execute() + total_count = count_result.count if hasattr(count_result, 'count') else len(count_result.data) + return paginate_query(result, skip, limit, total_count) # ========== COLLABORATION ROUTES ========== @router.post("/collaborations/") @@ -244,5 +256,7 @@ async def get_collaborations( start = skip end = skip + limit - 1 - result = supabase.table("collaborations").select("*").range(start, end).execute() - return paginate_query(result, skip, limit) + result = supabase.table("collaborations").select("*").order("id").range(start, end).execute() + count_result = supabase.table("collaborations").select("*", count="exact").execute() + total_count = count_result.count if hasattr(count_result, 'count') else len(count_result.data) + return paginate_query(result, skip, limit, total_count) diff --git a/Backend/app/utils/pagination.py b/Backend/app/utils/pagination.py index a5ba2ba..06da23f 100644 --- a/Backend/app/utils/pagination.py +++ b/Backend/app/utils/pagination.py @@ -17,7 +17,7 @@ def get_pagination_params(skip: int = DEFAULT_SKIP, limit: int = DEFAULT_LIMIT) return skip, limit -def paginate_query(query_result, skip: int, limit: int) -> Dict[str, Any]: +def paginate_query(query_result, skip: int, limit: int, total_count: int = 0) -> Dict[str, Any]: """Format paginated response with metadata.""" data = query_result.data if hasattr(query_result, 'data') else query_result @@ -28,5 +28,6 @@ def paginate_query(query_result, skip: int, limit: int) -> Dict[str, Any]: "data": data, "skip": skip, "limit": limit, - "count": len(data) + "count": len(data), + "total_count": total_count } From d4840c9748c8df22bd8fb66e75c4fc9e6be6feb8 Mon Sep 17 00:00:00 2001 From: sharma-sugurthi Date: Sun, 1 Mar 2026 18:42:57 +0530 Subject: [PATCH 3/3] fix: Optimize count query performance with .limit(0) - Add .limit(0) to all count queries to prevent fetching data - Returns only count metadata without transferring rows - Addresses CodeRabbit performance feedback --- Backend/app/routes/post.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Backend/app/routes/post.py b/Backend/app/routes/post.py index 7e1cdc2..673856c 100644 --- a/Backend/app/routes/post.py +++ b/Backend/app/routes/post.py @@ -62,7 +62,7 @@ async def get_users( end = skip + limit - 1 result = supabase.table("users").select("*").order("id").range(start, end).execute() - count_result = supabase.table("users").select("*", count="exact").execute() + count_result = supabase.table("users").select("*", count="exact").limit(0).execute() total_count = count_result.count if hasattr(count_result, 'count') else len(count_result.data) return paginate_query(result, skip, limit, total_count) @@ -96,7 +96,7 @@ async def get_audience_insights( end = skip + limit - 1 result = supabase.table("audience_insights").select("*").order("id").range(start, end).execute() - count_result = supabase.table("audience_insights").select("*", count="exact").execute() + count_result = supabase.table("audience_insights").select("*", count="exact").limit(0).execute() total_count = count_result.count if hasattr(count_result, 'count') else len(count_result.data) return paginate_query(result, skip, limit, total_count) @@ -130,7 +130,7 @@ async def get_sponsorships( end = skip + limit - 1 result = supabase.table("sponsorships").select("*").order("id").range(start, end).execute() - count_result = supabase.table("sponsorships").select("*", count="exact").execute() + count_result = supabase.table("sponsorships").select("*", count="exact").limit(0).execute() total_count = count_result.count if hasattr(count_result, 'count') else len(count_result.data) return paginate_query(result, skip, limit, total_count) @@ -163,7 +163,7 @@ async def get_posts( end = skip + limit - 1 result = supabase.table("user_posts").select("*").order("id").range(start, end).execute() - count_result = supabase.table("user_posts").select("*", count="exact").execute() + count_result = supabase.table("user_posts").select("*", count="exact").limit(0).execute() total_count = count_result.count if hasattr(count_result, 'count') else len(count_result.data) return paginate_query(result, skip, limit, total_count) @@ -195,7 +195,7 @@ async def get_sponsorship_applications( end = skip + limit - 1 result = supabase.table("sponsorship_applications").select("*").order("id").range(start, end).execute() - count_result = supabase.table("sponsorship_applications").select("*", count="exact").execute() + count_result = supabase.table("sponsorship_applications").select("*", count="exact").limit(0).execute() total_count = count_result.count if hasattr(count_result, 'count') else len(count_result.data) return paginate_query(result, skip, limit, total_count) @@ -226,7 +226,7 @@ async def get_sponsorship_payments( end = skip + limit - 1 result = supabase.table("sponsorship_payments").select("*").order("id").range(start, end).execute() - count_result = supabase.table("sponsorship_payments").select("*", count="exact").execute() + count_result = supabase.table("sponsorship_payments").select("*", count="exact").limit(0).execute() total_count = count_result.count if hasattr(count_result, 'count') else len(count_result.data) return paginate_query(result, skip, limit, total_count) @@ -257,6 +257,6 @@ async def get_collaborations( end = skip + limit - 1 result = supabase.table("collaborations").select("*").order("id").range(start, end).execute() - count_result = supabase.table("collaborations").select("*", count="exact").execute() + count_result = supabase.table("collaborations").select("*", count="exact").limit(0).execute() total_count = count_result.count if hasattr(count_result, 'count') else len(count_result.data) return paginate_query(result, skip, limit, total_count)