Skip to content

Commit 44708bc

Browse files
committed
fallbacks for random qn
1 parent ff48cc5 commit 44708bc

File tree

1 file changed

+77
-52
lines changed

1 file changed

+77
-52
lines changed

backend/question/src/services/get/get-random-question.ts

Lines changed: 77 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,6 @@ type Params = {
8888

8989
// Fetch an unattempted question or fallback to the least attempted one
9090
export const getRandomQuestion = async ({ userId1, userId2, topics, difficulty }: Params) => {
91-
/**
92-
* 1. Both Unattempted
93-
*/
9491
// If an attempt contains either user's ID
9592
const ids = [userId1, userId2];
9693
const userIdClause = [
@@ -103,62 +100,90 @@ export const getRandomQuestion = async ({ userId1, userId2, topics, difficulty }
103100
or(...userIdClause),
104101
];
105102

106-
// Build the filter clause
107-
// - attempt ID null: No attempts
108-
// - topics: If specified, must intersect using Array Intersect
109-
const filterClause = [];
110-
111-
if (topics) {
112-
filterClause.push(arrayOverlaps(QUESTIONS_TABLE.topic, topics));
113-
}
103+
// Try different filter combinations in order of specificity
104+
const filterCombinations = [
105+
// Exact match
106+
topics && difficulty
107+
? [arrayOverlaps(QUESTIONS_TABLE.topic, topics), eq(QUESTIONS_TABLE.difficulty, difficulty)]
108+
: // Topic only
109+
topics
110+
? [arrayOverlaps(QUESTIONS_TABLE.topic, topics)]
111+
: // Difficulty only
112+
difficulty
113+
? [eq(QUESTIONS_TABLE.difficulty, difficulty)]
114+
: // No filters
115+
[],
116+
];
114117

115-
if (difficulty) {
116-
filterClause.push(eq(QUESTIONS_TABLE.difficulty, difficulty));
118+
// Additional combinations if both topic and difficulty are provided
119+
if (topics && difficulty) {
120+
filterCombinations.push(
121+
// Topic only
122+
[arrayOverlaps(QUESTIONS_TABLE.topic, topics)],
123+
// Difficulty only
124+
[eq(QUESTIONS_TABLE.difficulty, difficulty)],
125+
// No filters
126+
[]
127+
);
117128
}
118129

119-
const bothUnattempted = await db
120-
.select({ question: QUESTIONS_TABLE })
121-
.from(QUESTIONS_TABLE)
122-
.leftJoin(QUESTION_ATTEMPTS_TABLE, and(...joinClause))
123-
.where(and(isNull(QUESTION_ATTEMPTS_TABLE.attemptId), ...filterClause))
124-
.orderBy(sql`RANDOM()`)
125-
.limit(1);
130+
for (const filterClause of filterCombinations) {
131+
// Check if questions exist with current filters
132+
const questionExists = await db
133+
.select({ count: sql<number>`count(*)` })
134+
.from(QUESTIONS_TABLE)
135+
.where(and(...filterClause))
136+
.then((result) => Number(result[0].count) > 0);
126137

127-
if (bothUnattempted && bothUnattempted.length > 0) {
128-
return bothUnattempted[0].question;
129-
}
138+
if (!questionExists) {
139+
continue;
140+
}
130141

131-
// 2. At least one user has attempted.
132-
// - Fetch all questions, summing attempts by both users, ranking and selecting the lowest count.
133-
const attempts = db.$with('at').as(
134-
db
135-
.select({
136-
...getTableColumns(QUESTIONS_TABLE),
137-
user1Count:
138-
sql`SUM(CASE WHEN ${QUESTION_ATTEMPTS_TABLE.userId1} = ${userId1}::uuid OR ${QUESTION_ATTEMPTS_TABLE.userId2} = ${userId1}::uuid THEN 1 END)`.as(
139-
'user1_attempts'
140-
),
141-
user2Count:
142-
sql`SUM(CASE WHEN ${QUESTION_ATTEMPTS_TABLE.userId1} = ${userId2}::uuid OR ${QUESTION_ATTEMPTS_TABLE.userId2} = ${userId2}::uuid THEN 1 END)`.as(
143-
'user2_attempts'
144-
),
145-
})
142+
// Try to find an unattempted question with current filters
143+
const bothUnattempted = await db
144+
.select({ question: QUESTIONS_TABLE })
146145
.from(QUESTIONS_TABLE)
147-
.innerJoin(QUESTION_ATTEMPTS_TABLE, and(...joinClause))
148-
.where(and(...filterClause))
149-
.groupBy(QUESTIONS_TABLE.id)
150-
);
151-
const result = await db
152-
.with(attempts)
153-
.select()
154-
.from(attempts)
155-
.orderBy(asc(sql`COALESCE(user1_attempts,0) + COALESCE(user2_attempts,0)`))
156-
.limit(1);
146+
.leftJoin(QUESTION_ATTEMPTS_TABLE, and(...joinClause))
147+
.where(and(isNull(QUESTION_ATTEMPTS_TABLE.attemptId), ...filterClause))
148+
.orderBy(sql`RANDOM()`)
149+
.limit(1);
150+
151+
if (bothUnattempted && bothUnattempted.length > 0) {
152+
return bothUnattempted[0].question;
153+
}
154+
155+
// If no unattempted question, try least attempted
156+
const attempts = db.$with('at').as(
157+
db
158+
.select({
159+
...getTableColumns(QUESTIONS_TABLE),
160+
user1Count:
161+
sql`SUM(CASE WHEN ${QUESTION_ATTEMPTS_TABLE.userId1} = ${userId1}::uuid OR ${QUESTION_ATTEMPTS_TABLE.userId2} = ${userId1}::uuid THEN 1 END)`.as(
162+
'user1_attempts'
163+
),
164+
user2Count:
165+
sql`SUM(CASE WHEN ${QUESTION_ATTEMPTS_TABLE.userId1} = ${userId2}::uuid OR ${QUESTION_ATTEMPTS_TABLE.userId2} = ${userId2}::uuid THEN 1 END)`.as(
166+
'user2_attempts'
167+
),
168+
})
169+
.from(QUESTIONS_TABLE)
170+
.innerJoin(QUESTION_ATTEMPTS_TABLE, and(...joinClause))
171+
.where(and(...filterClause))
172+
.groupBy(QUESTIONS_TABLE.id)
173+
);
174+
175+
const result = await db
176+
.with(attempts)
177+
.select()
178+
.from(attempts)
179+
.orderBy(asc(sql`COALESCE(user1_attempts,0) + COALESCE(user2_attempts,0)`))
180+
.limit(1);
157181

158-
if (result && result.length > 0) {
159-
return { ...result[0], user1Count: undefined, user2Count: undefined };
182+
if (result && result.length > 0) {
183+
return { ...result[0], user1Count: undefined, user2Count: undefined };
184+
}
160185
}
161186

162-
// This branch should not be reached
163-
logger.info('Unreachable Branch - If first query fails, second query must return something');
187+
logger.error('No questions found with any filter combination');
188+
return null;
164189
};

0 commit comments

Comments
 (0)