Skip to content

Commit ddc16bc

Browse files
Bug/fix qa (#332)
* fix(qa): prevent duplicate questions and improve cache invalidation * fix(qa): keep pagination totals consistent after deduplication * fix(qa): paginate by unique questions and bump cache namespace
1 parent 13d5f77 commit ddc16bc

3 files changed

Lines changed: 23 additions & 28 deletions

File tree

frontend/app/api/questions/[category]/route.ts

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { and, eq, ilike, sql } from 'drizzle-orm';
1+
import { and, eq, ilike } from 'drizzle-orm';
22
import { NextResponse } from 'next/server';
33

44
import { db } from '@/db';
@@ -139,19 +139,7 @@ export async function GET(
139139
? and(baseCondition, ilike(questionTranslations.question, `%${search}%`))
140140
: baseCondition;
141141

142-
const [{ count }] = await db
143-
.select({ count: sql<number>`count(*)` })
144-
.from(questions)
145-
.innerJoin(
146-
questionTranslations,
147-
eq(questions.id, questionTranslations.questionId)
148-
)
149-
.where(whereCondition);
150-
151-
const total = Number(count);
152-
const totalPages = Math.ceil(total / limit);
153-
154-
const items = await db
142+
const allItems = await db
155143
.select({
156144
id: questions.id,
157145
categoryId: questions.categoryId,
@@ -167,20 +155,20 @@ export async function GET(
167155
eq(questions.id, questionTranslations.questionId)
168156
)
169157
.where(whereCondition)
170-
.orderBy(questions.sortOrder)
171-
.limit(limit)
172-
.offset(offset);
158+
.orderBy(questions.sortOrder, questions.id);
173159

174-
const payload = normalizeResponse(
175-
{
160+
const uniqueItems = dedupeItems(allItems);
161+
const total = uniqueItems.length;
162+
const totalPages = Math.ceil(total / limit);
163+
const items = uniqueItems.slice(offset, offset + limit);
164+
165+
const payload = {
176166
items,
177167
total,
178168
page,
179169
totalPages,
180170
locale,
181-
},
182-
limit
183-
);
171+
} satisfies QaApiResponse;
184172
const response = NextResponse.json(payload);
185173
response.headers.set('Cache-Control', 'no-store');
186174
response.headers.set('x-qa-cache', 'MISS');

frontend/lib/cache/qa.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { getRedisClient } from '@/lib/redis';
22

3-
const QA_CACHE_VERSION = 'v2';
3+
const QA_CACHE_VERSION = 'v3';
44
const QA_CACHE_TTL_SECONDS = 60 * 30;
55

66
type QaCacheKeyInput = {

frontend/lib/tests/q&a/questions-route.test.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,8 @@ describe('GET /api/questions/[category]', () => {
6868
const selectMock = db.select as ReturnType<typeof vi.fn>;
6969
selectMock
7070
.mockReturnValueOnce(makeBuilder('limit', [{ id: 'cat-1' }]))
71-
.mockReturnValueOnce(makeBuilder('where', [{ count: 2 }]))
7271
.mockReturnValueOnce(
73-
makeBuilder('offset', [
72+
makeBuilder('orderBy', [
7473
{
7574
id: 'q1',
7675
categoryId: 'cat-1',
@@ -80,6 +79,15 @@ describe('GET /api/questions/[category]', () => {
8079
answerBlocks: [],
8180
locale: 'en',
8281
},
82+
{
83+
id: 'q2',
84+
categoryId: 'cat-1',
85+
sortOrder: 2,
86+
difficulty: null,
87+
question: 'Question 2',
88+
answerBlocks: [],
89+
locale: 'en',
90+
},
8391
])
8492
);
8593

@@ -93,7 +101,7 @@ describe('GET /api/questions/[category]', () => {
93101
const data = await res.json();
94102

95103
expect(res.status).toBe(200);
96-
expect(data.items).toHaveLength(1);
104+
expect(data.items).toHaveLength(2);
97105
expect(data.items[0].question).toBe('Question 1');
98106
expect(data.total).toBe(2);
99107
expect(data.totalPages).toBe(1);
@@ -127,9 +135,8 @@ describe('GET /api/questions/[category]', () => {
127135

128136
selectMock
129137
.mockReturnValueOnce(makeBuilder('limit', [{ id: 'cat-1' }]))
130-
.mockReturnValueOnce(makeBuilder('where', [{ count: 3 }]))
131138
.mockReturnValueOnce(
132-
makeBuilder('offset', [
139+
makeBuilder('orderBy', [
133140
{
134141
id: 'q1',
135142
categoryId: 'cat-1',

0 commit comments

Comments
 (0)