From 126951f4e1711d3add21fe847786ccab5aa3b7da Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Thu, 10 Jul 2025 17:21:56 +0800 Subject: [PATCH 1/4] Add working admin dashboard endpoint --- routes/dashboard.js | 182 ++++++++++++++++++++++++++++++++++++++++++++ server.js | 2 + 2 files changed, 184 insertions(+) create mode 100644 routes/dashboard.js diff --git a/routes/dashboard.js b/routes/dashboard.js new file mode 100644 index 0000000..4353962 --- /dev/null +++ b/routes/dashboard.js @@ -0,0 +1,182 @@ +const express = require("express"); +const pool = require("../database/db"); +const router = express.Router(); + +function getAuthUser(req) { + const { auth } = req.cookies; + if (!auth) return null; + try { + return JSON.parse(auth); + } catch { + return null; + } +} + +router.get("/user-dashboard", async (req, res) => { + const user = getAuthUser(req); + if (!user || !user.isLoggedIn) + return res.status(401).json({ message: "Not logged in" }); + + const client = await pool.connect(); + try { + await client.query("BEGIN"); + const userId = user.userId; + + const { rows: currCourse } = await client.query( + `SELECT c.id, c.name + FROM enrollments e + JOIN courses c ON c.id = e.course_id + WHERE e.user_id = $1 + AND e.status = 'enrolled' + ORDER BY e.started_at DESC + LIMIT 1`, + [userId] + ); + + let currModule = null; + if (currCourse.length) { + const courseId = currCourse[0].id; + const { rows } = await client.query( + `SELECT m.id, m.title + FROM module_status ms + JOIN modules m ON m.id = ms.module_id + WHERE ms.enrollment_id = ( + SELECT id FROM enrollments + WHERE user_id = $1 + AND course_id = $2 + ) + AND ms.status = 'in_progress' + ORDER BY ms.started_at DESC + LIMIT 1`, + [userId, courseId] + ); + currModule = rows[0] || null; + } + + const { rows: rm } = await client.query( + `SELECT id FROM roadmaps + WHERE user_id = $1 + ORDER BY id DESC + LIMIT 1`, + [userId] + ); + let roadmapProgress = { completed: 0, total: 0 }; + if (rm.length) { + const roadmapId = rm[0].id; + const { rows } = await client.query( + `SELECT + COUNT(*) FILTER(WHERE ri.module_id IS NOT NULL) AS total, + COUNT(*) FILTER(WHERE ms.status = 'completed') AS completed + FROM roadmap_items ri + LEFT JOIN module_status ms + ON ms.module_id = ri.module_id + AND ms.enrollment_id = ( + SELECT id FROM enrollments + WHERE user_id = $1 + AND course_id = ( + SELECT course_id FROM modules WHERE id = ri.module_id + ) + ) + WHERE ri.roadmap_id = $2`, + [userId, roadmapId] + ); + roadmapProgress = rows[0]; + } + + let courseProgress = { completed: 0, total: 0 }; + if (currCourse.length) { + const courseId = currCourse[0].id; + const { rows } = await client.query( + `SELECT + COUNT(m.id) AS total, + COUNT(ms.id) FILTER(ms.status = 'completed') AS completed + FROM modules m + LEFT JOIN module_status ms + ON ms.module_id = m.id + AND ms.enrollment_id = ( + SELECT id FROM enrollments + WHERE user_id = $1 + AND course_id = $2 + ) + WHERE m.course_id = $2`, + [userId, currCourse[0].id] + ); + courseProgress = rows[0]; + } + + await client.query("COMMIT"); + res.json({ + welcome: `Welcome, ${user.firstname}!`, + currentCourse: currCourse[0] || null, + currentModule: currModule, + roadmapProgress, + courseProgress, + }); + } catch (err) { + await client.query("ROLLBACK"); + console.error(err); + res.status(500).json({ message: "Server error" }); + } finally { + client.release(); + } +}); + +router.get("/admin-dashboard", async (req, res) => { + const user = getAuthUser(req); + if (!user || !user.isLoggedIn || user.organisation.role !== "admin") { + return res.status(403).json({ message: "Forbidden" }); + } + + const client = await pool.connect(); + try { + await client.query("BEGIN"); + const orgId = user.organisation.id; + + const { rows: employees } = await client.query( + ` + SELECT + u.id, + u.firstname, + u.lastname, + COUNT(e.id) FILTER (WHERE e.status IN ('enrolled','completed')) AS "totalCourses", + COUNT(e.id) FILTER (WHERE e.status = 'completed') AS "completedCourses" + FROM users u + JOIN organisation_users ou + ON ou.user_id = u.id + LEFT JOIN enrollments e + ON e.user_id = u.id + WHERE ou.organisation_id = $1 + AND ou.role = 'employee' + GROUP BY u.id, u.firstname, u.lastname + ORDER BY u.lastname, u.firstname + `, + [orgId] + ); + + const { rows: enrollments } = await client.query( + `SELECT c.name AS courseName, + COUNT(e.id) AS enrolledCount + FROM courses c + LEFT JOIN enrollments e ON e.course_id = c.id + WHERE c.organisation_id = $1 + GROUP BY c.id, c.name + ORDER BY enrolledCount DESC`, + [orgId] + ); + + await client.query("COMMIT"); + res.json({ + welcome: `Welcome, Admin ${user.firstname}!`, + employees, + enrollments, + }); + } catch (err) { + await client.query("ROLLBACK"); + console.error(err); + res.status(500).json({ message: "Server error" }); + } finally { + client.release(); + } +}); + +module.exports = router; diff --git a/server.js b/server.js index f01d7af..8dd8b42 100644 --- a/server.js +++ b/server.js @@ -9,6 +9,7 @@ const onboardingRoutes = require("./routes/onboarding"); const roadmapRoutes = require("./routes/roadmaps"); const materialRoutes = require("./routes/materials"); const activityRoutes = require("./routes/activity"); +const dashboardRoutes = require("./routes/dashboard"); const pool = require("./database/db"); const app = express(); const PORT = process.env.PORT || 4000; @@ -52,6 +53,7 @@ app.use("/api/onboarding", onboardingRoutes); app.use("/api/roadmaps", roadmapRoutes); app.use("/api/materials", materialRoutes); app.use("/api/activity", activityRoutes); +app.use("/api/dashboard", dashboardRoutes); app.listen(PORT, () => { console.log(`Server running on http://localhost:${PORT}`); From 7bb5b230c5f10b41227a8e568fe163c5f74f9e91 Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Thu, 10 Jul 2025 17:47:10 +0800 Subject: [PATCH 2/4] ass working user dashboard endpoint --- routes/dashboard.js | 134 +++++++++++++++++++++++--------------------- 1 file changed, 70 insertions(+), 64 deletions(-) diff --git a/routes/dashboard.js b/routes/dashboard.js index 4353962..eaf9225 100644 --- a/routes/dashboard.js +++ b/routes/dashboard.js @@ -14,103 +14,109 @@ function getAuthUser(req) { router.get("/user-dashboard", async (req, res) => { const user = getAuthUser(req); - if (!user || !user.isLoggedIn) + if (!user || !user.isLoggedIn) { return res.status(401).json({ message: "Not logged in" }); + } const client = await pool.connect(); try { await client.query("BEGIN"); const userId = user.userId; - const { rows: currCourse } = await client.query( + const { rows: currCourseArr } = await client.query( `SELECT c.id, c.name FROM enrollments e - JOIN courses c ON c.id = e.course_id + JOIN courses c ON c.id = e.course_id WHERE e.user_id = $1 - AND e.status = 'enrolled' - ORDER BY e.started_at DESC + AND e.status IN ('enrolled', 'in_progress') + ORDER BY e.started_at DESC NULLS LAST LIMIT 1`, [userId] ); + const currentCourse = currCourseArr[0] || null; - let currModule = null; - if (currCourse.length) { - const courseId = currCourse[0].id; - const { rows } = await client.query( + let currentModule = null; + if (currentCourse) { + const { rows: moduleArr } = await client.query( `SELECT m.id, m.title - FROM module_status ms - JOIN modules m ON m.id = ms.module_id + FROM modules m + JOIN module_status ms ON ms.module_id = m.id WHERE ms.enrollment_id = ( SELECT id FROM enrollments - WHERE user_id = $1 - AND course_id = $2 - ) + WHERE user_id = $1 AND course_id = $2 + LIMIT 1 + ) AND ms.status = 'in_progress' - ORDER BY ms.started_at DESC + ORDER BY ms.started_at DESC NULLS LAST LIMIT 1`, - [userId, courseId] + [userId, currentCourse.id] ); - currModule = rows[0] || null; + currentModule = moduleArr[0] || null; } - const { rows: rm } = await client.query( - `SELECT id FROM roadmaps - WHERE user_id = $1 - ORDER BY id DESC - LIMIT 1`, - [userId] - ); - let roadmapProgress = { completed: 0, total: 0 }; - if (rm.length) { - const roadmapId = rm[0].id; - const { rows } = await client.query( - `SELECT - COUNT(*) FILTER(WHERE ri.module_id IS NOT NULL) AS total, - COUNT(*) FILTER(WHERE ms.status = 'completed') AS completed - FROM roadmap_items ri - LEFT JOIN module_status ms - ON ms.module_id = ri.module_id - AND ms.enrollment_id = ( - SELECT id FROM enrollments - WHERE user_id = $1 - AND course_id = ( - SELECT course_id FROM modules WHERE id = ri.module_id - ) - ) - WHERE ri.roadmap_id = $2`, - [userId, roadmapId] + let nextToLearn = []; + if (currentCourse) { + const { rows: learnArr } = await client.query( + `SELECT m.id, m.title + FROM modules m + LEFT JOIN module_status ms + ON ms.module_id = m.id + AND ms.enrollment_id = ( + SELECT id FROM enrollments WHERE user_id = $1 AND course_id = $2 LIMIT 1 + ) + WHERE m.course_id = $2 + AND (ms.status IS NULL OR ms.status = 'not_started') + ORDER BY m.position ASC + LIMIT 2`, + [userId, currentCourse.id] + ); + nextToLearn = learnArr; + } + + let toRevise = []; + if (currentCourse) { + const { rows: reviseArr } = await client.query( + `SELECT m.id, m.title + FROM modules m + JOIN module_status ms + ON ms.module_id = m.id + AND ms.enrollment_id = ( + SELECT id FROM enrollments WHERE user_id = $1 AND course_id = $2 LIMIT 1 + ) + WHERE ms.status = 'completed' + ORDER BY ms.completed_at DESC NULLS LAST + LIMIT 1`, + [userId, currentCourse.id] ); - roadmapProgress = rows[0]; + toRevise = reviseArr; } - let courseProgress = { completed: 0, total: 0 }; - if (currCourse.length) { - const courseId = currCourse[0].id; + let summaryStats = { completedModules: 0, totalModules: 0 }; + if (currentCourse) { const { rows } = await client.query( `SELECT - COUNT(m.id) AS total, - COUNT(ms.id) FILTER(ms.status = 'completed') AS completed - FROM modules m - LEFT JOIN module_status ms - ON ms.module_id = m.id - AND ms.enrollment_id = ( - SELECT id FROM enrollments - WHERE user_id = $1 - AND course_id = $2 - ) - WHERE m.course_id = $2`, - [userId, currCourse[0].id] + COUNT(m.id) AS "totalModules", + COUNT(ms.id) FILTER (WHERE ms.status = 'completed') AS "completedModules" + FROM modules m + LEFT JOIN module_status ms + ON ms.module_id = m.id + AND ms.enrollment_id = ( + SELECT id FROM enrollments WHERE user_id = $1 AND course_id = $2 LIMIT 1 + ) + WHERE m.course_id = $2`, + [userId, currentCourse.id] ); - courseProgress = rows[0]; + summaryStats = rows[0]; } await client.query("COMMIT"); res.json({ welcome: `Welcome, ${user.firstname}!`, - currentCourse: currCourse[0] || null, - currentModule: currModule, - roadmapProgress, - courseProgress, + currentCourse, + currentModule, + nextToLearn, + toRevise, + summaryStats, }); } catch (err) { await client.query("ROLLBACK"); From ac3bfc869ef016ba7d198e1a3797c50efa46ee94 Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Thu, 10 Jul 2025 17:48:51 +0800 Subject: [PATCH 3/4] Update latest code logic --- routes/dashboard.js | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/routes/dashboard.js b/routes/dashboard.js index eaf9225..0dbd0fc 100644 --- a/routes/dashboard.js +++ b/routes/dashboard.js @@ -24,13 +24,23 @@ router.get("/user-dashboard", async (req, res) => { const userId = user.userId; const { rows: currCourseArr } = await client.query( - `SELECT c.id, c.name - FROM enrollments e - JOIN courses c ON c.id = e.course_id - WHERE e.user_id = $1 - AND e.status IN ('enrolled', 'in_progress') - ORDER BY e.started_at DESC NULLS LAST - LIMIT 1`, + ` + SELECT c.id, c.name + FROM enrollments e + JOIN courses c ON c.id = e.course_id + WHERE e.user_id = $1 + AND e.status IN ('enrolled', 'in_progress') + AND EXISTS ( + SELECT 1 FROM modules m + LEFT JOIN module_status ms + ON ms.module_id = m.id + AND ms.enrollment_id = e.id + WHERE m.course_id = c.id + AND (ms.status IS NULL OR ms.status != 'completed') + ) + ORDER BY e.started_at DESC NULLS LAST + LIMIT 1 + `, [userId] ); const currentCourse = currCourseArr[0] || null; From 4c5e18e65ae7c17fed68606902d45e92bd421fd1 Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Thu, 10 Jul 2025 17:56:24 +0800 Subject: [PATCH 4/4] Add global stats --- routes/dashboard.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/routes/dashboard.js b/routes/dashboard.js index 0dbd0fc..a4fc984 100644 --- a/routes/dashboard.js +++ b/routes/dashboard.js @@ -102,6 +102,7 @@ router.get("/user-dashboard", async (req, res) => { } let summaryStats = { completedModules: 0, totalModules: 0 }; + let globalStats = { completedModules: 0, totalModules: 0 }; if (currentCourse) { const { rows } = await client.query( `SELECT @@ -119,6 +120,19 @@ router.get("/user-dashboard", async (req, res) => { summaryStats = rows[0]; } + const { rows: globalRows } = await client.query( + `SELECT + COUNT(m.id) AS "totalModules", + COUNT(ms.id) FILTER (WHERE ms.status = 'completed') AS "completedModules" + FROM enrollments e +JOIN modules m ON m.course_id = e.course_id +LEFT JOIN module_status ms + ON ms.module_id = m.id AND ms.enrollment_id = e.id +WHERE e.user_id = $1`, + [userId] + ); + globalStats = globalRows[0]; + await client.query("COMMIT"); res.json({ welcome: `Welcome, ${user.firstname}!`, @@ -127,6 +141,7 @@ router.get("/user-dashboard", async (req, res) => { nextToLearn, toRevise, summaryStats, + globalStats, }); } catch (err) { await client.query("ROLLBACK");