Skip to content

(SP: 3) [Backend] Redis Cache for Quiz Questions#293

Merged
ViktorSvertoka merged 10 commits intodevelopfrom
sl/feat/quiz
Feb 7, 2026
Merged

(SP: 3) [Backend] Redis Cache for Quiz Questions#293
ViktorSvertoka merged 10 commits intodevelopfrom
sl/feat/quiz

Conversation

@LesiaUKR
Copy link
Collaborator

@LesiaUKR LesiaUKR commented Feb 7, 2026

#Closes #291

Summary

  • Add Redis caching for quiz questions to eliminate 2 DB queries per quiz page load
  • Create shared types/quiz.ts for quiz domain types
  • Integrate cache into getQuizQuestions() with fallback to DB if Redis unavailable

Test plan

  • Open quiz page → verify cache miss, data stored in Redis
  • Refresh quiz page → verify cache hit, no DB queries
  • Test with different locales (uk/en/pl) → each has separate cache key
  • Disable Redis → verify fallback to DB works
  • Complete quiz flow → verify answers validate correctly

Summary by CodeRabbit

  • New Features

    • Structured quiz question/answer model with localized content and locale-aware caching for faster quiz loading.
  • Chores

    • Improved quiz retrieval performance to reduce load times and backend requests.
    • Removed a legacy quiz API route as part of cleanup.
  • Tests

    • Deleted legacy unit tests associated with the removed quiz API route.

ViktorSvertoka and others added 10 commits February 5, 2026 19:03
- Create types/quiz.ts with shared quiz domain types
- Add getOrCreateQuestionsCache() to cache questions per quiz/locale
- Integrate cache into getQuizQuestions() with DB fallback
- Cache empty quiz results to prevent repeated DB queries
- Add try/catch around redis.set() for graceful degradation
- Redis failures no longer break requests when DB data is available
- Remove /api/quiz/[slug] route (unused, exposed isCorrect)
- Remove related test file quiz-slug-route.test.ts
Add error handling to redis.get() calls in:
- getOrCreateQuizAnswersCache
- getCorrectAnswer
- getOrCreateQuestionsCache
@vercel
Copy link
Contributor

vercel bot commented Feb 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
devlovers-net Ready Ready Preview, Comment Feb 7, 2026 9:08pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 7, 2026

📝 Walkthrough

Walkthrough

This PR adds locale-aware Redis caching for quiz questions: new shared types for quiz questions/answers, a getOrCreateQuestionsCache(quizId, locale) function that assembles and caches questions with answers, and integration of that cache into getQuizQuestions() to prefer cached results before querying the DB. It also removes an API route and its tests.

Changes

Cohort / File(s) Summary
Type Definitions
frontend/types/quiz.ts
Added exported interfaces QuizQuestion, QuizAnswer, and QuizQuestionWithAnswers.
Questions & Answers Cache
frontend/lib/quiz/quiz-answers-redis.ts
Added getOrCreateQuestionsCache(quizId, locale), cache key builders (quiz:questions:{quizId}:{locale}, quiz:answers:{quizId}), grouping/assembly of localized questions with answers, and robust Redis read/write try/catch with warning logs.
Cache Integration
frontend/db/queries/quiz.ts
Updated getQuizQuestions() to call getOrCreateQuestionsCache(quizId, locale) and return cached questions when available; removed local question/answer interfaces and re-exported types.
Routes & Tests Removed
frontend/app/api/quiz/[slug]/route.ts, frontend/lib/tests/quiz/quiz-slug-route.test.ts
Deleted the API route file and its corresponding unit tests for the quiz slug route.
Manifest / Dependencies
package.json, manifest_file
Manifest/dependency entries included in the change set.

Sequence Diagram

sequenceDiagram
    participant Client
    participant App as getQuizQuestions()
    participant Cache as getOrCreateQuestionsCache()
    participant Redis
    participant DB

    Client->>App: getQuizQuestions(quizId, locale)
    App->>Cache: getOrCreateQuestionsCache(quizId, locale)
    Cache->>Redis: GET quiz:questions:{quizId}:{locale}
    alt Cache Hit
        Redis-->>Cache: cached questions with answers
        Cache-->>App: return cached data
    else Cache Miss
        Cache->>DB: query localized questions, contents, and answers
        DB-->>Cache: question & answer rows
        Cache->>Cache: assemble QuizQuestionWithAnswers[]
        Cache->>Redis: SET quiz:questions:{quizId}:{locale}
        Cache-->>App: return assembled data
    end
    App-->>Client: quiz questions with answers
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

backend

Suggested reviewers

  • AM1007
  • ViktorSvertoka

Poem

🐰 A hop, a nibble, code in sight,
I cached the questions — tucked them tight.
Locale-aware, swift as a dart,
Answers snug, playing their part.
Hooray — fast quizzes, from my heart! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Deletion of frontend/app/api/quiz/[slug]/route.ts and its test file appear unrelated to the Redis caching objective stated in #291, suggesting these may be scope creep. Clarify whether API route deletion is intentional or should be reverted. If intentional, document the reasoning in the PR description.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title mentions 'Redis Cache for Quiz Questions' but the changes also include frontend API route deletion and test removal, making it partially incomplete in scope description.
Linked Issues check ✅ Passed All required objectives from #291 are met: shared types created, getOrCreateQuestionsCache implemented with proper caching and fallback, cache integrated into getQuizQuestions, and locale-specific keys used.
Description check ✅ Passed PR description clearly explains the Redis caching feature, test plan, and expected performance impact aligned with issue #291.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sl/feat/quiz

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
frontend/lib/quiz/quiz-answers-redis.ts (4)

1-1: Nit: missing space after comma in import.

-import { and, eq,inArray  } from 'drizzle-orm';
+import { and, eq, inArray } from 'drizzle-orm';

80-86: Inconsistent indentation.

Line 85 has extra indentation compared to the rest of the function body.

   try {
     await redis.set(key, cacheData);
   } catch (err) {
     console.warn('Failed to cache quiz answers in Redis', err);
   }
-    return true;
-
+  return true;
 }

162-174: Inconsistent indentation on the if block.

Line 162 has extra leading spaces compared to the surrounding code in this function.

-    if (questionsData.length === 0) {
-    const cacheData: QuizQuestionsCache = {
+  if (questionsData.length === 0) {
+    const cacheData: QuizQuestionsCache = {

223-227: Misaligned await inside try block.

await redis.set(key, cacheData) on line 224 should be indented one level inside the try.

   try {
-  await redis.set(key, cacheData);
+    await redis.set(key, cacheData);
   } catch (e) {

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@LesiaUKR LesiaUKR changed the base branch from main to develop February 7, 2026 19:28
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
frontend/app/layout.tsx (1)

1-7: ⚠️ Potential issue | 🟡 Minor

Duplicate globals.css import.

./globals.css is imported on both Line 1 and Line 7. Remove one of the duplicates.

Proposed fix
 import './globals.css';
 
 import type { Metadata } from 'next';
 import { Geist, Geist_Mono } from 'next/font/google';
 import { Analytics } from '@vercel/analytics/next';
 import { SpeedInsights } from '@vercel/speed-insights/next';
-import './globals.css';
frontend/lib/quiz/quiz-answers-redis.ts (1)

70-76: ⚠️ Potential issue | 🟠 Major

Same unhandled redis.set concern in getOrCreateQuizAnswersCache.

Line 76 has the same issue: if redis.set throws, the caller gets an error even though the answers were successfully queried. The same try/catch pattern should be applied here for resilience.

🛡️ Proposed fix
-  await redis.set(key, cacheData);
-  return true;
+  try {
+    await redis.set(key, cacheData);
+  } catch (err) {
+    console.warn('Failed to cache quiz answers in Redis', err);
+  }
+  return true;
🤖 Fix all issues with AI agents
In `@frontend/lib/quiz/quiz-answers-redis.ts`:
- Around line 145-147: The function currently returns early on an empty
questionsData (if (questionsData.length === 0) return [];) which bypasses Redis
and causes repeated DB hits; instead, write an empty result into Redis before
returning — use the same cache key generation used elsewhere in this module (the
code path that creates the Redis key for this quiz ID) and set an appropriate
TTL (either the normal TTL or a shorter negative-cache TTL) so subsequent calls
hit the cache; update the early-return branch to call the module's Redis write
method (e.g., the existing redis.set/client.set wrapper) with JSON-serialized []
and then return [].
- Around line 188-195: The redis.set call can throw and should not break the
request flow; wrap the await redis.set(key, cacheData) in a try/catch so Redis
failures degrade gracefully: keep the cacheData creation (QuizQuestionsCache)
and successful return path unchanged, but catch any error from redis.set, log it
(using the existing logger if available or console.warn) with context including
key/quizId/locale, and do not rethrow the error so callers still receive the
DB-fetched data.
🧹 Nitpick comments (2)
frontend/types/quiz.ts (1)

1-7: Consider narrowing explanation: any to a more specific type.

Using any for explanation bypasses type checking entirely and can silently propagate unexpected data shapes. If the DB column stores JSON, consider Record<string, unknown> | null or a dedicated interface. If it's always a string, use string | null.

♻️ Suggested type narrowing
 export interface QuizQuestion {
   id: string;
   displayOrder: number;
   difficulty: string | null;
   questionText: string | null;
-  explanation: any;
+  explanation: Record<string, unknown> | string | null;
 }
frontend/lib/quiz/quiz-answers-redis.ts (1)

126-186: Duplicated DB query logic with getQuizQuestions in frontend/db/queries/quiz.ts (lines 187–244).

The questions + answers fetch-and-assemble logic is nearly identical in both locations. If the schema or query shape changes, both copies must be updated in lockstep. Consider extracting a shared helper (e.g., fetchQuestionsFromDb(quizId, locale)) used by both the cache-populating path and the DB fallback.

@netlify
Copy link

netlify bot commented Feb 7, 2026

Deploy Preview for develop-devlovers ready!

Name Link
🔨 Latest commit 1b2abb7
🔍 Latest deploy log https://app.netlify.com/projects/develop-devlovers/deploys/6987a98d116c340008e96014
😎 Deploy Preview https://deploy-preview-293--develop-devlovers.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/lib/quiz/quiz-answers-redis.ts (1)

76-76: ⚠️ Potential issue | 🟠 Major

redis.set not wrapped in try/catch — same risk fixed in getOrCreateQuestionsCache.

The new function at Lines 206-210 correctly wraps redis.set in try/catch (per prior review feedback), but this older call site has the identical risk: a Redis write failure will propagate the exception and break the request despite having valid DB data.

🛡️ Proposed fix
-  await redis.set(key, cacheData);
-  return true;
+  try {
+    await redis.set(key, cacheData);
+  } catch (e) {
+    console.warn('Redis cache write failed:', e);
+  }
+  return true;
🤖 Fix all issues with AI agents
In `@frontend/lib/quiz/quiz-answers-redis.ts`:
- Around line 189-197: The API response currently exposes answer correctness in
frontend/app/api/quiz/[slug]/route.ts where answers are built using
q.answers.map; remove the isCorrect property from that mapping so the returned
objects only include id and text (or answerText) and do not expose correctness
to the client, leaving correctness checks to the verify endpoint
(frontend/app/api/quiz/verify-answer/route.ts); update the mapping in the
handler that constructs answers (the q.answers.map callback) to omit isCorrect.
🧹 Nitpick comments (3)
frontend/lib/quiz/quiz-answers-redis.ts (3)

1-1: Minor formatting: missing space after comma.

eq,inArrayeq, inArray


145-157: Inconsistent indentation in the empty-results block.

Line 145 (if (questionsData.length === 0)) has extra leading whitespace compared to the rest of the function body. This looks like a copy-paste artifact.

🧹 Fix indentation
-    if (questionsData.length === 0) {
-    const cacheData: QuizQuestionsCache = {
-      quizId,
-      locale,
-      questions: [],
-      cachedAt: Date.now(),
-    };
-    try {
-      await redis.set(key, cacheData);
-    } catch (e) {
-      console.warn('Redis cache write failed:', e);
-    }
-    return [];
-  }
+  if (questionsData.length === 0) {
+    const cacheData: QuizQuestionsCache = {
+      quizId,
+      locale,
+      questions: [],
+      cachedAt: Date.now(),
+    };
+    try {
+      await redis.set(key, cacheData);
+    } catch (e) {
+      console.warn('Redis cache write failed:', e);
+    }
+    return [];
+  }

206-210: Indentation inside try block is off.

Line 207 (await redis.set(key, cacheData)) is not indented relative to its enclosing try block.

🧹 Fix indentation
   try {
-  await redis.set(key, cacheData);
+    await redis.set(key, cacheData);
   } catch (e) {

Comment on lines +189 to +197
const questions: QuizQuestionWithAnswers[] = questionsData.map(q => ({
...q,
answers: (answersByQuestion.get(q.id) || []).map(a => ({
id: a.id,
displayOrder: a.displayOrder,
isCorrect: a.isCorrect,
answerText: a.answerText,
})),
}));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how getOrCreateQuestionsCache results are used and whether isCorrect is stripped
rg -n -C10 'getOrCreateQuestionsCache' --type=ts

Repository: DevLoversTeam/devlovers.net

Length of output: 3613


🏁 Script executed:

#!/bin/bash
# Check if isCorrect appears in any API route or page component response
rg -n 'isCorrect' --type=ts -g '!**/quiz-answers-redis.ts'

Repository: DevLoversTeam/devlovers.net

Length of output: 9115


🏁 Script executed:

#!/bin/bash
# Look for getQuizQuestions function in db/queries/quiz.ts
find . -name "quiz.ts" -path "*/db/queries/*" -type f

Repository: DevLoversTeam/devlovers.net

Length of output: 100


🏁 Script executed:

#!/bin/bash
# Check the full getQuizQuestions function implementation
sed -n '179,250p' frontend/db/queries/quiz.ts

Repository: DevLoversTeam/devlovers.net

Length of output: 2197


🏁 Script executed:

#!/bin/bash
# Check the API route at frontend/app/api/quiz/[slug]/route.ts
cat frontend/app/api/quiz/[slug]/route.ts

Repository: DevLoversTeam/devlovers.net

Length of output: 1470


🏁 Script executed:

#!/bin/bash
# Check how QuizQuestion component receives and uses isCorrect
head -100 frontend/components/quiz/QuizQuestion.tsx

Repository: DevLoversTeam/devlovers.net

Length of output: 3468


Remove isCorrect from the quiz API response before sending to the client.

The API route at frontend/app/api/quiz/[slug]/route.ts (line 29) includes isCorrect in the JSON response, exposing all correct answers to the client. Users can inspect the network response to discover correct answers before answering the quiz, defeating the purpose of the quiz.

Location: frontend/app/api/quiz/[slug]/route.ts lines 20-29
answers: q.answers.map(a => ({
  id: a.id,
  text: a.answerText,
  isCorrect: a.isCorrect,
})),

Remove the isCorrect field from the answers mapping in the API response. The isCorrect verification should only be returned by the frontend/app/api/quiz/verify-answer/route.ts endpoint after the user submits an answer, not in the initial questions fetch.

🤖 Prompt for AI Agents
In `@frontend/lib/quiz/quiz-answers-redis.ts` around lines 189 - 197, The API
response currently exposes answer correctness in
frontend/app/api/quiz/[slug]/route.ts where answers are built using
q.answers.map; remove the isCorrect property from that mapping so the returned
objects only include id and text (or answerText) and do not expose correctness
to the client, leaving correctness checks to the verify endpoint
(frontend/app/api/quiz/verify-answer/route.ts); update the mapping in the
handler that constructs answers (the q.answers.map callback) to omit isCorrect.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@frontend/lib/quiz/quiz-answers-redis.ts`:
- Around line 115-129: Wrap the redis.get call in getOrCreateQuestionsCache in a
try/catch so any exception (deserialization, timeout, etc.) is swallowed and the
function returns null (preserving the graceful-fallback contract); specifically,
catch errors around the call to redis.get<QuizQuestionsCache>(key) inside
getOrCreateQuestionsCache and on error log/debug as needed but return null.
Apply the same pattern to the redis.get usages in getOrCreateQuizAnswersCache
and getCorrectAnswer so those functions also return null on redis.get failures
instead of letting exceptions propagate.
🧹 Nitpick comments (3)
frontend/lib/quiz/quiz-answers-redis.ts (3)

1-1: Nit: missing space after comma.

-import { and, eq,inArray  } from 'drizzle-orm';
+import { and, eq, inArray } from 'drizzle-orm';

150-163: Inconsistent indentation inside the empty-result branch.

Line 150 has 4 spaces of leading indentation for the if statement, while the rest of the function body uses 2 spaces. Similarly, the try/catch block within (lines 157–161) is at the correct level but the if line itself is off. This likely happened during a fixup commit.

-    if (questionsData.length === 0) {
+  if (questionsData.length === 0) {

211-215: await redis.set is under-indented inside the try block.

Line 212 should be indented one level deeper than the try on line 211.

   try {
-  await redis.set(key, cacheData);
+    await redis.set(key, cacheData);
   } catch (e) {

@ViktorSvertoka ViktorSvertoka merged commit 8a4c0d4 into develop Feb 7, 2026
9 of 10 checks passed
@ViktorSvertoka ViktorSvertoka deleted the sl/feat/quiz branch February 7, 2026 21:11
@LesiaUKR LesiaUKR restored the sl/feat/quiz branch February 8, 2026 16:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

(SP: 1) [Backend] Redis Cache for Quiz Questions

2 participants