diff --git a/src/app/api/chat/messages/pagination.js b/src/app/api/chat/messages/pagination.js new file mode 100644 index 0000000..588bba4 --- /dev/null +++ b/src/app/api/chat/messages/pagination.js @@ -0,0 +1,26 @@ +const DEFAULT_LIMIT = 50; +const MAX_LIMIT = 100; +const DEFAULT_OFFSET = 0; + +export function normalizeMessagePagination(searchParams) { + return { + limit: readInteger(searchParams.get('limit'), { + fallback: DEFAULT_LIMIT, + min: 1, + max: MAX_LIMIT + }), + offset: readInteger(searchParams.get('offset'), { + fallback: DEFAULT_OFFSET, + min: 0 + }) + }; +} + +function readInteger(value, { fallback, min, max }) { + if (value == null || value === '') return fallback; + + const parsed = Number(value); + if (!Number.isInteger(parsed) || parsed < min) return fallback; + + return typeof max === 'number' ? Math.min(parsed, max) : parsed; +} diff --git a/src/app/api/chat/messages/pagination.test.js b/src/app/api/chat/messages/pagination.test.js new file mode 100644 index 0000000..45071ed --- /dev/null +++ b/src/app/api/chat/messages/pagination.test.js @@ -0,0 +1,32 @@ +import { describe, expect, it } from 'vitest'; +import { normalizeMessagePagination } from './pagination.js'; + +describe('normalizeMessagePagination', () => { + it('uses default pagination when query params are missing', () => { + expect(normalizeMessagePagination(new URLSearchParams())).toEqual({ + limit: 50, + offset: 0 + }); + }); + + it('accepts valid integer limit and offset params', () => { + expect(normalizeMessagePagination(new URLSearchParams('limit=25&offset=10'))).toEqual({ + limit: 25, + offset: 10 + }); + }); + + it('falls back for malformed or negative pagination params', () => { + expect(normalizeMessagePagination(new URLSearchParams('limit=1abc&offset=-5'))).toEqual({ + limit: 50, + offset: 0 + }); + }); + + it('caps oversized limits before building the database range', () => { + expect(normalizeMessagePagination(new URLSearchParams('limit=1000&offset=2'))).toEqual({ + limit: 100, + offset: 2 + }); + }); +}); diff --git a/src/app/api/chat/messages/route.js b/src/app/api/chat/messages/route.js index 1fcc14c..17eb1a6 100644 --- a/src/app/api/chat/messages/route.js +++ b/src/app/api/chat/messages/route.js @@ -5,6 +5,7 @@ import { NextResponse } from 'next/server'; import { createServiceRoleClient } from '@/lib/supabase/service-role.js'; import { createClient } from '@supabase/supabase-js'; +import { normalizeMessagePagination } from './pagination.js'; // Lazy service role client creation let supabaseServiceRole = null; function getServiceRoleClient() { @@ -277,8 +278,7 @@ export async function GET(request) { console.log('🔐 [API] ✅ Internal user ID:', userId); const conversationId = url.searchParams.get('conversation_id'); - const limit = parseInt(url.searchParams.get('limit') || '50'); - const offset = parseInt(url.searchParams.get('offset') || '0'); + const { limit, offset } = normalizeMessagePagination(url.searchParams); if (!conversationId) { return NextResponse.json({ error: 'Missing conversation_id parameter' }, { status: 400 }); @@ -492,4 +492,4 @@ export async function PATCH(request, { params } = {}) { console.error('Error in PATCH /api/chat/messages/:id/read:', error); return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } -} \ No newline at end of file +}