1- import { and , eq } from 'drizzle-orm' ;
1+ import { and , eq , inArray } from 'drizzle-orm' ;
22
33import { db } from '@/db' ;
4- import { quizAnswers , quizQuestions } from '@/db/schema/quiz' ;
4+ import {
5+ quizAnswers ,
6+ quizAnswerTranslations ,
7+ quizQuestionContent ,
8+ quizQuestions } from '@/db/schema/quiz' ;
59import { getRedisClient } from '@/lib/redis' ;
10+ import type { QuizQuestionWithAnswers } from '@/types/quiz' ;
611
712interface QuizAnswersCache {
813 quizId : string ;
914 answers : Record < string , string > ;
1015 cachedAt : number ;
1116}
1217
13- function getCacheKey ( quizId : string ) : string {
18+ interface QuizQuestionsCache {
19+ quizId : string ;
20+ locale : string ;
21+ questions : QuizQuestionWithAnswers [ ] ;
22+ cachedAt : number ;
23+ }
24+
25+ function getQuestionsCacheKey ( quizId : string , locale : string ) : string {
26+ return `quiz:questions:${ quizId } :${ locale } ` ;
27+ }
28+
29+
30+ function getAnswersCacheKey ( quizId : string ) : string {
1431 return `quiz:answers:${ quizId } ` ;
1532}
1633
@@ -23,11 +40,15 @@ export async function getOrCreateQuizAnswersCache(
2340 return true ;
2441 }
2542
26- const key = getCacheKey ( quizId ) ;
43+ const key = getAnswersCacheKey ( quizId ) ;
2744
28- const existing = await redis . get < QuizAnswersCache > ( key ) ;
29- if ( existing ) {
30- return true ;
45+ try {
46+ const existing = await redis . get < QuizAnswersCache > ( key ) ;
47+ if ( existing ) {
48+ return true ;
49+ }
50+ } catch ( err ) {
51+ console . warn ( 'Redis cache read failed:' , err ) ;
3152 }
3253
3354 const correctAnswers = await db
@@ -56,8 +77,13 @@ export async function getOrCreateQuizAnswersCache(
5677 cachedAt : Date . now ( ) ,
5778 } ;
5879
59- await redis . set ( key , cacheData ) ;
60- return true ;
80+ try {
81+ await redis . set ( key , cacheData ) ;
82+ } catch ( err ) {
83+ console . warn ( 'Failed to cache quiz answers in Redis' , err ) ;
84+ }
85+ return true ;
86+
6187}
6288
6389export async function getCorrectAnswer (
@@ -67,10 +93,14 @@ export async function getCorrectAnswer(
6793 const redis = getRedisClient ( ) ;
6894
6995 if ( redis ) {
70- const key = getCacheKey ( quizId ) ;
71- const cache = await redis . get < QuizAnswersCache > ( key ) ;
72- if ( cache ) {
73- return cache . answers [ questionId ] ?? null ;
96+ try {
97+ const key = getAnswersCacheKey ( quizId ) ;
98+ const cache = await redis . get < QuizAnswersCache > ( key ) ;
99+ if ( cache ) {
100+ return cache . answers [ questionId ] ?? null ;
101+ }
102+ } catch ( err ) {
103+ console . warn ( 'Redis cache read failed, falling back to DB:' , err ) ;
74104 }
75105 }
76106
@@ -89,3 +119,112 @@ export async function getCorrectAnswer(
89119
90120 return result [ 0 ] ?. answerId ?? null ;
91121}
122+
123+ export async function getOrCreateQuestionsCache (
124+ quizId : string ,
125+ locale : string
126+ ) : Promise < QuizQuestionWithAnswers [ ] | null > {
127+ const redis = getRedisClient ( ) ;
128+ if ( ! redis ) {
129+ return null ;
130+ }
131+
132+ const key = getQuestionsCacheKey ( quizId , locale ) ;
133+
134+ try {
135+ const existing = await redis . get < QuizQuestionsCache > ( key ) ;
136+ if ( existing ) {
137+ return existing . questions ;
138+ }
139+ } catch ( err ) {
140+ console . warn ( 'Redis cache read failed, falling back to DB:' , err ) ;
141+ }
142+
143+ const questionsData = await db
144+ . select ( {
145+ id : quizQuestions . id ,
146+ displayOrder : quizQuestions . displayOrder ,
147+ difficulty : quizQuestions . difficulty ,
148+ questionText : quizQuestionContent . questionText ,
149+ explanation : quizQuestionContent . explanation ,
150+ } )
151+ . from ( quizQuestions )
152+ . leftJoin (
153+ quizQuestionContent ,
154+ and (
155+ eq ( quizQuestionContent . quizQuestionId , quizQuestions . id ) ,
156+ eq ( quizQuestionContent . locale , locale )
157+ )
158+ )
159+ . where ( eq ( quizQuestions . quizId , quizId ) )
160+ . orderBy ( quizQuestions . displayOrder ) ;
161+
162+ if ( questionsData . length === 0 ) {
163+ const cacheData : QuizQuestionsCache = {
164+ quizId,
165+ locale,
166+ questions : [ ] ,
167+ cachedAt : Date . now ( ) ,
168+ } ;
169+ try {
170+ await redis . set ( key , cacheData ) ;
171+ } catch ( e ) {
172+ console . warn ( 'Redis cache write failed:' , e ) ;
173+ }
174+ return [ ] ;
175+ }
176+
177+ const questionIds = questionsData . map ( q => q . id ) ;
178+
179+ const allAnswers = await db
180+ . select ( {
181+ id : quizAnswers . id ,
182+ questionId : quizAnswers . quizQuestionId ,
183+ displayOrder : quizAnswers . displayOrder ,
184+ isCorrect : quizAnswers . isCorrect ,
185+ answerText : quizAnswerTranslations . answerText ,
186+ } )
187+ . from ( quizAnswers )
188+ . leftJoin (
189+ quizAnswerTranslations ,
190+ and (
191+ eq ( quizAnswerTranslations . quizAnswerId , quizAnswers . id ) ,
192+ eq ( quizAnswerTranslations . locale , locale )
193+ )
194+ )
195+ . where ( inArray ( quizAnswers . quizQuestionId , questionIds ) )
196+ . orderBy ( quizAnswers . displayOrder ) ;
197+
198+ const answersByQuestion = new Map < string , typeof allAnswers > ( ) ;
199+ for ( const answer of allAnswers ) {
200+ const arr = answersByQuestion . get ( answer . questionId ) || [ ] ;
201+ arr . push ( answer ) ;
202+ answersByQuestion . set ( answer . questionId , arr ) ;
203+ }
204+
205+
206+ const questions : QuizQuestionWithAnswers [ ] = questionsData . map ( q => ( {
207+ ...q ,
208+ answers : ( answersByQuestion . get ( q . id ) || [ ] ) . map ( a => ( {
209+ id : a . id ,
210+ displayOrder : a . displayOrder ,
211+ isCorrect : a . isCorrect ,
212+ answerText : a . answerText ,
213+ } ) ) ,
214+ } ) ) ;
215+
216+ const cacheData : QuizQuestionsCache = {
217+ quizId,
218+ locale,
219+ questions,
220+ cachedAt : Date . now ( ) ,
221+ } ;
222+
223+ try {
224+ await redis . set ( key , cacheData ) ;
225+ } catch ( e ) {
226+ console . warn ( 'Redis cache write failed:' , e ) ;
227+ }
228+
229+ return questions ;
230+ }
0 commit comments