diff --git a/src/app/api/gigs/route.test.ts b/src/app/api/gigs/route.test.ts index 5006710d..ca705ff7 100644 --- a/src/app/api/gigs/route.test.ts +++ b/src/app/api/gigs/route.test.ts @@ -113,6 +113,37 @@ describe("GET /api/gigs", () => { expect(json.pagination.total).toBe(1); }); + it("caps huge page values before building the Supabase range", async () => { + const chain = chainResult({ data: null, error: null }); + chain.select = vi.fn().mockReturnValue(chain); + chain.range = vi.fn().mockResolvedValue({ data: [], error: null, count: 0 }); + + mockFrom.mockReturnValue(chain); + + const res = await GET(makeGetRequest({ page: "999999999" })); + const json = await res.json(); + + expect(res.status).toBe(200); + expect(chain.range).toHaveBeenCalledWith(1999980, 1999999); + expect(json.pagination.page).toBe(100000); + }); + + it("caps huge limit values before building range and pagination metadata", async () => { + const chain = chainResult({ data: null, error: null }); + chain.select = vi.fn().mockReturnValue(chain); + chain.range = vi.fn().mockResolvedValue({ data: [], error: null, count: 125 }); + + mockFrom.mockReturnValue(chain); + + const res = await GET(makeGetRequest({ limit: "999999999" })); + const json = await res.json(); + + expect(res.status).toBe(200); + expect(chain.range).toHaveBeenCalledWith(0, 49); + expect(json.pagination.limit).toBe(50); + expect(json.pagination.totalPages).toBe(3); + }); + it("filters by listing_type when provided", async () => { const chain = chainResult({ data: null, error: null }); chain.select = vi.fn().mockReturnValue(chain); diff --git a/src/app/api/gigs/route.ts b/src/app/api/gigs/route.ts index 952f81a0..5d7172d0 100644 --- a/src/app/api/gigs/route.ts +++ b/src/app/api/gigs/route.ts @@ -7,6 +7,9 @@ import { sanitizeTitle, sanitizeContent, stripProtoPollution } from "@/lib/sanit import { getUserDid, onGigPosted } from "@/lib/reputation-hooks"; import { logActivity } from "@/lib/activity"; +const MAX_GIG_PAGE = 100_000; +const MAX_GIG_LIMIT = 50; + // GET /api/gigs - List gigs (public) export async function GET(request: NextRequest) { try { @@ -24,8 +27,8 @@ export async function GET(request: NextRequest) { account_type: searchParams.get("account_type") || undefined, listing_type: searchParams.get("listing_type") || undefined, sort: searchParams.get("sort") || "newest", - page: Number(searchParams.get("page")) || 1, - limit: Number(searchParams.get("limit")) || 20, + page: Math.min(Number(searchParams.get("page")) || 1, MAX_GIG_PAGE), + limit: Math.min(Number(searchParams.get("limit")) || 20, MAX_GIG_LIMIT), }); if (!filters.success) { @@ -128,8 +131,7 @@ export async function GET(request: NextRequest) { // Apply pagination — ensure non-negative offset (#69) const offset = Math.max(0, (page - 1) * limit); - const clampedLimit = Math.max(1, Math.min(50, limit)); - query = query.range(offset, offset + clampedLimit - 1); + query = query.range(offset, offset + limit - 1); const { data: gigs, error, count } = await query;