feat(comments): implement pagination for comments retrieval with limit and offset

This commit is contained in:
Yusuf İpek
2026-04-09 16:35:02 +03:00
parent 11dfa4938f
commit 173261149f
2 changed files with 55 additions and 25 deletions
+26 -14
View File
@@ -83,15 +83,23 @@ export async function GET(request: NextRequest, { params }: RouteParams) {
const { searchParams } = new URL(request.url); const { searchParams } = new URL(request.url);
const includeResolved = searchParams.get('includeResolved') !== 'false'; const includeResolved = searchParams.get('includeResolved') !== 'false';
const limit = Math.min(Math.max(1, parseInt(searchParams.get('limit') ?? '200', 10)), 500);
const offset = Math.max(0, parseInt(searchParams.get('offset') ?? '0', 10));
const commentsRevision = await db.comment.aggregate({ const commentsFilter = {
where: { versionId,
versionId, parentId: null as null,
...(includeResolved ? {} : { isResolved: false }), ...(includeResolved ? {} : { isResolved: false }),
}, };
_count: { id: true },
_max: { updatedAt: true }, const [commentsRevision, total] = await Promise.all([
}); db.comment.aggregate({
where: { versionId, ...(includeResolved ? {} : { isResolved: false }) },
_count: { id: true },
_max: { updatedAt: true },
}),
db.comment.count({ where: commentsFilter }),
]);
const etag = `"comments:${versionId}:${includeResolved ? 1 : 0}:${commentsRevision._count.id}:${commentsRevision._max.updatedAt?.getTime() ?? 0}"`; const etag = `"comments:${versionId}:${includeResolved ? 1 : 0}:${commentsRevision._count.id}:${commentsRevision._max.updatedAt?.getTime() ?? 0}"`;
const ifNoneMatch = request.headers.get('if-none-match'); const ifNoneMatch = request.headers.get('if-none-match');
@@ -109,12 +117,10 @@ export async function GET(request: NextRequest, { params }: RouteParams) {
} }
const comments = await db.comment.findMany({ const comments = await db.comment.findMany({
where: { where: commentsFilter,
versionId,
parentId: null, // Only top-level comments
...(includeResolved ? {} : { isResolved: false }),
},
orderBy: { timestamp: 'asc' }, orderBy: { timestamp: 'asc' },
skip: offset,
take: limit,
select: { select: {
id: true, id: true,
content: true, content: true,
@@ -162,7 +168,13 @@ export async function GET(request: NextRequest, { params }: RouteParams) {
}, },
}); });
const response = successResponse({ comments }); const response = successResponse({
comments,
total,
hasMore: offset + comments.length < total,
offset,
limit,
});
response.headers.set('ETag', etag); response.headers.set('ETag', etag);
return withCacheControl(response, 'private, no-cache'); return withCacheControl(response, 'private, no-cache');
} catch (error) { } catch (error) {
@@ -38,20 +38,38 @@ export function useVideoPageData({
if (etag) headers['If-None-Match'] = etag; if (etag) headers['If-None-Match'] = etag;
} }
const res = await fetch(`/api/versions/${versionId}/comments?includeResolved=true`, { const LIMIT = 200;
cache: 'no-store', let offset = 0;
headers, let allComments: Comment[] = [];
}); let latestEtag: string | null = null;
if (res.status === 304) return; // Fetch pages until we have all comments
if (!res.ok) return; while (true) {
const res = await fetch(
`/api/versions/${versionId}/comments?includeResolved=true&limit=${LIMIT}&offset=${offset}`,
{ cache: 'no-store', headers: offset === 0 ? headers : {} }
);
const etag = res.headers.get('etag'); if (offset === 0 && res.status === 304) return;
if (etag) commentsEtagRef.current.set(versionId, etag); if (!res.ok) return;
const payload = await res.json(); if (offset === 0) {
const commentsList = payload?.data?.comments; latestEtag = res.headers.get('etag');
if (!Array.isArray(commentsList)) return; }
const payload = await res.json();
const page: Comment[] = payload?.data?.comments;
if (!Array.isArray(page)) return;
allComments = allComments.concat(page);
if (!payload?.data?.hasMore) break;
offset += LIMIT;
}
if (latestEtag) commentsEtagRef.current.set(versionId, latestEtag);
const commentsList = allComments;
setVideo((prev) => { setVideo((prev) => {
if (!prev) return prev; if (!prev) return prev;