From e8e1d94e5b97cb1b30c83777eff6bf210a7f74a4 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Wed, 26 Feb 2025 23:31:50 +0100 Subject: [PATCH 01/42] feat: added learningPaths.ts --- backend/package.json | 1 + backend/src/app.ts | 2 + backend/src/controllers/learningPaths.ts | 134 +++++++++++++++++++++++ backend/src/routes/learningPaths.ts | 18 +++ package-lock.json | 46 ++++++-- 5 files changed, 193 insertions(+), 8 deletions(-) create mode 100644 backend/src/controllers/learningPaths.ts create mode 100644 backend/src/routes/learningPaths.ts diff --git a/backend/package.json b/backend/package.json index 7b03d8f6..8a05b694 100644 --- a/backend/package.json +++ b/backend/package.json @@ -18,6 +18,7 @@ "@mikro-orm/postgresql": "^6.4.6", "@mikro-orm/reflection": "^6.4.6", "@types/js-yaml": "^4.0.9", + "axios": "^1.8.1", "dotenv": "^16.4.7", "express": "^5.0.1", "js-yaml": "^4.1.0" diff --git a/backend/src/app.ts b/backend/src/app.ts index 90a6f45b..2debf166 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -1,6 +1,7 @@ import express, { Express, Response } from 'express'; import initORM from './orm.js'; import themeRoutes from './routes/themes.js'; +import learningPathRoutes from './routes/learningPaths.js' const app: Express = express(); const port: string | number = process.env.PORT || 3000; @@ -13,6 +14,7 @@ app.get('/', (_, res: Response) => { }); app.use('/theme', themeRoutes); +app.use('/learningPath', learningPathRoutes); async function startServer() { await initORM(); diff --git a/backend/src/controllers/learningPaths.ts b/backend/src/controllers/learningPaths.ts new file mode 100644 index 00000000..308fe9b2 --- /dev/null +++ b/backend/src/controllers/learningPaths.ts @@ -0,0 +1,134 @@ +import { Request, Response } from "express"; +import axios from "axios"; +import { themes } from "../data/themes.js"; +import dotenv from "dotenv"; + +// Load environment variables +dotenv.config(); + +// Get API base URL from environment variables +const DWENGO_API_BASE = process.env.DWENGO_API_BASE as string; +if (!DWENGO_API_BASE) { + throw new Error("DWENGO_API_BASE is not defined in the .env file"); +} + +/** + * Fetch learning paths for a given list of HRUIDs. + * This function sends a request to the Dwengo API with the provided HRUIDs. + */ +export async function getLearningPathsFromIds(req: Request, res: Response): Promise { + try { + const { hruids } = req.query; + const language = (req.query.language as string) || "nl"; // Default to Dutch + + if (!hruids) { + res.status(400).json({ error: "Missing required parameter: hruids" }); + return; + } + + // Convert the input to an array if it's a string + const hruidList = Array.isArray(hruids) ? hruids : [hruids]; + + // Request learning paths from Dwengo API + const response = await axios.get(`${DWENGO_API_BASE}/learningPath/getPathsFromIdList`, { + params: { + pathIdList: JSON.stringify({ hruids: hruidList }), + language + } + }); + + res.json(response.data); + } catch (error) { + console.error("Error fetching learning paths:", error); + res.status(500).json({ error: "Internal server error" }); + } +} + +/** + * Fetch all learning paths for a specific theme. + * First retrieves the HRUIDs associated with the theme, + * then fetches the corresponding learning paths from the Dwengo API. + */ +export async function getLearningPathsByTheme(req: Request, res: Response): Promise { + try { + const themeKey = req.params.theme; + const language = (req.query.language as string) || "nl"; // Default to Dutch + + // Find the theme by its title + const theme = themes.find((t) => t.title === themeKey); + + if (!theme) { + res.status(404).json({ error: "Theme not found" }); + return; + } + + // Extract HRUIDs from the theme + const hruidList = theme.hruids; + + // Request learning paths from Dwengo API using the extracted HRUIDs + const response = await axios.get(`${DWENGO_API_BASE}/learningPath/getPathsFromIdList`, { + params: { + pathIdList: JSON.stringify({ hruids: hruidList }), + language + } + }); + + res.json({ + theme: themeKey, + hruids: hruidList, + learningPaths: response.data + }); + + } catch (error) { + console.error("Error fetching learning paths for theme:", error); + res.status(500).json({ error: "Internal server error" }); + } +} + +export async function searchLearningPaths(req: Request, res: Response): Promise { + try { + const query = req.query.query as string; + const language = (req.query.language as string) || "nl"; + + if (!query) { + res.status(400).json({ error: "Missing search query" }); + return; + } + + const response = await axios.get(`${DWENGO_API_BASE}/learningPath/search`, { + params: { all: query, language } + }); + + res.json(response.data); + } catch (error) { + console.error("Error searching learning paths:", error); + res.status(500).json({ error: "Internal server error" }); + } +} + +export async function getAllLearningPaths(req: Request, res: Response): Promise { + try { + const language = (req.query.language as string) || "nl"; // Default to Dutch + + // Collect all HRUIDs from all themes + const allHruids: string[] = themes.flatMap(theme => theme.hruids); + + if (allHruids.length === 0) { + res.status(404).json({ error: "No HRUIDs found in themes" }); + return; + } + + // Call the Dwengo API with all HRUIDs combined + const response = await axios.get(`${DWENGO_API_BASE}/learningPath/getPathsFromIdList`, { + params: { + pathIdList: JSON.stringify({ hruids: allHruids }), + language + } + }); + + res.json(response.data); + } catch (error) { + console.error("Error fetching all learning paths:", error); + res.status(500).json({ error: "Internal server error" }); + } +} diff --git a/backend/src/routes/learningPaths.ts b/backend/src/routes/learningPaths.ts new file mode 100644 index 00000000..84e5367e --- /dev/null +++ b/backend/src/routes/learningPaths.ts @@ -0,0 +1,18 @@ +import express from "express"; +import { getLearningPathsFromIds, getLearningPathsByTheme, getAllLearningPaths, searchLearningPaths } from "../controllers/learningPaths.js"; + +const router = express.Router(); + +// Route to fetch learning paths based on a list of HRUIDs +router.get("/", getLearningPathsFromIds); + +// Route to fetch all possible learning paths +router.get("/all", getAllLearningPaths); + +// Route to fetch learning paths based on a searchterm +router.get("/search", searchLearningPaths); + +// Route to fetch learning paths based on a theme +router.get("/theme/:theme", getLearningPathsByTheme); + +export default router; diff --git a/package-lock.json b/package-lock.json index 6aa75b60..ba4b6ae4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "@mikro-orm/postgresql": "^6.4.6", "@mikro-orm/reflection": "^6.4.6", "@types/js-yaml": "^4.0.9", + "axios": "^1.8.1", "dotenv": "^16.4.7", "express": "^5.0.1", "js-yaml": "^4.1.0" @@ -3152,9 +3153,19 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, "license": "MIT" }, + "node_modules/axios": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz", + "integrity": "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3520,7 +3531,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -3788,7 +3798,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -4003,7 +4012,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -4745,6 +4753,26 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", @@ -4766,7 +4794,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", - "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -4782,7 +4809,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -4792,7 +4818,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -5077,7 +5102,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -6772,6 +6796,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", From 3ffb0678593eed4f71b382f31a970c6d574e4b8e Mon Sep 17 00:00:00 2001 From: Laure Jablonski Date: Thu, 27 Feb 2025 17:12:57 +0100 Subject: [PATCH 02/42] feat(frontend): basis van de menubar aangemaakt bij menubar-component --- frontend/src/components/MenuBar.vue | 71 ++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/MenuBar.vue b/frontend/src/components/MenuBar.vue index 73336fa3..1d956ef0 100644 --- a/frontend/src/components/MenuBar.vue +++ b/frontend/src/components/MenuBar.vue @@ -1,11 +1,80 @@ From 94f36e36c948ae759205b83d1e0f3adf1df0418d Mon Sep 17 00:00:00 2001 From: Laure Jablonski Date: Fri, 28 Feb 2025 10:55:15 +0100 Subject: [PATCH 03/42] feat(frontend): menubar code opgekuist en stijl aangepast --- frontend/src/components/MenuBar.vue | 151 ++++++++++++++++------------ 1 file changed, 87 insertions(+), 64 deletions(-) diff --git a/frontend/src/components/MenuBar.vue b/frontend/src/components/MenuBar.vue index 1d956ef0..f5da8863 100644 --- a/frontend/src/components/MenuBar.vue +++ b/frontend/src/components/MenuBar.vue @@ -1,80 +1,103 @@ From 5088b02f75ba9da44ff93c635c5c3eaf17bcb411 Mon Sep 17 00:00:00 2001 From: Laure Jablonski Date: Fri, 28 Feb 2025 11:11:20 +0100 Subject: [PATCH 04/42] feat(frontend): add Material Design Icons library for UI icons --- frontend/src/main.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/src/main.ts b/frontend/src/main.ts index bc0c3d42..bfeb3abe 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -5,6 +5,7 @@ import "vuetify/styles"; import { createVuetify } from "vuetify"; import * as components from "vuetify/components"; import * as directives from "vuetify/directives"; +import { aliases, mdi } from 'vuetify/iconsets/mdi' // Components import App from "./App.vue"; @@ -14,6 +15,11 @@ const app = createApp(App); app.use(router); +const link = document.createElement("link"); +link.rel = "stylesheet"; +link.href = "https://cdn.jsdelivr.net/npm/@mdi/font@5.x/css/materialdesignicons.min.css"; +document.head.appendChild(link); + const vuetify = createVuetify({ components, directives, From 4009bb2111e3d2894b34307b3a09ee8d793a50e7 Mon Sep 17 00:00:00 2001 From: Laure Jablonski Date: Fri, 28 Feb 2025 12:26:07 +0100 Subject: [PATCH 05/42] feat(frontend): menubalk is volledig af qua stijl en items --- frontend/src/components/MenuBar.vue | 109 ++++++++++++++++++---------- 1 file changed, 72 insertions(+), 37 deletions(-) diff --git a/frontend/src/components/MenuBar.vue b/frontend/src/components/MenuBar.vue index f5da8863..6f13ddff 100644 --- a/frontend/src/components/MenuBar.vue +++ b/frontend/src/components/MenuBar.vue @@ -7,7 +7,11 @@ const isTeacher = route.path.includes("teacher"); const role = isTeacher ? "teacher" : "student"; - const name = "Bob Debouwer"; // TODO: naam opvragen + const name = "Kurt Cobain"; //TODO: naam opvragen + const initials = name + .split(" ") + .map((n) => n[0]) + .join(""); const userId = computed(() => route.params.id as string); @@ -15,46 +19,66 @@ @@ -62,6 +86,17 @@