diff --git a/src/lib/queries/agents.test.ts b/src/lib/queries/agents.test.ts index cae14f6e..2665deb1 100644 --- a/src/lib/queries/agents.test.ts +++ b/src/lib/queries/agents.test.ts @@ -101,4 +101,22 @@ describe("buildAgentsQuery", () => { expect(mock.chain.range).toHaveBeenCalledWith(40, 59); }); + + it("clamps negative page values to page 1", () => { + buildAgentsQuery(mock.client, { page: "-1" }); + + expect(mock.chain.range).toHaveBeenCalledWith(0, 19); + }); + + it("falls back to page 1 for non-numeric values", () => { + buildAgentsQuery(mock.client, { page: "abc" }); + + expect(mock.chain.range).toHaveBeenCalledWith(0, 19); + }); + + it("caps very large page values before calculating the range", () => { + buildAgentsQuery(mock.client, { page: "999999999" }); + + expect(mock.chain.range).toHaveBeenCalledWith(1999980, 1999999); + }); }); diff --git a/src/lib/queries/agents.ts b/src/lib/queries/agents.ts index 3faf33a5..69576b25 100644 --- a/src/lib/queries/agents.ts +++ b/src/lib/queries/agents.ts @@ -1,5 +1,7 @@ import { SupabaseClient } from "@supabase/supabase-js"; +const MAX_PAGE = 100_000; + export interface AgentsQueryParams { q?: string; sort?: string; @@ -8,6 +10,13 @@ export interface AgentsQueryParams { tags?: string[]; } +function parsePage(value?: string) { + const parsed = parseInt(value || "1", 10); + return Number.isFinite(parsed) + ? Math.min(Math.max(parsed, 1), MAX_PAGE) + : 1; +} + export function buildAgentsQuery( supabase: SupabaseClient, params: AgentsQueryParams @@ -49,7 +58,7 @@ export function buildAgentsQuery( query = query.order("created_at", { ascending: false }); } - const pageNum = parseInt(page || "1"); + const pageNum = parsePage(page); const limit = 20; const offset = (pageNum - 1) * limit; query = query.range(offset, offset + limit - 1); diff --git a/src/lib/queries/candidates.test.ts b/src/lib/queries/candidates.test.ts index 201ebaf8..1e9b3afa 100644 --- a/src/lib/queries/candidates.test.ts +++ b/src/lib/queries/candidates.test.ts @@ -73,4 +73,22 @@ describe("buildCandidatesQuery", () => { expect(mock.chain.range).toHaveBeenCalledWith(20, 39); }); + + it("clamps negative page values to page 1", () => { + buildCandidatesQuery(mock.client, { page: "-1" }); + + expect(mock.chain.range).toHaveBeenCalledWith(0, 19); + }); + + it("falls back to page 1 for non-numeric values", () => { + buildCandidatesQuery(mock.client, { page: "abc" }); + + expect(mock.chain.range).toHaveBeenCalledWith(0, 19); + }); + + it("caps very large page values before calculating the range", () => { + buildCandidatesQuery(mock.client, { page: "999999999" }); + + expect(mock.chain.range).toHaveBeenCalledWith(1999980, 1999999); + }); }); diff --git a/src/lib/queries/candidates.ts b/src/lib/queries/candidates.ts index 9615c033..767db245 100644 --- a/src/lib/queries/candidates.ts +++ b/src/lib/queries/candidates.ts @@ -1,5 +1,7 @@ import { SupabaseClient } from "@supabase/supabase-js"; +const MAX_PAGE = 100_000; + export interface CandidatesQueryParams { q?: string; sort?: string; @@ -8,6 +10,13 @@ export interface CandidatesQueryParams { tags?: string[]; } +function parsePage(value?: string) { + const parsed = parseInt(value || "1", 10); + return Number.isFinite(parsed) + ? Math.min(Math.max(parsed, 1), MAX_PAGE) + : 1; +} + export function buildCandidatesQuery( supabase: SupabaseClient, params: CandidatesQueryParams @@ -49,7 +58,7 @@ export function buildCandidatesQuery( query = query.order("created_at", { ascending: false }); } - const pageNum = parseInt(page || "1"); + const pageNum = parsePage(page); const limit = 20; const offset = (pageNum - 1) * limit; query = query.range(offset, offset + limit - 1);