From a6bc688d7e4596d80a1a8caf2408b2fb3271f19c Mon Sep 17 00:00:00 2001 From: atul-upadhyay-7 Date: Sun, 7 Jun 2026 18:09:08 +0530 Subject: [PATCH] Fix flashcard generation and progress API routes (fixes #449) --- rag-service/main.py | 6 +++-- server.js | 46 ++++++++++++++++++++++++++++++++++++ server.test.js | 57 +++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 105 insertions(+), 4 deletions(-) diff --git a/rag-service/main.py b/rag-service/main.py index d3b9906..9ebe17e 100644 --- a/rag-service/main.py +++ b/rag-service/main.py @@ -274,6 +274,8 @@ def update_processing_progress(session_id, stage, progress): "/validate-session-write", "/sessions/lookup", "/demo-query-validation", + "/sessions/flashcards", + "/sessions/flashcards/progress", } PROTECTED_RAG_PREFIXES = ( "/ask/", @@ -4473,7 +4475,7 @@ def generate_flashcards_from_text(indexed_docs, count): return cards -@app.post("/sessions/flashcards/generate") +@app.post("/sessions/flashcards") def generate_flashcards(data: FlashcardGenerateRequest): cleanup_expired_sessions() session_id = str(data.session_id) @@ -4517,7 +4519,7 @@ def generate_flashcards(data: FlashcardGenerateRequest): return {"flashcards": cards} -@app.post("/sessions/flashcards/update-progress") +@app.post("/sessions/flashcards/progress") def update_flashcard_progress(data: FlashcardProgressRequest): cleanup_expired_sessions() session_id = str(data.session_id) diff --git a/server.js b/server.js index 6220c17..f5ad04c 100644 --- a/server.js +++ b/server.js @@ -21,6 +21,8 @@ const { sessionsLookupSchema, knowledgeGapsSchema, MAX_QUESTION_LENGTH, + generateFlashcardsSchema, + updateFlashcardProgressSchema, } = require("./validators/schemas"); const { clientIpFromRequest } = require("./security/ip"); const { createRedisClient } = require("./security/redis"); @@ -1337,6 +1339,50 @@ app.post("/sessions/lookup", async (req, res) => { } }); +app.post("/sessions/flashcards", async (req, res) => { + const validation = generateFlashcardsSchema.safeParse(req.body); + + if (!validation.success) { + return res.status(400).json({ + error: "Validation failed", + details: validation.error.flatten(), + }); + } + + try { + const response = await axios.post( + `${RAG_SERVICE_URL}/sessions/flashcards`, + validation.data, + { headers: ragAuthHeaders(), timeout: 60000 }, + ); + return res.json(response.data); + } catch (err) { + return propagateRagError(err, res, "Failed to generate flashcards"); + } +}); + +app.post("/sessions/flashcards/progress", async (req, res) => { + const validation = updateFlashcardProgressSchema.safeParse(req.body); + + if (!validation.success) { + return res.status(400).json({ + error: "Validation failed", + details: validation.error.flatten(), + }); + } + + try { + const response = await axios.post( + `${RAG_SERVICE_URL}/sessions/flashcards/progress`, + validation.data, + { headers: ragAuthHeaders(), timeout: 10000 }, + ); + return res.json(response.data); + } catch (err) { + return propagateRagError(err, res, "Failed to update flashcard progress"); + } +}); + app.get("/health", (req, res) => { res.json({ status: "ok" }); }); diff --git a/server.test.js b/server.test.js index 7181e53..84d13c1 100644 --- a/server.test.js +++ b/server.test.js @@ -517,7 +517,7 @@ describe("route error responses", () => { method: "POST", headers: { "Content-Type": "application/json", - Authorization: "Bearer test-token", + Authorization: `Bearer ${jwt.sign({ role: "authenticated" }, process.env.SUPABASE_JWT_SECRET)}`, }, body: JSON.stringify({ url: "https://xyz.supabase.co//evil.com/file.pdf?download=1", @@ -560,7 +560,7 @@ describe("route error responses", () => { method: "POST", headers: { "Content-Type": "application/json", - Authorization: "Bearer test-token", + Authorization: `Bearer ${jwt.sign({ role: "authenticated" }, process.env.SUPABASE_JWT_SECRET)}`, }, body: JSON.stringify({ url: " https://xyz.supabase.co/storage/v1/object/public/docs/trimmed.pdf ", @@ -845,6 +845,59 @@ describe("route error responses", () => { } }); + test("POST /sessions/flashcards generates flashcards", async () => { + const originalPost = axios.post; + let forwardedHeaders = null; + + axios.post = async (url, body, options) => { + forwardedHeaders = options?.headers; + return { data: { flashcards: [] } }; + }; + + try { + const res = await fetch(`${baseUrl}/sessions/flashcards`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + session_id: "550e8400-e29b-41d4-a716-446655440000", + session_secret: "secret-abc", + count: 5, + }), + }); + assert.equal(res.status, 200); + assert.equal(forwardedHeaders["X-Internal-Token"], process.env.INTERNAL_RAG_TOKEN); + } finally { + axios.post = originalPost; + } + }); + + test("POST /sessions/flashcards/progress updates progress", async () => { + const originalPost = axios.post; + let forwardedHeaders = null; + + axios.post = async (url, body, options) => { + forwardedHeaders = options?.headers; + return { data: { success: true } }; + }; + + try { + const res = await fetch(`${baseUrl}/sessions/flashcards/progress`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + session_id: "550e8400-e29b-41d4-a716-446655440000", + session_secret: "secret-abc", + card_id: "card-1", + rating: "good", + }), + }); + assert.equal(res.status, 200); + assert.equal(forwardedHeaders["X-Internal-Token"], process.env.INTERNAL_RAG_TOKEN); + } finally { + axios.post = originalPost; + } + }); + test("GET unknown route returns 404", async () => { const res = await fetch(`${baseUrl}/nonexistent`, { method: "GET",