Skip to content

Commit d0376b4

Browse files
authored
Merge branch 'main' into d6/ui-fixes
2 parents f1a27bf + c780947 commit d0376b4

File tree

4 files changed

+94
-43
lines changed

4 files changed

+94
-43
lines changed

backend/matching/src/services/collab.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { collabServiceClient, routes } from './_hosts';
33
export async function createRoom(
44
userId1: string,
55
userId2: string,
6-
questionId: string
6+
questionId: string,
7+
_attemptCounts: number
78
): Promise<string> {
89
const response = await collabServiceClient.get<{ roomName: string }>(
910
routes.COLLAB_SERVICE.GET_ROOM.path,

backend/matching/src/services/get-match-items.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,12 @@ export async function getMatchItems(
3333
return undefined;
3434
}
3535

36-
const roomId = await createRoom(userId1, userId2, question.id.toString());
36+
const roomId = await createRoom(
37+
userId1,
38+
userId2,
39+
question.id.toString(),
40+
question.attemptCount
41+
);
3742

3843
logger.info('Successfully got match items');
3944
return {

backend/matching/src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export interface IQuestion {
5858
// description: string;
5959
// difficulty: string;
6060
// topic: string[];
61+
attemptCount: number;
6162
}
6263

6364
export interface IGetRandomQuestionPayload {

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

Lines changed: 85 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
eq,
66
getTableColumns,
77
inArray,
8+
InferSelectModel,
89
isNull,
910
or,
1011
sql,
@@ -86,11 +87,17 @@ type Params = {
8687
difficulty?: string;
8788
};
8889

90+
type IGetRandomQuestionResponse = InferSelectModel<typeof QUESTIONS_TABLE> & {
91+
attemptCount: number;
92+
};
93+
8994
// Fetch an unattempted question or fallback to the least attempted one
90-
export const getRandomQuestion = async ({ userId1, userId2, topics, difficulty }: Params) => {
91-
/**
92-
* 1. Both Unattempted
93-
*/
95+
export const getRandomQuestion = async ({
96+
userId1,
97+
userId2,
98+
topics,
99+
difficulty,
100+
}: Params): Promise<IGetRandomQuestionResponse | null> => {
94101
// If an attempt contains either user's ID
95102
const ids = [userId1, userId2];
96103
const userIdClause = [
@@ -103,35 +110,61 @@ export const getRandomQuestion = async ({ userId1, userId2, topics, difficulty }
103110
or(...userIdClause),
104111
];
105112

106-
// Build the filter clause
107-
// - attempt ID null: No attempts
108-
// - topics: If specified, must intersect using Array Intersect
109-
const filterClause = [];
113+
// Try different filter combinations in order of specificity
114+
const filterCombinations = [
115+
// Exact match
116+
topics && difficulty
117+
? [arrayOverlaps(QUESTIONS_TABLE.topic, topics), eq(QUESTIONS_TABLE.difficulty, difficulty)]
118+
: // Topic only
119+
topics
120+
? [arrayOverlaps(QUESTIONS_TABLE.topic, topics)]
121+
: // Difficulty only
122+
difficulty
123+
? [eq(QUESTIONS_TABLE.difficulty, difficulty)]
124+
: // No filters
125+
[],
126+
];
110127

111-
if (topics) {
112-
filterClause.push(arrayOverlaps(QUESTIONS_TABLE.topic, topics));
128+
// Additional combinations if both topic and difficulty are provided
129+
if (topics && difficulty) {
130+
filterCombinations.push(
131+
// Topic only
132+
[arrayOverlaps(QUESTIONS_TABLE.topic, topics)],
133+
// Difficulty only
134+
[eq(QUESTIONS_TABLE.difficulty, difficulty)],
135+
// No filters
136+
[]
137+
);
113138
}
114139

115-
if (difficulty) {
116-
filterClause.push(eq(QUESTIONS_TABLE.difficulty, difficulty));
117-
}
140+
for (const filterClause of filterCombinations) {
141+
// Check if AT LEAST 1 question exists with current filters
142+
const questionCounts = await db
143+
.select({ id: QUESTIONS_TABLE.id })
144+
.from(QUESTIONS_TABLE)
145+
.where(and(...filterClause))
146+
.limit(1);
118147

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);
148+
// No questions exist with the filter.
149+
if (!questionCounts || !questionCounts.length) {
150+
continue;
151+
}
126152

127-
if (bothUnattempted && bothUnattempted.length > 0) {
128-
return bothUnattempted[0].question;
129-
}
153+
// Try to find an unattempted question with current filters
154+
const bothUnattempted = await db
155+
.select({ question: QUESTIONS_TABLE })
156+
.from(QUESTIONS_TABLE)
157+
.leftJoin(QUESTION_ATTEMPTS_TABLE, and(...joinClause))
158+
.where(and(isNull(QUESTION_ATTEMPTS_TABLE.attemptId), ...filterClause))
159+
.orderBy(sql`RANDOM()`)
160+
.limit(1);
130161

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
162+
if (bothUnattempted && bothUnattempted.length > 0) {
163+
return { ...bothUnattempted[0].question, attemptCount: 0 };
164+
}
165+
166+
// If no unattempted question, try least attempted
167+
let nestedQuery = db
135168
.select({
136169
...getTableColumns(QUESTIONS_TABLE),
137170
user1Count:
@@ -145,20 +178,31 @@ export const getRandomQuestion = async ({ userId1, userId2, topics, difficulty }
145178
})
146179
.from(QUESTIONS_TABLE)
147180
.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);
157-
158-
if (result && result.length > 0) {
159-
return { ...result[0], user1Count: undefined, user2Count: undefined };
181+
.$dynamic();
182+
183+
if (filterClause.length) {
184+
nestedQuery = nestedQuery.where(and(...filterClause));
185+
}
186+
187+
nestedQuery = nestedQuery.groupBy(QUESTIONS_TABLE.id);
188+
189+
const attempts = db.$with('at').as(nestedQuery);
190+
191+
const result = await db
192+
.with(attempts)
193+
.select()
194+
.from(attempts)
195+
.orderBy(asc(sql`COALESCE(user1_attempts,0) + COALESCE(user2_attempts,0)`))
196+
.limit(1);
197+
198+
if (result && result.length > 0) {
199+
const { user1Count, user2Count, ...details } = result[0];
200+
const attemptCount =
201+
(user1Count ? (user1Count as number) : 0) + (user2Count ? (user2Count as number) : 0);
202+
return { ...details, attemptCount };
203+
}
160204
}
161205

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

0 commit comments

Comments
 (0)