diff --git a/jest.setup.ts b/jest.setup.ts index 021cee1e..a8826e6a 100644 --- a/jest.setup.ts +++ b/jest.setup.ts @@ -86,7 +86,7 @@ jest.mock("react-hotkeys-hook", () => ({ useHotkeys: () => {} })); -jest.mock("@/lib/ratelimit", () => { +jest.mock("@/lib/ratelimit/ratelimit", () => { const createLimiter = () => ({ limit: jest.fn().mockResolvedValue({ success: true, diff --git a/src/app/api/doubts/route.ts b/src/app/api/doubts/route.ts index 5e1edb21..76220dc7 100644 --- a/src/app/api/doubts/route.ts +++ b/src/app/api/doubts/route.ts @@ -162,11 +162,6 @@ export async function GET(req: Request) { conditions.push(eq(doubtsTable.isSolved, "unsolved")); } - const replyCountSql = - sql`coalesce((SELECT count(*) FROM ${repliesTable} WHERE ${repliesTable.doubtId} = ${doubtsTable.id}), 0)`.mapWith( - Number, - ); - const [totalCountRow] = await db .select({ count: count() }) .from(doubtsTable) @@ -176,7 +171,6 @@ export async function GET(req: Request) { const query = db .select({ ...getTableColumns(doubtsTable), - replyCount: replyCountSql, }) .from(doubtsTable); @@ -190,7 +184,10 @@ export async function GET(req: Request) { if (sort === "popular") { orderByFields.push(desc(doubtsTable.likes)); } else if (sort === "most-replied") { - orderByFields.push(desc(replyCountSql)); + // Needed here only to sort correctly at the DB level before paging. + // Not selected as a column, so it costs nothing for any other sort mode. + const replyCountOrderSql = sql`coalesce((SELECT count(*) FROM ${repliesTable} WHERE ${repliesTable.doubtId} = ${doubtsTable.id}), 0)`; + orderByFields.push(desc(replyCountOrderSql)); } orderByFields.push(desc(doubtsTable.createdAt)); @@ -200,6 +197,25 @@ export async function GET(req: Request) { .limit(limit) .offset(offset); + // Batch-fetch reply counts for just the returned page (max `limit` ids) + // instead of running a correlated subquery per row in the SELECT. + if (doubts.length > 0) { + const replyCountRows = await db + .select({ doubtId: repliesTable.doubtId, count: count() }) + .from(repliesTable) + .where(inArray(repliesTable.doubtId, doubts.map((d: any) => d.id))) + .groupBy(repliesTable.doubtId); + + const replyCountMap = new Map( + replyCountRows.map((r: any) => [r.doubtId, Number(r.count)]), + ); + + doubts = doubts.map((doubt: any) => ({ + ...doubt, + replyCount: replyCountMap.get(doubt.id) ?? 0, + })); + } + if (email && doubts.length > 0) { const userLikes = await db .select({ doubtId: likesTable.doubtId })