@@ -88,9 +88,6 @@ type Params = {
8888
8989// Fetch an unattempted question or fallback to the least attempted one
9090export 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