diff --git a/src/__tests__/api/prompts.test.ts b/src/__tests__/api/prompts.test.ts index 97305a50..52eb6f0b 100644 --- a/src/__tests__/api/prompts.test.ts +++ b/src/__tests__/api/prompts.test.ts @@ -87,6 +87,36 @@ describe("GET /api/prompts", () => { expect(data.perPage).toBe(24); }); + it("should fall back to defaults when pagination parameters are malformed", async () => { + vi.mocked(db.prompt.findMany).mockResolvedValue([]); + vi.mocked(db.prompt.count).mockResolvedValue(0); + + const request = new Request( + "http://localhost:3000/api/prompts?page=not-a-number&perPage=bad-value" + ); + const response = await GET(request); + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.page).toBe(1); + expect(data.perPage).toBe(24); + }); + + it("should clamp pagination parameters to configured maximums", async () => { + vi.mocked(db.prompt.findMany).mockResolvedValue([]); + vi.mocked(db.prompt.count).mockResolvedValue(0); + + const request = new Request( + "http://localhost:3000/api/prompts?page=20000&perPage=999" + ); + const response = await GET(request); + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.page).toBe(10000); + expect(data.perPage).toBe(100); + }); + it("should filter by type", async () => { vi.mocked(db.prompt.findMany).mockResolvedValue([]); vi.mocked(db.prompt.count).mockResolvedValue(0); diff --git a/src/app/api/prompts/route.ts b/src/app/api/prompts/route.ts index 5ec5dbf4..838faa66 100644 --- a/src/app/api/prompts/route.ts +++ b/src/app/api/prompts/route.ts @@ -306,11 +306,33 @@ export async function POST(request: Request) { } // List prompts (for API access) +const MAX_API_PER_PAGE = 100; +const DEFAULT_API_PER_PAGE = 24; +const MAX_API_PAGE = 10000; +const DEFAULT_API_PAGE = 1; + +const paginationQuerySchema = z.object({ + page: z.coerce + .number() + .int() + .min(1) + .transform((value) => Math.min(value, MAX_API_PAGE)) + .catch(DEFAULT_API_PAGE), + perPage: z.coerce + .number() + .int() + .min(1) + .transform((value) => Math.min(value, MAX_API_PER_PAGE)) + .catch(DEFAULT_API_PER_PAGE), +}); + export async function GET(request: Request) { try { const { searchParams } = new URL(request.url); - const page = parseInt(searchParams.get("page") || "1"); - const perPage = parseInt(searchParams.get("perPage") || "24"); + const { page, perPage } = paginationQuerySchema.parse({ + page: searchParams.get("page"), + perPage: searchParams.get("perPage"), + }); const type = searchParams.get("type"); const categoryId = searchParams.get("category"); const tag = searchParams.get("tag");