From 472831ee8f9d3166a5b630537625dcc28434b418 Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Tue, 24 Jun 2025 00:27:51 +0200 Subject: [PATCH 1/6] Add endpoint to get enrolled and unenrolled courses --- routes/courses.js | 55 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/routes/courses.js b/routes/courses.js index ec17ff0..e52fca4 100644 --- a/routes/courses.js +++ b/routes/courses.js @@ -715,4 +715,59 @@ router.put("/update-module", upload.single("file"), async (req, res) => { client.release(); } }); + +router.get("/all-user-courses", async (req, res) => { + const { auth } = req.cookies; + if (!auth) return res.status(401).json({ message: "Not authenticated" }); + + let session; + try { + session = JSON.parse(auth); + } catch { + return res.status(400).json({ message: "Invalid session data" }); + } + + const userId = session.userId; + const organisationId = session.organisation?.id; + if (!organisationId) { + return res.status(400).json({ message: "Organisation context missing" }); + } + + const client = await pool.connect(); + try { + await client.query("BEGIN"); + + // 1) Courses the user is enrolled in + const enrolledRes = await client.query( + `SELECT c.id, c.name, c.description + FROM courses c + JOIN enrollments e ON e.course_id = c.id + WHERE e.user_id = $1`, + [userId] + ); + + // 2) All other courses in the same org that they're NOT enrolled in + const otherRes = await client.query( + `SELECT c.id, c.name, c.description + FROM courses c + WHERE c.organisation_id = $1 + AND c.id NOT IN ( + SELECT course_id FROM enrollments WHERE user_id = $2 + )`, + [organisationId, userId] + ); + + await client.query("COMMIT"); + return res.status(200).json({ + enrolled: enrolledRes.rows, + other: otherRes.rows, + }); + } catch (err) { + await client.query("ROLLBACK"); + console.error("Error in all-user-courses:", err); + return res.status(500).json({ message: "Server error" }); + } finally { + client.release(); + } +}); module.exports = router; From cbe6d82f9420dde7b17d7860eadfae9d14fddc63 Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Tue, 24 Jun 2025 00:32:57 +0200 Subject: [PATCH 2/6] Add endpoint to enroll user --- routes/courses.js | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/routes/courses.js b/routes/courses.js index e52fca4..6b0cbc8 100644 --- a/routes/courses.js +++ b/routes/courses.js @@ -770,4 +770,52 @@ router.get("/all-user-courses", async (req, res) => { client.release(); } }); + +router.post("/enroll-course", async (req, res) => { + const { auth } = req.cookies; + if (!auth) return res.status(401).json({ message: "Not authenticated" }); + + let session; + try { + session = JSON.parse(auth); + } catch { + return res.status(400).json({ message: "Invalid session data" }); + } + + const userId = session.userId; + const { courseId } = req.body; + if (!courseId) { + return res.status(400).json({ message: "courseId is required" }); + } + + const client = await pool.connect(); + try { + await client.query("BEGIN"); + + const insertRes = await client.query( + `INSERT INTO enrollments (user_id, course_id) + VALUES ($1, $2) + RETURNING id, status, started_at`, + [userId, courseId] + ); + + await client.query("COMMIT"); + return res.status(201).json({ + success: true, + enrollment: insertRes.rows[0], + }); + } catch (err) { + await client.query("ROLLBACK"); + // if the user is already enrolled, unique constraint violation + if (err.code === "23505") { + return res + .status(400) + .json({ message: "Already enrolled in this course" }); + } + console.error("Error enrolling user:", err); + return res.status(500).json({ message: "Server error" }); + } finally { + client.release(); + } +}); module.exports = router; From ff06bd27af92eff8faa2c95e980f95b47a66ae88 Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Tue, 24 Jun 2025 00:35:44 +0200 Subject: [PATCH 3/6] Add unenrolled endpoint --- routes/courses.js | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/routes/courses.js b/routes/courses.js index 6b0cbc8..313b730 100644 --- a/routes/courses.js +++ b/routes/courses.js @@ -818,4 +818,49 @@ router.post("/enroll-course", async (req, res) => { client.release(); } }); + +router.post("/unenroll-course", async (req, res) => { + const { auth } = req.cookies; + if (!auth) return res.status(401).json({ message: "Not authenticated" }); + + let session; + try { + session = JSON.parse(auth); + } catch { + return res.status(400).json({ message: "Invalid session data" }); + } + + const userId = session.userId; + const { courseId } = req.body; + if (!courseId) { + return res.status(400).json({ message: "courseId is required" }); + } + + const client = await pool.connect(); + try { + await client.query("BEGIN"); + + const delRes = await client.query( + `DELETE FROM enrollments + WHERE user_id = $1 + AND course_id = $2 + RETURNING id`, + [userId, courseId] + ); + + if (!delRes.rows.length) { + await client.query("ROLLBACK"); + return res.status(404).json({ message: "Not enrolled in this course" }); + } + + await client.query("COMMIT"); + return res.status(200).json({ success: true }); + } catch (err) { + await client.query("ROLLBACK"); + console.error("Error unenrolling user:", err); + return res.status(500).json({ message: "Server error" }); + } finally { + client.release(); + } +}); module.exports = router; From 1b9c288ea55c520eed09bf624361ded0147e90a4 Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Tue, 24 Jun 2025 00:46:40 +0200 Subject: [PATCH 4/6] Add endpoint to check for enrollment --- routes/courses.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/routes/courses.js b/routes/courses.js index 313b730..23913a8 100644 --- a/routes/courses.js +++ b/routes/courses.js @@ -863,4 +863,46 @@ router.post("/unenroll-course", async (req, res) => { client.release(); } }); + +router.post("/is-enrolled", async (req, res) => { + const { auth } = req.cookies; + if (!auth) return res.status(401).json({ message: "Not authenticated" }); + + let session; + try { + session = JSON.parse(auth); + } catch { + return res.status(400).json({ message: "Invalid session data" }); + } + + const userId = session.userId; + const { courseId } = req.body; + if (!courseId) { + return res.status(400).json({ message: "courseId is required" }); + } + + const client = await pool.connect(); + try { + await client.query("BEGIN"); + + const enrollRes = await client.query( + `SELECT 1 FROM enrollments + WHERE user_id = $1 + AND course_id = $2 + LIMIT 1`, + [userId, courseId] + ); + + await client.query("COMMIT"); + return res.status(200).json({ + enrolled: enrollRes.rows.length > 0, + }); + } catch (err) { + await client.query("ROLLBACK"); + console.error("Error checking enrollment:", err); + return res.status(500).json({ message: "Server error" }); + } finally { + client.release(); + } +}); module.exports = router; From b7ef3e41e56bb07affd713c62cd2821594e7cc24 Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Tue, 24 Jun 2025 01:04:17 +0200 Subject: [PATCH 5/6] send id for q and opt to ensure unique key prop --- routes/courses.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/routes/courses.js b/routes/courses.js index 23913a8..7f896d8 100644 --- a/routes/courses.js +++ b/routes/courses.js @@ -499,7 +499,7 @@ router.post("/get-module", async (req, res) => { let optionsRes = { rows: [] }; if (questionIds.length) { optionsRes = await client.query( - `SELECT question_id, option_text, is_correct + `SELECT id, question_id, option_text, is_correct FROM question_options WHERE question_id = ANY($1)`, [questionIds] @@ -507,11 +507,13 @@ router.post("/get-module", async (req, res) => { } const questions = questionsRes.rows.map((q) => ({ + id: q.id, question_text: q.question_text, question_type: q.question_type, options: optionsRes.rows .filter((opt) => opt.question_id === q.id) .map((opt) => ({ + id: opt.id, option_text: opt.option_text, is_correct: opt.is_correct, })), From ddf771739f1bcf530ff2670a51259ebabd257360 Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Tue, 24 Jun 2025 01:09:50 +0200 Subject: [PATCH 6/6] Admin is considered enrolled in check enrollment --- routes/courses.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/routes/courses.js b/routes/courses.js index 7f896d8..9995d5f 100644 --- a/routes/courses.js +++ b/routes/courses.js @@ -878,6 +878,14 @@ router.post("/is-enrolled", async (req, res) => { } const userId = session.userId; + const isOrganisationAdmin = session.organisation?.role === "admin"; + + if (isOrganisationAdmin) { + return res.status(200).json({ + enrolled: true, + }); + } + const { courseId } = req.body; if (!courseId) { return res.status(400).json({ message: "courseId is required" });