From e8e1d94e5b97cb1b30c83777eff6bf210a7f74a4 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Wed, 26 Feb 2025 23:31:50 +0100 Subject: [PATCH 01/21] 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 d401136d3ad4bd970a859342bb9db8d2e9bde7a8 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Fri, 28 Feb 2025 17:04:52 +0100 Subject: [PATCH 02/21] feat: added route for learningObjects --- backend/src/app.ts | 3 + backend/src/config/config.ts | 9 +++ backend/src/controllers/learningObjects.ts | 20 +++++++ backend/src/controllers/learningPaths.ts | 11 +--- backend/src/routes/learningObjects.ts | 11 ++++ backend/src/routes/learningPaths.ts | 5 ++ backend/src/services/learningObjects.ts | 67 ++++++++++++++++++++++ 7 files changed, 116 insertions(+), 10 deletions(-) create mode 100644 backend/src/config/config.ts create mode 100644 backend/src/controllers/learningObjects.ts create mode 100644 backend/src/routes/learningObjects.ts create mode 100644 backend/src/services/learningObjects.ts diff --git a/backend/src/app.ts b/backend/src/app.ts index 2debf166..5282468d 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -2,6 +2,8 @@ import express, { Express, Response } from 'express'; import initORM from './orm.js'; import themeRoutes from './routes/themes.js'; import learningPathRoutes from './routes/learningPaths.js' +import learningObjectRoutes from './routes/learningObjects.js' + const app: Express = express(); const port: string | number = process.env.PORT || 3000; @@ -15,6 +17,7 @@ app.get('/', (_, res: Response) => { app.use('/theme', themeRoutes); app.use('/learningPath', learningPathRoutes); +app.use('/learningObject', learningObjectRoutes); async function startServer() { await initORM(); diff --git a/backend/src/config/config.ts b/backend/src/config/config.ts new file mode 100644 index 00000000..414d10a6 --- /dev/null +++ b/backend/src/config/config.ts @@ -0,0 +1,9 @@ +// Can be placed in dotenv but found it redundant + +// import dotenv from "dotenv"; + +// Load .env file +// dotenv.config(); + +export const DWENGO_API_BASE = "https://dwengo.org/backend/api"; + diff --git a/backend/src/controllers/learningObjects.ts b/backend/src/controllers/learningObjects.ts new file mode 100644 index 00000000..fc020b7e --- /dev/null +++ b/backend/src/controllers/learningObjects.ts @@ -0,0 +1,20 @@ +import { Request, Response } from "express"; +import { getLearningObjectsFromPath } from "../services/learningObjects.js"; + +export async function getAllLearningObjects(req: Request, res: Response): Promise { + try { + const { hruid } = req.params; + const language = req.query.language as string || "nl"; // Default to Dutch; + + if (!language) { + res.status(400).json({ error: "Language query parameter is required." }); + return; + } + + const learningObjects = await getLearningObjectsFromPath(hruid, language); + res.json(learningObjects); + } catch (error) { + console.error("Error fetching learning objects:", error); + res.status(500).json({ error: "Internal server error" }); + } +} diff --git a/backend/src/controllers/learningPaths.ts b/backend/src/controllers/learningPaths.ts index 308fe9b2..c8cadd16 100644 --- a/backend/src/controllers/learningPaths.ts +++ b/backend/src/controllers/learningPaths.ts @@ -1,16 +1,7 @@ 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"); -} +import { DWENGO_API_BASE } from "../config/config.js"; /** * Fetch learning paths for a given list of HRUIDs. diff --git a/backend/src/routes/learningObjects.ts b/backend/src/routes/learningObjects.ts new file mode 100644 index 00000000..886b42f3 --- /dev/null +++ b/backend/src/routes/learningObjects.ts @@ -0,0 +1,11 @@ +import express from "express"; +import { getAllLearningObjects } from "../controllers/learningObjects.js"; + +const router = express.Router(); + +// arg: hruid learningPath +// query: language +// Route to fetch list of learning objects based on hruid of learning path +router.get("/:hruid", getAllLearningObjects); + +export default router; diff --git a/backend/src/routes/learningPaths.ts b/backend/src/routes/learningPaths.ts index 84e5367e..5b49937d 100644 --- a/backend/src/routes/learningPaths.ts +++ b/backend/src/routes/learningPaths.ts @@ -3,15 +3,20 @@ import { getLearningPathsFromIds, getLearningPathsByTheme, getAllLearningPaths, const router = express.Router(); +// query: language // Route to fetch learning paths based on a list of HRUIDs router.get("/", getLearningPathsFromIds); +// query: language // Route to fetch all possible learning paths router.get("/all", getAllLearningPaths); +// query: language // Route to fetch learning paths based on a searchterm router.get("/search", searchLearningPaths); +// arg: theme id +// query: language // Route to fetch learning paths based on a theme router.get("/theme/:theme", getLearningPathsByTheme); diff --git a/backend/src/services/learningObjects.ts b/backend/src/services/learningObjects.ts new file mode 100644 index 00000000..6e58defb --- /dev/null +++ b/backend/src/services/learningObjects.ts @@ -0,0 +1,67 @@ +import axios from "axios"; +import { DWENGO_API_BASE } from "../config/config.js"; + +interface LearningObjectNode { + _id: string; + learningobject_hruid: string; + version: number; + language: string; +} + + +export async function getLearningObjectsFromPath(hruid: string, language: string) { + try { + const learningPathUrl = `${DWENGO_API_BASE}/learningPath/${hruid}/${language}`; + const learningPathResponse = await axios.get(learningPathUrl); + const nodes = learningPathResponse.data.nodes; + + if (!nodes || nodes.length === 0) { + throw new Error("No learning objects found in this learning path."); + } + + return await Promise.all( + nodes.map(async (node: LearningObjectNode) => { + const metadataUrl = `${DWENGO_API_BASE}/learningObject/getMetadata?hruid=${node.learningobject_hruid}&version=${node.version}&language=${language}`; + const metadataResponse = await axios.get(metadataUrl); + + const html_url = `${DWENGO_API_BASE}/learningObject/getRaw?hruid=${node.learningobject_hruid}&version=${node.version}&language=${language}`; + + return filterLearningObjectMetadata(metadataResponse.data, html_url); + }) + ); + + } catch (error) { + console.error("Error fetching learning objects:", error); + throw new Error("Failed to fetch learning objects."); + } +} + +function filterLearningObjectMetadata(data: any, html_url: String) { + return { + key: data.hruid, + // hruid learningObject (not path) + _id: data._id, + uuid: data.uuid, + version: data.version, + + title: data.title, + html_url, + // html content object + language: data.language, + difficulty: data.difficulty, + estimated_time: data.estimated_time, + available: data.available, + teacher_exclusive: data.teacher_exclusive, + educational_goals: data.educational_goals, + // list with learningObjects + keywords: data.keywords, + // for search + description: data.description, + // for search (not an actual description) + target_ages: data.target_ages, + + // skos concepts needed ?? + // content type needed ?? + // content location ?? + }; +} From ac778981e23ef530d3865bfc35a91f5bd4735acb Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Fri, 28 Feb 2025 17:12:21 +0100 Subject: [PATCH 03/21] fix: beter commentaar en van nl naar eng vertaald --- backend/src/controllers/themes.ts | 15 ++++----------- backend/src/routes/themes.ts | 5 +++++ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/backend/src/controllers/themes.ts b/backend/src/controllers/themes.ts index 442eb982..73091dd7 100644 --- a/backend/src/controllers/themes.ts +++ b/backend/src/controllers/themes.ts @@ -10,9 +10,6 @@ interface Translations { }; } -/** - * Laadt de vertalingen uit een YAML-bestand - */ function loadTranslations(language: string): Translations { try { const filePath = path.join(process.cwd(), '_i18n', `${language}.yml`); @@ -20,7 +17,7 @@ function loadTranslations(language: string): Translations { return yaml.load(yamlFile) as Translations; } catch (error) { console.error( - `Kan vertaling niet laden voor ${language}, fallback naar Nederlands` + `Cant load for language: ${language}, fallen back on dutch` ); console.error(error); const fallbackPath = path.join(process.cwd(), '_i18n', 'nl.yml'); @@ -28,9 +25,7 @@ function loadTranslations(language: string): Translations { } } -/** - * GET /themes → Haalt de lijst met thema's op inclusief vertalingen - */ + export function getThemes(req: Request, res: Response) { const language = (req.query.language as string)?.toLowerCase() || 'nl'; const translations = loadTranslations(language); @@ -48,9 +43,7 @@ export function getThemes(req: Request, res: Response) { res.json(themeList); } -/** - * GET /themes/:theme → Geeft de HRUIDs terug voor een specifiek thema - */ + export function getThemeByTitle(req: Request, res: Response) { const themeKey = req.params.theme; const theme = themes.find((t) => { @@ -60,6 +53,6 @@ export function getThemeByTitle(req: Request, res: Response) { if (theme) { res.json(theme.hruids); } else { - res.status(404).json({ error: 'Thema niet gevonden' }); + res.status(404).json({ error: 'Theme not found' }); } } diff --git a/backend/src/routes/themes.ts b/backend/src/routes/themes.ts index 3185640e..434ee687 100644 --- a/backend/src/routes/themes.ts +++ b/backend/src/routes/themes.ts @@ -3,7 +3,12 @@ import { getThemes, getThemeByTitle } from '../controllers/themes.js'; const router = express.Router(); +// query: language +// Route to fetch list of {key, title, description, image} themes in their respective language router.get('/', getThemes); + +// arg: theme (key) +// Route to fetch list of hruids based on theme router.get('/:theme', getThemeByTitle); export default router; From acfffda82d9984ddd9cda7a33441e2b528220799 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Fri, 28 Feb 2025 17:21:03 +0100 Subject: [PATCH 04/21] fix: lint + format --- backend/src/app.ts | 5 +- backend/src/config/config.ts | 7 +- backend/src/controllers/learningObjects.ts | 24 ++-- backend/src/controllers/learningPaths.ts | 121 +++++++++++++-------- backend/src/routes/learningObjects.ts | 10 +- backend/src/routes/learningPaths.ts | 27 +++-- backend/src/routes/themes.ts | 4 +- backend/src/services/learningObjects.ts | 82 +++++++------- 8 files changed, 162 insertions(+), 118 deletions(-) diff --git a/backend/src/app.ts b/backend/src/app.ts index 5282468d..1701e12c 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -1,9 +1,8 @@ import express, { Express, Response } from 'express'; import initORM from './orm.js'; import themeRoutes from './routes/themes.js'; -import learningPathRoutes from './routes/learningPaths.js' -import learningObjectRoutes from './routes/learningObjects.js' - +import learningPathRoutes from './routes/learningPaths.js'; +import learningObjectRoutes from './routes/learningObjects.js'; const app: Express = express(); const port: string | number = process.env.PORT || 3000; diff --git a/backend/src/config/config.ts b/backend/src/config/config.ts index 414d10a6..84181070 100644 --- a/backend/src/config/config.ts +++ b/backend/src/config/config.ts @@ -1,9 +1,8 @@ // Can be placed in dotenv but found it redundant -// import dotenv from "dotenv"; +// Import dotenv from "dotenv"; // Load .env file -// dotenv.config(); - -export const DWENGO_API_BASE = "https://dwengo.org/backend/api"; +// Dotenv.config(); +export const DWENGO_API_BASE = 'https://dwengo.org/backend/api'; diff --git a/backend/src/controllers/learningObjects.ts b/backend/src/controllers/learningObjects.ts index fc020b7e..f7640891 100644 --- a/backend/src/controllers/learningObjects.ts +++ b/backend/src/controllers/learningObjects.ts @@ -1,20 +1,28 @@ -import { Request, Response } from "express"; -import { getLearningObjectsFromPath } from "../services/learningObjects.js"; +import { Request, Response } from 'express'; +import { getLearningObjectsFromPath } from '../services/learningObjects.js'; -export async function getAllLearningObjects(req: Request, res: Response): Promise { +export async function getAllLearningObjects( + req: Request, + res: Response +): Promise { try { const { hruid } = req.params; - const language = req.query.language as string || "nl"; // Default to Dutch; + const language = (req.query.language as string) || 'nl'; // Default to Dutch; if (!language) { - res.status(400).json({ error: "Language query parameter is required." }); + res.status(400).json({ + error: 'Language query parameter is required.', + }); return; } - const learningObjects = await getLearningObjectsFromPath(hruid, language); + const learningObjects = await getLearningObjectsFromPath( + hruid, + language + ); res.json(learningObjects); } catch (error) { - console.error("Error fetching learning objects:", error); - res.status(500).json({ error: "Internal server error" }); + console.error('Error fetching learning objects:', error); + res.status(500).json({ error: 'Internal server error' }); } } diff --git a/backend/src/controllers/learningPaths.ts b/backend/src/controllers/learningPaths.ts index c8cadd16..e588bbe3 100644 --- a/backend/src/controllers/learningPaths.ts +++ b/backend/src/controllers/learningPaths.ts @@ -1,19 +1,24 @@ -import { Request, Response } from "express"; -import axios from "axios"; -import { themes } from "../data/themes.js"; -import { DWENGO_API_BASE } from "../config/config.js"; +import { Request, Response } from 'express'; +import axios from 'axios'; +import { themes } from '../data/themes.js'; +import { DWENGO_API_BASE } from '../config/config.js'; /** * 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 { +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 + const language = (req.query.language as string) || 'nl'; // Default to Dutch if (!hruids) { - res.status(400).json({ error: "Missing required parameter: hruids" }); + res.status(400).json({ + error: 'Missing required parameter: hruids', + }); return; } @@ -21,17 +26,20 @@ export async function getLearningPathsFromIds(req: Request, res: Response): Prom 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 + 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" }); + console.error('Error fetching learning paths:', error); + res.status(500).json({ error: 'Internal server error' }); } } @@ -40,16 +48,21 @@ export async function getLearningPathsFromIds(req: Request, res: Response): Prom * 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 { +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 + 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); + const theme = themes.find((t) => { + return t.title === themeKey; + }); if (!theme) { - res.status(404).json({ error: "Theme not found" }); + res.status(404).json({ error: 'Theme not found' }); return; } @@ -57,69 +70,85 @@ export async function getLearningPathsByTheme(req: Request, res: Response): Prom 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 + 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 + learningPaths: response.data, }); - } catch (error) { - console.error("Error fetching learning paths for theme:", error); - res.status(500).json({ error: "Internal server 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 { +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"; + const language = (req.query.language as string) || 'nl'; if (!query) { - res.status(400).json({ error: "Missing search query" }); + res.status(400).json({ error: 'Missing search query' }); return; } - const response = await axios.get(`${DWENGO_API_BASE}/learningPath/search`, { - params: { all: query, language } - }); + 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" }); + console.error('Error searching learning paths:', error); + res.status(500).json({ error: 'Internal server error' }); } } -export async function getAllLearningPaths(req: Request, res: Response): Promise { +export async function getAllLearningPaths( + req: Request, + res: Response +): Promise { try { - const language = (req.query.language as string) || "nl"; // Default to Dutch + 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); + const allHruids: string[] = themes.flatMap((theme) => { + return theme.hruids; + }); if (allHruids.length === 0) { - res.status(404).json({ error: "No HRUIDs found in themes" }); + 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 + 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" }); + console.error('Error fetching all learning paths:', error); + res.status(500).json({ error: 'Internal server error' }); } } diff --git a/backend/src/routes/learningObjects.ts b/backend/src/routes/learningObjects.ts index 886b42f3..907ec1d7 100644 --- a/backend/src/routes/learningObjects.ts +++ b/backend/src/routes/learningObjects.ts @@ -1,11 +1,11 @@ -import express from "express"; -import { getAllLearningObjects } from "../controllers/learningObjects.js"; +import express from 'express'; +import { getAllLearningObjects } from '../controllers/learningObjects.js'; const router = express.Router(); -// arg: hruid learningPath -// query: language +// Arg: hruid learningPath +// Query: language // Route to fetch list of learning objects based on hruid of learning path -router.get("/:hruid", getAllLearningObjects); +router.get('/:hruid', getAllLearningObjects); export default router; diff --git a/backend/src/routes/learningPaths.ts b/backend/src/routes/learningPaths.ts index 5b49937d..ead506cd 100644 --- a/backend/src/routes/learningPaths.ts +++ b/backend/src/routes/learningPaths.ts @@ -1,23 +1,28 @@ -import express from "express"; -import { getLearningPathsFromIds, getLearningPathsByTheme, getAllLearningPaths, searchLearningPaths } from "../controllers/learningPaths.js"; +import express from 'express'; +import { + getLearningPathsFromIds, + getLearningPathsByTheme, + getAllLearningPaths, + searchLearningPaths, +} from '../controllers/learningPaths.js'; const router = express.Router(); -// query: language +// Query: language // Route to fetch learning paths based on a list of HRUIDs -router.get("/", getLearningPathsFromIds); +router.get('/', getLearningPathsFromIds); -// query: language +// Query: language // Route to fetch all possible learning paths -router.get("/all", getAllLearningPaths); +router.get('/all', getAllLearningPaths); -// query: language +// Query: language // Route to fetch learning paths based on a searchterm -router.get("/search", searchLearningPaths); +router.get('/search', searchLearningPaths); -// arg: theme id -// query: language +// Arg: theme id +// Query: language // Route to fetch learning paths based on a theme -router.get("/theme/:theme", getLearningPathsByTheme); +router.get('/theme/:theme', getLearningPathsByTheme); export default router; diff --git a/backend/src/routes/themes.ts b/backend/src/routes/themes.ts index 434ee687..388b3e38 100644 --- a/backend/src/routes/themes.ts +++ b/backend/src/routes/themes.ts @@ -3,11 +3,11 @@ import { getThemes, getThemeByTitle } from '../controllers/themes.js'; const router = express.Router(); -// query: language +// Query: language // Route to fetch list of {key, title, description, image} themes in their respective language router.get('/', getThemes); -// arg: theme (key) +// Arg: theme (key) // Route to fetch list of hruids based on theme router.get('/:theme', getThemeByTitle); diff --git a/backend/src/services/learningObjects.ts b/backend/src/services/learningObjects.ts index 6e58defb..bbec3d3a 100644 --- a/backend/src/services/learningObjects.ts +++ b/backend/src/services/learningObjects.ts @@ -1,5 +1,5 @@ -import axios from "axios"; -import { DWENGO_API_BASE } from "../config/config.js"; +import axios from 'axios'; +import { DWENGO_API_BASE } from '../config/config.js'; interface LearningObjectNode { _id: string; @@ -8,15 +8,47 @@ interface LearningObjectNode { language: string; } +function filterLearningObjectMetadata(data: any, htmlUrl: string) { + return { + key: data.hruid, + // Hruid learningObject (not path) + _id: data._id, + uuid: data.uuid, + version: data.version, -export async function getLearningObjectsFromPath(hruid: string, language: string) { + title: data.title, + html_url: htmlUrl, + // Html content object + language: data.language, + difficulty: data.difficulty, + estimated_time: data.estimated_time, + available: data.available, + teacher_exclusive: data.teacher_exclusive, + educational_goals: data.educational_goals, + // List with learningObjects + keywords: data.keywords, + // For search + description: data.description, + // For search (not an actual description) + target_ages: data.target_ages, + + // Skos concepts needed ?? + // Content type needed ?? + // Content location ?? + }; +} + +export async function getLearningObjectsFromPath( + hruid: string, + language: string +) { try { const learningPathUrl = `${DWENGO_API_BASE}/learningPath/${hruid}/${language}`; const learningPathResponse = await axios.get(learningPathUrl); const nodes = learningPathResponse.data.nodes; if (!nodes || nodes.length === 0) { - throw new Error("No learning objects found in this learning path."); + throw new Error('No learning objects found in this learning path.'); } return await Promise.all( @@ -24,44 +56,16 @@ export async function getLearningObjectsFromPath(hruid: string, language: string const metadataUrl = `${DWENGO_API_BASE}/learningObject/getMetadata?hruid=${node.learningobject_hruid}&version=${node.version}&language=${language}`; const metadataResponse = await axios.get(metadataUrl); - const html_url = `${DWENGO_API_BASE}/learningObject/getRaw?hruid=${node.learningobject_hruid}&version=${node.version}&language=${language}`; + const htmlUrl = `${DWENGO_API_BASE}/learningObject/getRaw?hruid=${node.learningobject_hruid}&version=${node.version}&language=${language}`; - return filterLearningObjectMetadata(metadataResponse.data, html_url); + return filterLearningObjectMetadata( + metadataResponse.data, + htmlUrl + ); }) ); - } catch (error) { - console.error("Error fetching learning objects:", error); - throw new Error("Failed to fetch learning objects."); + console.error('Error fetching learning objects:', error); + throw new Error('Failed to fetch learning objects.'); } } - -function filterLearningObjectMetadata(data: any, html_url: String) { - return { - key: data.hruid, - // hruid learningObject (not path) - _id: data._id, - uuid: data.uuid, - version: data.version, - - title: data.title, - html_url, - // html content object - language: data.language, - difficulty: data.difficulty, - estimated_time: data.estimated_time, - available: data.available, - teacher_exclusive: data.teacher_exclusive, - educational_goals: data.educational_goals, - // list with learningObjects - keywords: data.keywords, - // for search - description: data.description, - // for search (not an actual description) - target_ages: data.target_ages, - - // skos concepts needed ?? - // content type needed ?? - // content location ?? - }; -} From 594aa540dda6112b474f0e2051d37cdb2efd8019 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Fri, 28 Feb 2025 18:52:48 +0100 Subject: [PATCH 05/21] fix: example urls bij learningPaths.ts --- backend/src/controllers/themes.ts | 2 - backend/src/routes/learningPaths.ts | 5 +- package-lock.json | 114 ++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 3 deletions(-) diff --git a/backend/src/controllers/themes.ts b/backend/src/controllers/themes.ts index 73091dd7..d81cf054 100644 --- a/backend/src/controllers/themes.ts +++ b/backend/src/controllers/themes.ts @@ -25,7 +25,6 @@ function loadTranslations(language: string): Translations { } } - export function getThemes(req: Request, res: Response) { const language = (req.query.language as string)?.toLowerCase() || 'nl'; const translations = loadTranslations(language); @@ -43,7 +42,6 @@ export function getThemes(req: Request, res: Response) { res.json(themeList); } - export function getThemeByTitle(req: Request, res: Response) { const themeKey = req.params.theme; const theme = themes.find((t) => { diff --git a/backend/src/routes/learningPaths.ts b/backend/src/routes/learningPaths.ts index ead506cd..7e821350 100644 --- a/backend/src/routes/learningPaths.ts +++ b/backend/src/routes/learningPaths.ts @@ -8,8 +8,9 @@ import { const router = express.Router(); -// Query: language +// Query: hruids(list), language // Route to fetch learning paths based on a list of HRUIDs +// Example: http://localhost:3000/learningPath?hruids=pn_werking&hruids=art1 router.get('/', getLearningPathsFromIds); // Query: language @@ -18,11 +19,13 @@ router.get('/all', getAllLearningPaths); // Query: language // Route to fetch learning paths based on a searchterm +// Example: http://localhost:3000/learningPath/search?query=robot router.get('/search', searchLearningPaths); // Arg: theme id // Query: language // Route to fetch learning paths based on a theme +// Example: http://localhost:3000/learningPath/theme/kiks router.get('/theme/:theme', getLearningPathsByTheme); export default router; diff --git a/package-lock.json b/package-lock.json index ba4b6ae4..daab699e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3182,6 +3182,12 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/bmp-js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", + "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==", + "license": "MIT" + }, "node_modules/body-parser": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.1.0.tgz", @@ -5234,6 +5240,12 @@ "node": ">=0.10.0" } }, + "node_modules/idb-keyval": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz", + "integrity": "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==", + "license": "Apache-2.0" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -5443,6 +5455,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "license": "MIT" + }, "node_modules/is-what": { "version": "4.1.16", "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", @@ -6048,6 +6066,48 @@ "node": ">= 0.6" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -6249,6 +6309,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/opencollective-postinstall": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", + "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", + "license": "MIT", + "bin": { + "opencollective-postinstall": "index.js" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -6915,6 +6984,12 @@ "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", "license": "Apache-2.0" }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -7633,6 +7708,30 @@ "node": ">=8.0.0" } }, + "node_modules/tesseract.js": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-6.0.0.tgz", + "integrity": "sha512-tqYCod1HwJzkeZw1l6XWx+ly2hhisGcBtak9MArhYwDAxL0NgeVhLJcUjqPxZMQtpgtVUzWcpZPryi+hnaQGVw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "bmp-js": "^0.1.0", + "idb-keyval": "^6.2.0", + "is-url": "^1.2.4", + "node-fetch": "^2.6.9", + "opencollective-postinstall": "^2.0.3", + "regenerator-runtime": "^0.13.3", + "tesseract.js-core": "^6.0.0", + "wasm-feature-detect": "^1.2.11", + "zlibjs": "^0.3.1" + } + }, + "node_modules/tesseract.js-core": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-6.0.0.tgz", + "integrity": "sha512-1Qncm/9oKM7xgrQXZXNB+NRh19qiXGhxlrR8EwFbK5SaUbPZnS5OMtP/ghtqfd23hsr1ZvZbZjeuAGcMxd/ooA==", + "license": "Apache-2.0" + }, "node_modules/tildify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz", @@ -8990,6 +9089,12 @@ "node": ">=18" } }, + "node_modules/wasm-feature-detect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.8.0.tgz", + "integrity": "sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==", + "license": "Apache-2.0" + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -9368,6 +9473,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zlibjs": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz", + "integrity": "sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==", + "license": "MIT", + "engines": { + "node": "*" + } } } } From 5ca1bebe381dfd737a167bd780174467631443fd Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Fri, 28 Feb 2025 18:53:36 +0100 Subject: [PATCH 06/21] fix: van snake_case naar camelCase --- backend/src/services/learningObjects.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/backend/src/services/learningObjects.ts b/backend/src/services/learningObjects.ts index bbec3d3a..0168981c 100644 --- a/backend/src/services/learningObjects.ts +++ b/backend/src/services/learningObjects.ts @@ -17,24 +17,30 @@ function filterLearningObjectMetadata(data: any, htmlUrl: string) { version: data.version, title: data.title, - html_url: htmlUrl, + htmlUrl, // Html content object language: data.language, difficulty: data.difficulty, - estimated_time: data.estimated_time, + estimatedTime: data.estimated_time, available: data.available, - teacher_exclusive: data.teacher_exclusive, - educational_goals: data.educational_goals, + teacherExclusive: data.teacher_exclusive, + educationalGoals: data.educational_goals, // List with learningObjects keywords: data.keywords, // For search description: data.description, // For search (not an actual description) - target_ages: data.target_ages, + targetAges: data.target_ages, + contentType: data.content_type, + // Text/plain, text/md, image/image-block, + // Image/image/, audio/mpeg or extern + contentLocation: data.content_location, + // If content type extern // Skos concepts needed ?? - // Content type needed ?? - // Content location ?? + // Return value needed ?? + // Callback_url to send response + // Callback_scheme }; } From 91eb374b7e8b6e72a832f33affdfc1a1c84849a4 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Sat, 1 Mar 2025 15:15:04 +0100 Subject: [PATCH 07/21] fix: api helper voor extra error checks bij fetch dwengo api + schrijffout in controllers/themes --- backend/src/controllers/themes.ts | 2 +- backend/src/routes/learningObjects.ts | 3 +++ backend/src/routes/learningPaths.ts | 2 ++ backend/src/services/learningObjects.ts | 17 +++++++++------ backend/src/util/apiHelper.ts | 29 +++++++++++++++++++++++++ 5 files changed, 45 insertions(+), 8 deletions(-) create mode 100644 backend/src/util/apiHelper.ts diff --git a/backend/src/controllers/themes.ts b/backend/src/controllers/themes.ts index d81cf054..d6c67107 100644 --- a/backend/src/controllers/themes.ts +++ b/backend/src/controllers/themes.ts @@ -17,7 +17,7 @@ function loadTranslations(language: string): Translations { return yaml.load(yamlFile) as Translations; } catch (error) { console.error( - `Cant load for language: ${language}, fallen back on dutch` + `Cannot load translation for: ${language}, fallen back to Dutch` ); console.error(error); const fallbackPath = path.join(process.cwd(), '_i18n', 'nl.yml'); diff --git a/backend/src/routes/learningObjects.ts b/backend/src/routes/learningObjects.ts index 907ec1d7..1d72daaf 100644 --- a/backend/src/routes/learningObjects.ts +++ b/backend/src/routes/learningObjects.ts @@ -3,9 +3,12 @@ import { getAllLearningObjects } from '../controllers/learningObjects.js'; const router = express.Router(); +// DWENGO learning objects + // Arg: hruid learningPath // Query: language // Route to fetch list of learning objects based on hruid of learning path +// example: http://localhost:3000/learningObject/un_artificiele_intelligentie router.get('/:hruid', getAllLearningObjects); export default router; diff --git a/backend/src/routes/learningPaths.ts b/backend/src/routes/learningPaths.ts index 7e821350..17eb881d 100644 --- a/backend/src/routes/learningPaths.ts +++ b/backend/src/routes/learningPaths.ts @@ -8,6 +8,8 @@ import { const router = express.Router(); +// DWENGO learning paths + // Query: hruids(list), language // Route to fetch learning paths based on a list of HRUIDs // Example: http://localhost:3000/learningPath?hruids=pn_werking&hruids=art1 diff --git a/backend/src/services/learningObjects.ts b/backend/src/services/learningObjects.ts index 0168981c..bab246e0 100644 --- a/backend/src/services/learningObjects.ts +++ b/backend/src/services/learningObjects.ts @@ -1,5 +1,6 @@ import axios from 'axios'; import { DWENGO_API_BASE } from '../config/config.js'; +import { fetchWithLogging } from "../util/apiHelper.js"; interface LearningObjectNode { _id: string; @@ -18,7 +19,7 @@ function filterLearningObjectMetadata(data: any, htmlUrl: string) { title: data.title, htmlUrl, - // Html content object + // Url to fetch html content language: data.language, difficulty: data.difficulty, estimatedTime: data.estimated_time, @@ -50,15 +51,18 @@ export async function getLearningObjectsFromPath( ) { try { const learningPathUrl = `${DWENGO_API_BASE}/learningPath/${hruid}/${language}`; - const learningPathResponse = await axios.get(learningPathUrl); - const nodes = learningPathResponse.data.nodes; + const learningPathData = await fetchWithLogging<{ nodes: LearningObjectNode[] }>( + learningPathUrl, + `Learning path for HRUID "${hruid}" in language "${language}"` + ); - if (!nodes || nodes.length === 0) { - throw new Error('No learning objects found in this learning path.'); + if (!learningPathData || !learningPathData.nodes || learningPathData.nodes.length === 0) { + console.error(`⚠️ WARNING: Learning path "${hruid}" exists but contains no learning objects.`); + return []; } return await Promise.all( - nodes.map(async (node: LearningObjectNode) => { + learningPathData.nodes.map(async (node: LearningObjectNode) => { const metadataUrl = `${DWENGO_API_BASE}/learningObject/getMetadata?hruid=${node.learningobject_hruid}&version=${node.version}&language=${language}`; const metadataResponse = await axios.get(metadataUrl); @@ -72,6 +76,5 @@ export async function getLearningObjectsFromPath( ); } catch (error) { console.error('Error fetching learning objects:', error); - throw new Error('Failed to fetch learning objects.'); } } diff --git a/backend/src/util/apiHelper.ts b/backend/src/util/apiHelper.ts new file mode 100644 index 00000000..08e12b11 --- /dev/null +++ b/backend/src/util/apiHelper.ts @@ -0,0 +1,29 @@ +import axios from 'axios'; + +// !!!! when logger is done -> change + +/** + * Utility function to fetch data from an API endpoint with error handling. + * Logs errors but does NOT throw exceptions to keep the system running. + * + * @param url The API endpoint to fetch from. + * @param description A short description of what is being fetched (for logging). + * @returns The response data if successful, or null if an error occurs. + */ +export async function fetchWithLogging(url: string, description: string): Promise { + try { + const response = await axios.get(url); + return response.data; + } catch (error: any) { + if (error.response) { + if (error.response.status === 404) { + console.error(`❌ ERROR: ${description} not found (404) at "${url}".`); + } else { + console.error(`❌ ERROR: Failed to fetch ${description}. Status: ${error.response.status} - ${error.response.statusText} (URL: "${url}")`); + } + } else { + console.error(`❌ ERROR: Network or unexpected error when fetching ${description}:`, error.message); + } + return null; + } +} From f7d6cbce65eb4698ba008f2c0e4cf6f6904d8cbf Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Sat, 1 Mar 2025 17:52:55 +0100 Subject: [PATCH 08/21] fix: opslitsing learningpath controller met extra service + api helper --- backend/src/controllers/learningPaths.ts | 143 ++++++----------------- backend/src/services/learningObjects.ts | 13 ++- backend/src/services/learningPaths.ts | 45 +++++++ backend/src/util/apiHelper.ts | 13 ++- 4 files changed, 98 insertions(+), 116 deletions(-) create mode 100644 backend/src/services/learningPaths.ts diff --git a/backend/src/controllers/learningPaths.ts b/backend/src/controllers/learningPaths.ts index e588bbe3..880858f3 100644 --- a/backend/src/controllers/learningPaths.ts +++ b/backend/src/controllers/learningPaths.ts @@ -1,100 +1,68 @@ import { Request, Response } from 'express'; -import axios from 'axios'; import { themes } from '../data/themes.js'; import { DWENGO_API_BASE } from '../config/config.js'; +import { fetchWithLogging } from "../util/apiHelper.js"; +import { fetchLearningPaths } from "../services/learningPaths.js"; /** - * Fetch learning paths for a given list of HRUIDs. - * This function sends a request to the Dwengo API with the provided HRUIDs. + * Fetch learning paths based on HRUIDs or return all if no HRUIDs are provided. + * - If `hruids` are given -> fetch specific learning paths. + * - If `hruids` is missing -> return all available learning paths. */ -export async function getLearningPathsFromIds( - req: Request, - res: Response -): Promise { +export async function getLearningPaths(req: Request, res: Response): Promise { try { - const { hruids } = req.query; - const language = (req.query.language as string) || 'nl'; // Default to Dutch + const hruids = req.query.hruids; // Can be string or array + const language = (req.query.language as string) || 'nl'; - if (!hruids) { - res.status(400).json({ - error: 'Missing required parameter: hruids', - }); - return; + let hruidList: string[] = []; + + if (hruids) { + hruidList = Array.isArray(hruids) ? hruids.map(String) : [String(hruids)]; + } else { + // If no hruids are provided, fetch ALL learning paths + hruidList = themes.flatMap((theme) => theme.hruids); } - // Convert the input to an array if it's a string - const hruidList = Array.isArray(hruids) ? hruids : [hruids]; + const learningPaths = await fetchLearningPaths(hruidList, language, `HRUIDs: ${hruidList.join(', ')}`); - // 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); + res.json(learningPaths); } catch (error) { - console.error('Error fetching learning paths:', error); + console.error('❌ Unexpected 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 { +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) => { - return t.title === themeKey; - }); + const language = (req.query.language as string) || 'nl'; + const theme = themes.find((t) => t.title === themeKey); if (!theme) { + console.error(`⚠️ WARNING: Theme "${themeKey}" not found.`); 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, - }, - } - ); - + const response = await fetchLearningPaths(theme.hruids, language, `theme "${themeKey}"`); res.json({ theme: themeKey, - hruids: hruidList, - learningPaths: response.data, + hruids: theme.hruids, + ...response, }); } catch (error) { - console.error('Error fetching learning paths for theme:', error); - res.status(500).json({ error: 'Internal server error' }); + console.error('❌ Unexpected error fetching learning paths by theme:', error); } } -export async function searchLearningPaths( - req: Request, - res: Response -): Promise { +/** + * Search learning paths by query. + */ +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'; @@ -104,51 +72,12 @@ export async function searchLearningPaths( return; } - const response = await axios.get( - `${DWENGO_API_BASE}/learningPath/search`, - { - params: { all: query, language }, - } - ); + const apiUrl = `${DWENGO_API_BASE}/learningPath/search`; + const params = { all: query, language }; - res.json(response.data); + const searchResults = await fetchWithLogging(apiUrl, `Search learning paths with query "${query}"`, params); + res.json(searchResults ?? []); } 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) => { - return 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' }); + console.error('❌ Unexpected error searching learning paths:', error); } } diff --git a/backend/src/services/learningObjects.ts b/backend/src/services/learningObjects.ts index bab246e0..52913169 100644 --- a/backend/src/services/learningObjects.ts +++ b/backend/src/services/learningObjects.ts @@ -64,14 +64,15 @@ export async function getLearningObjectsFromPath( return await Promise.all( learningPathData.nodes.map(async (node: LearningObjectNode) => { const metadataUrl = `${DWENGO_API_BASE}/learningObject/getMetadata?hruid=${node.learningobject_hruid}&version=${node.version}&language=${language}`; - const metadataResponse = await axios.get(metadataUrl); + const metadata = await fetchWithLogging( + metadataUrl, + `Metadata for Learning Object HRUID "${node.learningobject_hruid}" (version ${node.version}, language ${language})` + ); + + if (!metadata) return null; const htmlUrl = `${DWENGO_API_BASE}/learningObject/getRaw?hruid=${node.learningobject_hruid}&version=${node.version}&language=${language}`; - - return filterLearningObjectMetadata( - metadataResponse.data, - htmlUrl - ); + return filterLearningObjectMetadata(metadata, htmlUrl); }) ); } catch (error) { diff --git a/backend/src/services/learningPaths.ts b/backend/src/services/learningPaths.ts new file mode 100644 index 00000000..6a3039f7 --- /dev/null +++ b/backend/src/services/learningPaths.ts @@ -0,0 +1,45 @@ +import { fetchWithLogging } from "../util/apiHelper.js"; +import { DWENGO_API_BASE } from "../config/config.js"; + +interface LearningPathResponse { + success: boolean; + source: string; + data: any[] | null; + message?: string; +} + +export async function fetchLearningPaths( + hruids: string[], + language: string, + source: string +): Promise { + if (hruids.length === 0) { + return { + success: false, + source, + data: null, + message: `No HRUIDs provided for ${source}.`, + }; + } + + const apiUrl = `${DWENGO_API_BASE}/learningPath/getPathsFromIdList`; + const params = { pathIdList: JSON.stringify({ hruids }), language }; + + const learningPaths = await fetchWithLogging(apiUrl, `Learning paths for ${source}`, params); + + if (!learningPaths || learningPaths.length === 0) { + console.error(`⚠️ WARNING: No learning paths found for ${source}.`); + return { + success: false, + source, + data: [], + message: `No learning paths found for ${source}.`, + }; + } + + return { + success: true, + source, + data: learningPaths, + }; +} diff --git a/backend/src/util/apiHelper.ts b/backend/src/util/apiHelper.ts index 08e12b11..4c7c10fe 100644 --- a/backend/src/util/apiHelper.ts +++ b/backend/src/util/apiHelper.ts @@ -1,4 +1,4 @@ -import axios from 'axios'; +import axios, {AxiosRequestConfig} from 'axios'; // !!!! when logger is done -> change @@ -8,11 +8,18 @@ import axios from 'axios'; * * @param url The API endpoint to fetch from. * @param description A short description of what is being fetched (for logging). + * @param params * @returns The response data if successful, or null if an error occurs. */ -export async function fetchWithLogging(url: string, description: string): Promise { +export async function fetchWithLogging( + url: string, + description: string, + params?: Record +): Promise { try { - const response = await axios.get(url); + const config: AxiosRequestConfig = params ? { params } : {}; + + const response = await axios.get(url, config); return response.data; } catch (error: any) { if (error.response) { From ae4d76a84b415d255be9943d73c97f119ccd2776 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Sat, 1 Mar 2025 17:54:29 +0100 Subject: [PATCH 09/21] fix: 2 learning path routes gecombineerd, all en hruid lijst --- backend/src/routes/learningPaths.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/src/routes/learningPaths.ts b/backend/src/routes/learningPaths.ts index 17eb881d..e196c0d3 100644 --- a/backend/src/routes/learningPaths.ts +++ b/backend/src/routes/learningPaths.ts @@ -1,8 +1,7 @@ import express from 'express'; import { - getLearningPathsFromIds, + getLearningPaths, getLearningPathsByTheme, - getAllLearningPaths, searchLearningPaths, } from '../controllers/learningPaths.js'; @@ -10,14 +9,15 @@ const router = express.Router(); // DWENGO learning paths -// Query: hruids(list), language -// Route to fetch learning paths based on a list of HRUIDs -// Example: http://localhost:3000/learningPath?hruids=pn_werking&hruids=art1 -router.get('/', getLearningPathsFromIds); +// Unified route for fetching learning paths +// Route 1: Query: hruid (list), language +// Fetch learning paths based on hruid list +// Example 1: http://localhost:3000/learningPath?hruids=pn_werking&hruids=art1 -// Query: language -// Route to fetch all possible learning paths -router.get('/all', getAllLearningPaths); +// Route 2: no query +// Fetch all learning paths +// Example 2: http://localhost:3000/learningPath ( +router.get('/', getLearningPaths); // Query: language // Route to fetch learning paths based on a searchterm From 13f563bb56ffdae106181ab707460eb34ad13b53 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Sat, 1 Mar 2025 18:03:14 +0100 Subject: [PATCH 10/21] feat: interface toevoegingen voor het type any te vermijden --- backend/src/interfaces/learningPath.ts | 46 ++++++++++++++++++++++++++ backend/src/services/learningPaths.ts | 10 ++---- 2 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 backend/src/interfaces/learningPath.ts diff --git a/backend/src/interfaces/learningPath.ts b/backend/src/interfaces/learningPath.ts new file mode 100644 index 00000000..3260c6ea --- /dev/null +++ b/backend/src/interfaces/learningPath.ts @@ -0,0 +1,46 @@ +export interface LearningPathResponse { + success: boolean; + source: string; + data: any[] | null; + message?: string; +} + +export interface LearningPath { + _id: string; + language: string; + hruid: string; + title: string; + description: string; + image?: string; // Image might be missing, so it's optional + num_nodes: number; + num_nodes_left: number; + nodes: LearningObjectNode[]; + keywords: string; + target_ages: number[]; + min_age: number; + max_age: number; + __order: number; +} + + +interface LearningObjectNode { + _id: string; + learningobject_hruid: string; + version: number; + language: string; + start_node?: boolean; + transitions: Transition[]; + created_at: string; + updatedAt: string; +} + +interface Transition { + default: boolean; + _id: string; + next: { + _id: string; + hruid: string; + version: number; + language: string; + }; +} diff --git a/backend/src/services/learningPaths.ts b/backend/src/services/learningPaths.ts index 6a3039f7..a5a84b1d 100644 --- a/backend/src/services/learningPaths.ts +++ b/backend/src/services/learningPaths.ts @@ -1,12 +1,6 @@ import { fetchWithLogging } from "../util/apiHelper.js"; import { DWENGO_API_BASE } from "../config/config.js"; - -interface LearningPathResponse { - success: boolean; - source: string; - data: any[] | null; - message?: string; -} +import {LearningPath, LearningPathResponse} from "../interfaces/learningPath.js"; export async function fetchLearningPaths( hruids: string[], @@ -25,7 +19,7 @@ export async function fetchLearningPaths( const apiUrl = `${DWENGO_API_BASE}/learningPath/getPathsFromIdList`; const params = { pathIdList: JSON.stringify({ hruids }), language }; - const learningPaths = await fetchWithLogging(apiUrl, `Learning paths for ${source}`, params); + const learningPaths = await fetchWithLogging(apiUrl, `Learning paths for ${source}`, params); if (!learningPaths || learningPaths.length === 0) { console.error(`⚠️ WARNING: No learning paths found for ${source}.`); From 008e2e1c2c65ea9b82342629b3ce1a0e5ed31c7b Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Sat, 1 Mar 2025 18:24:21 +0100 Subject: [PATCH 11/21] feat: gebruik interface voor learning objects --- backend/src/interfaces/learningPath.ts | 60 +++++++++++++++++++++++-- backend/src/services/learningObjects.ts | 60 +++++++++---------------- 2 files changed, 77 insertions(+), 43 deletions(-) diff --git a/backend/src/interfaces/learningPath.ts b/backend/src/interfaces/learningPath.ts index 3260c6ea..6f187bec 100644 --- a/backend/src/interfaces/learningPath.ts +++ b/backend/src/interfaces/learningPath.ts @@ -1,7 +1,7 @@ export interface LearningPathResponse { success: boolean; source: string; - data: any[] | null; + data: LearningPath[] | null; message?: string; } @@ -22,8 +22,7 @@ export interface LearningPath { __order: number; } - -interface LearningObjectNode { +export interface LearningObjectNode { _id: string; learningobject_hruid: string; version: number; @@ -34,7 +33,7 @@ interface LearningObjectNode { updatedAt: string; } -interface Transition { +export interface Transition { default: boolean; _id: string; next: { @@ -44,3 +43,56 @@ interface Transition { language: string; }; } + +export interface LearningObjectMetadata { + _id: string; + uuid: string; + hruid: string; + version: number; + language: string; + title: string; + description: string; + difficulty: number; + estimated_time: number; + available: boolean; + teacher_exclusive: boolean; + educational_goals: EducationalGoal[]; + keywords: string[]; + target_ages: number[]; + content_type: string; // Markdown, image, etc. + content_location?: string; + skos_concepts?: string[]; + return_value?: ReturnValue; +} + +export interface EducationalGoal { + source: string; + id: string; +} + +export interface ReturnValue { + callback_url: string; + callback_schema: Record; +} + +export interface FilteredLearningObject { + key: string; + _id: string; + uuid: string; + version: number; + title: string; + htmlUrl: string; + language: string; + difficulty: number; + estimatedTime: number; + available: boolean; + teacherExclusive: boolean; + educationalGoals: EducationalGoal[]; + keywords: string[]; + description: string; + targetAges: number[]; + contentType: string; + contentLocation?: string; + skosConcepts?: string[]; + returnValue?: ReturnValue; +} diff --git a/backend/src/services/learningObjects.ts b/backend/src/services/learningObjects.ts index 52913169..9e1bb3a7 100644 --- a/backend/src/services/learningObjects.ts +++ b/backend/src/services/learningObjects.ts @@ -1,70 +1,51 @@ -import axios from 'axios'; import { DWENGO_API_BASE } from '../config/config.js'; import { fetchWithLogging } from "../util/apiHelper.js"; +import {FilteredLearningObject, LearningObjectMetadata, LearningObjectNode} from "../interfaces/learningPath.js"; +import {fetchLearningPaths} from "./learningPaths.js"; -interface LearningObjectNode { - _id: string; - learningobject_hruid: string; - version: number; - language: string; -} -function filterLearningObjectMetadata(data: any, htmlUrl: string) { +function filterLearningObjectMetadata(data: LearningObjectMetadata, htmlUrl: string) : FilteredLearningObject { return { - key: data.hruid, - // Hruid learningObject (not path) + key: data.hruid, // Hruid learningObject (not path) _id: data._id, uuid: data.uuid, version: data.version, - title: data.title, - htmlUrl, - // Url to fetch html content + htmlUrl, // Url to fetch html content language: data.language, difficulty: data.difficulty, estimatedTime: data.estimated_time, available: data.available, teacherExclusive: data.teacher_exclusive, - educationalGoals: data.educational_goals, - // List with learningObjects - keywords: data.keywords, - // For search - description: data.description, - // For search (not an actual description) + educationalGoals: data.educational_goals, // List with learningObjects + keywords: data.keywords, // For search + description: data.description, // For search (not an actual description) targetAges: data.target_ages, - contentType: data.content_type, - // Text/plain, text/md, image/image-block, - // Image/image/, audio/mpeg or extern - contentLocation: data.content_location, - // If content type extern - - // Skos concepts needed ?? - // Return value needed ?? - // Callback_url to send response - // Callback_scheme + contentType: data.content_type, // Markdown, image, audio, etc. + contentLocation: data.content_location, // If content type extern + skosConcepts: data.skos_concepts, + returnValue: data.return_value, // Callback response information }; } export async function getLearningObjectsFromPath( hruid: string, language: string -) { +): Promise { try { - const learningPathUrl = `${DWENGO_API_BASE}/learningPath/${hruid}/${language}`; - const learningPathData = await fetchWithLogging<{ nodes: LearningObjectNode[] }>( - learningPathUrl, - `Learning path for HRUID "${hruid}" in language "${language}"` - ); + const learningPathResponse = await fetchLearningPaths([hruid], language, `Learning path for HRUID "${hruid}"`); - if (!learningPathData || !learningPathData.nodes || learningPathData.nodes.length === 0) { + if (!learningPathResponse.success || !learningPathResponse.data || learningPathResponse.data.length === 0) { console.error(`⚠️ WARNING: Learning path "${hruid}" exists but contains no learning objects.`); return []; } + const nodes: LearningObjectNode[] = learningPathResponse.data[0].nodes; + return await Promise.all( - learningPathData.nodes.map(async (node: LearningObjectNode) => { + nodes.map(async (node) => { const metadataUrl = `${DWENGO_API_BASE}/learningObject/getMetadata?hruid=${node.learningobject_hruid}&version=${node.version}&language=${language}`; - const metadata = await fetchWithLogging( + const metadata = await fetchWithLogging( metadataUrl, `Metadata for Learning Object HRUID "${node.learningobject_hruid}" (version ${node.version}, language ${language})` ); @@ -74,8 +55,9 @@ export async function getLearningObjectsFromPath( const htmlUrl = `${DWENGO_API_BASE}/learningObject/getRaw?hruid=${node.learningobject_hruid}&version=${node.version}&language=${language}`; return filterLearningObjectMetadata(metadata, htmlUrl); }) - ); + ).then((objects) => objects.filter((obj): obj is FilteredLearningObject => obj !== null)); } catch (error) { console.error('Error fetching learning objects:', error); + return []; } } From 6bb8c364b93f99861c999dc08c94d0081b90e84d Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Sat, 1 Mar 2025 18:31:38 +0100 Subject: [PATCH 12/21] feat: toevoeging fallback language constante --- backend/src/{config => }/config.ts | 2 ++ backend/src/controllers/learningObjects.ts | 3 ++- backend/src/controllers/learningPaths.ts | 8 ++++---- backend/src/controllers/themes.ts | 3 ++- backend/src/services/learningObjects.ts | 2 +- backend/src/services/learningPaths.ts | 2 +- 6 files changed, 12 insertions(+), 8 deletions(-) rename backend/src/{config => }/config.ts (83%) diff --git a/backend/src/config/config.ts b/backend/src/config.ts similarity index 83% rename from backend/src/config/config.ts rename to backend/src/config.ts index 84181070..2de2920b 100644 --- a/backend/src/config/config.ts +++ b/backend/src/config.ts @@ -6,3 +6,5 @@ // Dotenv.config(); export const DWENGO_API_BASE = 'https://dwengo.org/backend/api'; + +export const FALLBACK_LANG = "nl"; diff --git a/backend/src/controllers/learningObjects.ts b/backend/src/controllers/learningObjects.ts index f7640891..91a1a3b1 100644 --- a/backend/src/controllers/learningObjects.ts +++ b/backend/src/controllers/learningObjects.ts @@ -1,5 +1,6 @@ import { Request, Response } from 'express'; import { getLearningObjectsFromPath } from '../services/learningObjects.js'; +import {FALLBACK_LANG} from "../config"; export async function getAllLearningObjects( req: Request, @@ -7,7 +8,7 @@ export async function getAllLearningObjects( ): Promise { try { const { hruid } = req.params; - const language = (req.query.language as string) || 'nl'; // Default to Dutch; + const language = (req.query.language as string) || FALLBACK_LANG; if (!language) { res.status(400).json({ diff --git a/backend/src/controllers/learningPaths.ts b/backend/src/controllers/learningPaths.ts index 880858f3..cf084e2b 100644 --- a/backend/src/controllers/learningPaths.ts +++ b/backend/src/controllers/learningPaths.ts @@ -1,6 +1,6 @@ import { Request, Response } from 'express'; import { themes } from '../data/themes.js'; -import { DWENGO_API_BASE } from '../config/config.js'; +import {DWENGO_API_BASE, FALLBACK_LANG} from '../config.js'; import { fetchWithLogging } from "../util/apiHelper.js"; import { fetchLearningPaths } from "../services/learningPaths.js"; @@ -12,7 +12,7 @@ import { fetchLearningPaths } from "../services/learningPaths.js"; export async function getLearningPaths(req: Request, res: Response): Promise { try { const hruids = req.query.hruids; // Can be string or array - const language = (req.query.language as string) || 'nl'; + const language = (req.query.language as string) || FALLBACK_LANG; let hruidList: string[] = []; @@ -39,7 +39,7 @@ export async function getLearningPaths(req: Request, res: Response): Promise { try { const themeKey = req.params.theme; - const language = (req.query.language as string) || 'nl'; + const language = (req.query.language as string) || FALLBACK_LANG; const theme = themes.find((t) => t.title === themeKey); if (!theme) { @@ -65,7 +65,7 @@ export async function getLearningPathsByTheme(req: Request, res: Response): Prom 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'; + const language = (req.query.language as string) || FALLBACK_LANG; if (!query) { res.status(400).json({ error: 'Missing search query' }); diff --git a/backend/src/controllers/themes.ts b/backend/src/controllers/themes.ts index d6c67107..f4e24369 100644 --- a/backend/src/controllers/themes.ts +++ b/backend/src/controllers/themes.ts @@ -3,6 +3,7 @@ import path from 'path'; import yaml from 'js-yaml'; import { Request, Response } from 'express'; import { themes } from '../data/themes.js'; +import {FALLBACK_LANG} from "../config"; interface Translations { curricula_page: { @@ -26,7 +27,7 @@ function loadTranslations(language: string): Translations { } export function getThemes(req: Request, res: Response) { - const language = (req.query.language as string)?.toLowerCase() || 'nl'; + const language = (req.query.language as string)?.toLowerCase() || FALLBACK_LANG; const translations = loadTranslations(language); const themeList = themes.map((theme) => { diff --git a/backend/src/services/learningObjects.ts b/backend/src/services/learningObjects.ts index 9e1bb3a7..e753ad26 100644 --- a/backend/src/services/learningObjects.ts +++ b/backend/src/services/learningObjects.ts @@ -1,4 +1,4 @@ -import { DWENGO_API_BASE } from '../config/config.js'; +import { DWENGO_API_BASE } from '../config.js'; import { fetchWithLogging } from "../util/apiHelper.js"; import {FilteredLearningObject, LearningObjectMetadata, LearningObjectNode} from "../interfaces/learningPath.js"; import {fetchLearningPaths} from "./learningPaths.js"; diff --git a/backend/src/services/learningPaths.ts b/backend/src/services/learningPaths.ts index a5a84b1d..0bff4f92 100644 --- a/backend/src/services/learningPaths.ts +++ b/backend/src/services/learningPaths.ts @@ -1,5 +1,5 @@ import { fetchWithLogging } from "../util/apiHelper.js"; -import { DWENGO_API_BASE } from "../config/config.js"; +import { DWENGO_API_BASE } from "../config.js"; import {LearningPath, LearningPathResponse} from "../interfaces/learningPath.js"; export async function fetchLearningPaths( From 5da0720a08fc0827080960b3c7fe4cce1ccb6cb1 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Sat, 1 Mar 2025 18:40:32 +0100 Subject: [PATCH 13/21] fix: lint --- backend/src/controllers/learningPaths.ts | 9 ++-- backend/src/interfaces/learningPath.ts | 66 ++++++++++++------------ backend/src/services/learningObjects.ts | 4 +- 3 files changed, 40 insertions(+), 39 deletions(-) diff --git a/backend/src/controllers/learningPaths.ts b/backend/src/controllers/learningPaths.ts index cf084e2b..8c86f0d2 100644 --- a/backend/src/controllers/learningPaths.ts +++ b/backend/src/controllers/learningPaths.ts @@ -3,6 +3,7 @@ import { themes } from '../data/themes.js'; import {DWENGO_API_BASE, FALLBACK_LANG} from '../config.js'; import { fetchWithLogging } from "../util/apiHelper.js"; import { fetchLearningPaths } from "../services/learningPaths.js"; +import {LearningPath} from "../interfaces/learningPath"; /** * Fetch learning paths based on HRUIDs or return all if no HRUIDs are provided. @@ -14,13 +15,13 @@ export async function getLearningPaths(req: Request, res: Response): Promise theme.hruids); + hruidList = themes.flatMap((theme) => {return theme.hruids}); } const learningPaths = await fetchLearningPaths(hruidList, language, `HRUIDs: ${hruidList.join(', ')}`); @@ -41,7 +42,7 @@ export async function getLearningPathsByTheme(req: Request, res: Response): Prom const themeKey = req.params.theme; const language = (req.query.language as string) || FALLBACK_LANG; - const theme = themes.find((t) => t.title === themeKey); + const theme = themes.find((t) => {return t.title === themeKey}); if (!theme) { console.error(`⚠️ WARNING: Theme "${themeKey}" not found.`); res.status(404).json({ error: 'Theme not found' }); @@ -75,7 +76,7 @@ export async function searchLearningPaths(req: Request, res: Response): Promise< const apiUrl = `${DWENGO_API_BASE}/learningPath/search`; const params = { all: query, language }; - const searchResults = await fetchWithLogging(apiUrl, `Search learning paths with query "${query}"`, params); + const searchResults = await fetchWithLogging(apiUrl, `Search learning paths with query "${query}"`, params); res.json(searchResults ?? []); } catch (error) { console.error('❌ Unexpected error searching learning paths:', error); diff --git a/backend/src/interfaces/learningPath.ts b/backend/src/interfaces/learningPath.ts index 6f187bec..1e2cc6ef 100644 --- a/backend/src/interfaces/learningPath.ts +++ b/backend/src/interfaces/learningPath.ts @@ -1,8 +1,23 @@ -export interface LearningPathResponse { - success: boolean; - source: string; - data: LearningPath[] | null; - message?: string; +export interface Transition { + default: boolean; + _id: string; + next: { + _id: string; + hruid: string; + version: number; + language: string; + }; +} + +export interface LearningObjectNode { + _id: string; + learningobject_hruid: string; + version: number; + language: string; + start_node?: boolean; + transitions: Transition[]; + created_at: string; + updatedAt: string; } export interface LearningPath { @@ -22,26 +37,14 @@ export interface LearningPath { __order: number; } -export interface LearningObjectNode { - _id: string; - learningobject_hruid: string; - version: number; - language: string; - start_node?: boolean; - transitions: Transition[]; - created_at: string; - updatedAt: string; +export interface EducationalGoal { + source: string; + id: string; } -export interface Transition { - default: boolean; - _id: string; - next: { - _id: string; - hruid: string; - version: number; - language: string; - }; +export interface ReturnValue { + callback_url: string; + callback_schema: Record; } export interface LearningObjectMetadata { @@ -65,16 +68,6 @@ export interface LearningObjectMetadata { return_value?: ReturnValue; } -export interface EducationalGoal { - source: string; - id: string; -} - -export interface ReturnValue { - callback_url: string; - callback_schema: Record; -} - export interface FilteredLearningObject { key: string; _id: string; @@ -96,3 +89,10 @@ export interface FilteredLearningObject { skosConcepts?: string[]; returnValue?: ReturnValue; } + +export interface LearningPathResponse { + success: boolean; + source: string; + data: LearningPath[] | null; + message?: string; +} diff --git a/backend/src/services/learningObjects.ts b/backend/src/services/learningObjects.ts index e753ad26..f4da4faf 100644 --- a/backend/src/services/learningObjects.ts +++ b/backend/src/services/learningObjects.ts @@ -50,12 +50,12 @@ export async function getLearningObjectsFromPath( `Metadata for Learning Object HRUID "${node.learningobject_hruid}" (version ${node.version}, language ${language})` ); - if (!metadata) return null; + if (!metadata) {return null;} const htmlUrl = `${DWENGO_API_BASE}/learningObject/getRaw?hruid=${node.learningobject_hruid}&version=${node.version}&language=${language}`; return filterLearningObjectMetadata(metadata, htmlUrl); }) - ).then((objects) => objects.filter((obj): obj is FilteredLearningObject => obj !== null)); + ).then((objects) => {return objects.filter((obj): obj is FilteredLearningObject => {return obj !== null})}); } catch (error) { console.error('Error fetching learning objects:', error); return []; From 887787de05e2200d44b4f60ce18beaca2054d0e6 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Sat, 1 Mar 2025 18:42:45 +0100 Subject: [PATCH 14/21] fix: format --- backend/src/config.ts | 2 +- backend/src/controllers/learningObjects.ts | 2 +- backend/src/controllers/learningPaths.ts | 59 ++++++++++++++++------ backend/src/controllers/themes.ts | 5 +- backend/src/routes/learningObjects.ts | 2 +- backend/src/services/learningObjects.ts | 44 ++++++++++++---- backend/src/services/learningPaths.ts | 15 ++++-- backend/src/util/apiHelper.ts | 19 ++++--- 8 files changed, 107 insertions(+), 41 deletions(-) diff --git a/backend/src/config.ts b/backend/src/config.ts index 2de2920b..8fd8ec3f 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -7,4 +7,4 @@ export const DWENGO_API_BASE = 'https://dwengo.org/backend/api'; -export const FALLBACK_LANG = "nl"; +export const FALLBACK_LANG = 'nl'; diff --git a/backend/src/controllers/learningObjects.ts b/backend/src/controllers/learningObjects.ts index 91a1a3b1..2d733d97 100644 --- a/backend/src/controllers/learningObjects.ts +++ b/backend/src/controllers/learningObjects.ts @@ -1,6 +1,6 @@ import { Request, Response } from 'express'; import { getLearningObjectsFromPath } from '../services/learningObjects.js'; -import {FALLBACK_LANG} from "../config"; +import { FALLBACK_LANG } from '../config'; export async function getAllLearningObjects( req: Request, diff --git a/backend/src/controllers/learningPaths.ts b/backend/src/controllers/learningPaths.ts index 8c86f0d2..c819b072 100644 --- a/backend/src/controllers/learningPaths.ts +++ b/backend/src/controllers/learningPaths.ts @@ -1,16 +1,19 @@ import { Request, Response } from 'express'; import { themes } from '../data/themes.js'; -import {DWENGO_API_BASE, FALLBACK_LANG} from '../config.js'; -import { fetchWithLogging } from "../util/apiHelper.js"; -import { fetchLearningPaths } from "../services/learningPaths.js"; -import {LearningPath} from "../interfaces/learningPath"; +import { DWENGO_API_BASE, FALLBACK_LANG } from '../config.js'; +import { fetchWithLogging } from '../util/apiHelper.js'; +import { fetchLearningPaths } from '../services/learningPaths.js'; +import { LearningPath } from '../interfaces/learningPath'; /** * Fetch learning paths based on HRUIDs or return all if no HRUIDs are provided. * - If `hruids` are given -> fetch specific learning paths. * - If `hruids` is missing -> return all available learning paths. */ -export async function getLearningPaths(req: Request, res: Response): Promise { +export async function getLearningPaths( + req: Request, + res: Response +): Promise { try { const hruids = req.query.hruids; // Can be string or array const language = (req.query.language as string) || FALLBACK_LANG; @@ -18,13 +21,21 @@ export async function getLearningPaths(req: Request, res: Response): Promise {return theme.hruids}); + hruidList = themes.flatMap((theme) => { + return theme.hruids; + }); } - const learningPaths = await fetchLearningPaths(hruidList, language, `HRUIDs: ${hruidList.join(', ')}`); + const learningPaths = await fetchLearningPaths( + hruidList, + language, + `HRUIDs: ${hruidList.join(', ')}` + ); res.json(learningPaths); } catch (error) { @@ -33,37 +44,51 @@ export async function getLearningPaths(req: Request, res: Response): Promise { +export async function getLearningPathsByTheme( + req: Request, + res: Response +): Promise { try { const themeKey = req.params.theme; const language = (req.query.language as string) || FALLBACK_LANG; - const theme = themes.find((t) => {return t.title === themeKey}); + const theme = themes.find((t) => { + return t.title === themeKey; + }); if (!theme) { console.error(`⚠️ WARNING: Theme "${themeKey}" not found.`); res.status(404).json({ error: 'Theme not found' }); return; } - const response = await fetchLearningPaths(theme.hruids, language, `theme "${themeKey}"`); + const response = await fetchLearningPaths( + theme.hruids, + language, + `theme "${themeKey}"` + ); res.json({ theme: themeKey, hruids: theme.hruids, ...response, }); } catch (error) { - console.error('❌ Unexpected error fetching learning paths by theme:', error); + console.error( + '❌ Unexpected error fetching learning paths by theme:', + error + ); } } /** * Search learning paths by query. */ -export async function searchLearningPaths(req: Request, res: Response): Promise { +export async function searchLearningPaths( + req: Request, + res: Response +): Promise { try { const query = req.query.query as string; const language = (req.query.language as string) || FALLBACK_LANG; @@ -76,7 +101,11 @@ export async function searchLearningPaths(req: Request, res: Response): Promise< const apiUrl = `${DWENGO_API_BASE}/learningPath/search`; const params = { all: query, language }; - const searchResults = await fetchWithLogging(apiUrl, `Search learning paths with query "${query}"`, params); + const searchResults = await fetchWithLogging( + apiUrl, + `Search learning paths with query "${query}"`, + params + ); res.json(searchResults ?? []); } catch (error) { console.error('❌ Unexpected error searching learning paths:', error); diff --git a/backend/src/controllers/themes.ts b/backend/src/controllers/themes.ts index f4e24369..62564485 100644 --- a/backend/src/controllers/themes.ts +++ b/backend/src/controllers/themes.ts @@ -3,7 +3,7 @@ import path from 'path'; import yaml from 'js-yaml'; import { Request, Response } from 'express'; import { themes } from '../data/themes.js'; -import {FALLBACK_LANG} from "../config"; +import { FALLBACK_LANG } from '../config'; interface Translations { curricula_page: { @@ -27,7 +27,8 @@ function loadTranslations(language: string): Translations { } export function getThemes(req: Request, res: Response) { - const language = (req.query.language as string)?.toLowerCase() || FALLBACK_LANG; + const language = + (req.query.language as string)?.toLowerCase() || FALLBACK_LANG; const translations = loadTranslations(language); const themeList = themes.map((theme) => { diff --git a/backend/src/routes/learningObjects.ts b/backend/src/routes/learningObjects.ts index 1d72daaf..ec702c6d 100644 --- a/backend/src/routes/learningObjects.ts +++ b/backend/src/routes/learningObjects.ts @@ -8,7 +8,7 @@ const router = express.Router(); // Arg: hruid learningPath // Query: language // Route to fetch list of learning objects based on hruid of learning path -// example: http://localhost:3000/learningObject/un_artificiele_intelligentie +// Example: http://localhost:3000/learningObject/un_artificiele_intelligentie router.get('/:hruid', getAllLearningObjects); export default router; diff --git a/backend/src/services/learningObjects.ts b/backend/src/services/learningObjects.ts index f4da4faf..a2243a9a 100644 --- a/backend/src/services/learningObjects.ts +++ b/backend/src/services/learningObjects.ts @@ -1,10 +1,16 @@ import { DWENGO_API_BASE } from '../config.js'; -import { fetchWithLogging } from "../util/apiHelper.js"; -import {FilteredLearningObject, LearningObjectMetadata, LearningObjectNode} from "../interfaces/learningPath.js"; -import {fetchLearningPaths} from "./learningPaths.js"; +import { fetchWithLogging } from '../util/apiHelper.js'; +import { + FilteredLearningObject, + LearningObjectMetadata, + LearningObjectNode, +} from '../interfaces/learningPath.js'; +import { fetchLearningPaths } from './learningPaths.js'; - -function filterLearningObjectMetadata(data: LearningObjectMetadata, htmlUrl: string) : FilteredLearningObject { +function filterLearningObjectMetadata( + data: LearningObjectMetadata, + htmlUrl: string +): FilteredLearningObject { return { key: data.hruid, // Hruid learningObject (not path) _id: data._id, @@ -31,12 +37,22 @@ function filterLearningObjectMetadata(data: LearningObjectMetadata, htmlUrl: str export async function getLearningObjectsFromPath( hruid: string, language: string -): Promise { +): Promise { try { - const learningPathResponse = await fetchLearningPaths([hruid], language, `Learning path for HRUID "${hruid}"`); + const learningPathResponse = await fetchLearningPaths( + [hruid], + language, + `Learning path for HRUID "${hruid}"` + ); - if (!learningPathResponse.success || !learningPathResponse.data || learningPathResponse.data.length === 0) { - console.error(`⚠️ WARNING: Learning path "${hruid}" exists but contains no learning objects.`); + if ( + !learningPathResponse.success || + !learningPathResponse.data || + learningPathResponse.data.length === 0 + ) { + console.error( + `⚠️ WARNING: Learning path "${hruid}" exists but contains no learning objects.` + ); return []; } @@ -50,12 +66,18 @@ export async function getLearningObjectsFromPath( `Metadata for Learning Object HRUID "${node.learningobject_hruid}" (version ${node.version}, language ${language})` ); - if (!metadata) {return null;} + if (!metadata) { + return null; + } const htmlUrl = `${DWENGO_API_BASE}/learningObject/getRaw?hruid=${node.learningobject_hruid}&version=${node.version}&language=${language}`; return filterLearningObjectMetadata(metadata, htmlUrl); }) - ).then((objects) => {return objects.filter((obj): obj is FilteredLearningObject => {return obj !== null})}); + ).then((objects) => { + return objects.filter((obj): obj is FilteredLearningObject => { + return obj !== null; + }); + }); } catch (error) { console.error('Error fetching learning objects:', error); return []; diff --git a/backend/src/services/learningPaths.ts b/backend/src/services/learningPaths.ts index 0bff4f92..734207db 100644 --- a/backend/src/services/learningPaths.ts +++ b/backend/src/services/learningPaths.ts @@ -1,6 +1,9 @@ -import { fetchWithLogging } from "../util/apiHelper.js"; -import { DWENGO_API_BASE } from "../config.js"; -import {LearningPath, LearningPathResponse} from "../interfaces/learningPath.js"; +import { fetchWithLogging } from '../util/apiHelper.js'; +import { DWENGO_API_BASE } from '../config.js'; +import { + LearningPath, + LearningPathResponse, +} from '../interfaces/learningPath.js'; export async function fetchLearningPaths( hruids: string[], @@ -19,7 +22,11 @@ export async function fetchLearningPaths( const apiUrl = `${DWENGO_API_BASE}/learningPath/getPathsFromIdList`; const params = { pathIdList: JSON.stringify({ hruids }), language }; - const learningPaths = await fetchWithLogging(apiUrl, `Learning paths for ${source}`, params); + const learningPaths = await fetchWithLogging( + apiUrl, + `Learning paths for ${source}`, + params + ); if (!learningPaths || learningPaths.length === 0) { console.error(`⚠️ WARNING: No learning paths found for ${source}.`); diff --git a/backend/src/util/apiHelper.ts b/backend/src/util/apiHelper.ts index 4c7c10fe..76d166c8 100644 --- a/backend/src/util/apiHelper.ts +++ b/backend/src/util/apiHelper.ts @@ -1,4 +1,4 @@ -import axios, {AxiosRequestConfig} from 'axios'; +import axios, { AxiosRequestConfig } from 'axios'; // !!!! when logger is done -> change @@ -14,8 +14,8 @@ import axios, {AxiosRequestConfig} from 'axios'; export async function fetchWithLogging( url: string, description: string, - params?: Record -): Promise { + params?: Record +): Promise { try { const config: AxiosRequestConfig = params ? { params } : {}; @@ -24,12 +24,19 @@ export async function fetchWithLogging( } catch (error: any) { if (error.response) { if (error.response.status === 404) { - console.error(`❌ ERROR: ${description} not found (404) at "${url}".`); + console.error( + `❌ ERROR: ${description} not found (404) at "${url}".` + ); } else { - console.error(`❌ ERROR: Failed to fetch ${description}. Status: ${error.response.status} - ${error.response.statusText} (URL: "${url}")`); + console.error( + `❌ ERROR: Failed to fetch ${description}. Status: ${error.response.status} - ${error.response.statusText} (URL: "${url}")` + ); } } else { - console.error(`❌ ERROR: Network or unexpected error when fetching ${description}:`, error.message); + console.error( + `❌ ERROR: Network or unexpected error when fetching ${description}:`, + error.message + ); } return null; } From 4642040315ed35687ee647b1d475bc6a428b8ffb Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sat, 1 Mar 2025 22:35:18 +0100 Subject: [PATCH 15/21] fix: .js toevoegen aan imports --- backend/src/app.ts | 14 +++++++------- backend/src/controllers/learningObjects.ts | 2 +- backend/src/controllers/themes.ts | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/src/app.ts b/backend/src/app.ts index cd0e715b..b21bb9f1 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -6,13 +6,13 @@ import themeRoutes from './routes/themes.js'; import learningPathRoutes from './routes/learningPaths.js'; import learningObjectRoutes from './routes/learningObjects.js'; -import studentRouter from './routes/student'; -import groupRouter from './routes/group'; -import assignmentRouter from './routes/assignment'; -import submissionRouter from './routes/submission'; -import classRouter from './routes/class'; -import questionRouter from './routes/question'; -import loginRouter from './routes/login'; +import studentRouter from './routes/student.js'; +import groupRouter from './routes/group.js'; +import assignmentRouter from './routes/assignment.js'; +import submissionRouter from './routes/submission.js'; +import classRouter from './routes/class.js'; +import questionRouter from './routes/question.js'; +import loginRouter from './routes/login.js'; const app: Express = express(); const port: string | number = getNumericEnvVar(EnvVars.Port); diff --git a/backend/src/controllers/learningObjects.ts b/backend/src/controllers/learningObjects.ts index 2d733d97..d0df24fd 100644 --- a/backend/src/controllers/learningObjects.ts +++ b/backend/src/controllers/learningObjects.ts @@ -1,6 +1,6 @@ import { Request, Response } from 'express'; import { getLearningObjectsFromPath } from '../services/learningObjects.js'; -import { FALLBACK_LANG } from '../config'; +import { FALLBACK_LANG } from '../config.js'; export async function getAllLearningObjects( req: Request, diff --git a/backend/src/controllers/themes.ts b/backend/src/controllers/themes.ts index 62564485..817464ab 100644 --- a/backend/src/controllers/themes.ts +++ b/backend/src/controllers/themes.ts @@ -3,7 +3,7 @@ import path from 'path'; import yaml from 'js-yaml'; import { Request, Response } from 'express'; import { themes } from '../data/themes.js'; -import { FALLBACK_LANG } from '../config'; +import { FALLBACK_LANG } from '../config.js'; interface Translations { curricula_page: { From 79009de80cc6700aedfd3b64f8f6e3c6e07020cf Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Sat, 1 Mar 2025 22:40:42 +0100 Subject: [PATCH 16/21] fix: voeg js toe bij config import --- backend/src/controllers/learningObjects.ts | 2 +- backend/src/controllers/themes.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/controllers/learningObjects.ts b/backend/src/controllers/learningObjects.ts index 2d733d97..d0df24fd 100644 --- a/backend/src/controllers/learningObjects.ts +++ b/backend/src/controllers/learningObjects.ts @@ -1,6 +1,6 @@ import { Request, Response } from 'express'; import { getLearningObjectsFromPath } from '../services/learningObjects.js'; -import { FALLBACK_LANG } from '../config'; +import { FALLBACK_LANG } from '../config.js'; export async function getAllLearningObjects( req: Request, diff --git a/backend/src/controllers/themes.ts b/backend/src/controllers/themes.ts index 62564485..817464ab 100644 --- a/backend/src/controllers/themes.ts +++ b/backend/src/controllers/themes.ts @@ -3,7 +3,7 @@ import path from 'path'; import yaml from 'js-yaml'; import { Request, Response } from 'express'; import { themes } from '../data/themes.js'; -import { FALLBACK_LANG } from '../config'; +import { FALLBACK_LANG } from '../config.js'; interface Translations { curricula_page: { From 484120ab50a05288f3b03072c5e25b7378a9f8d8 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Sun, 2 Mar 2025 11:04:30 +0100 Subject: [PATCH 17/21] feat: extra learning object routes --- backend/src/routes/learningObjects.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/backend/src/routes/learningObjects.ts b/backend/src/routes/learningObjects.ts index ec702c6d..42e72bca 100644 --- a/backend/src/routes/learningObjects.ts +++ b/backend/src/routes/learningObjects.ts @@ -1,14 +1,25 @@ import express from 'express'; -import { getAllLearningObjects } from '../controllers/learningObjects.js'; +import {getAllLearningObjects, getLearningObject} from '../controllers/learningObjects.js'; const router = express.Router(); // DWENGO learning objects -// Arg: hruid learningPath -// Query: language +// Queries: hruid(path), full, language // Route to fetch list of learning objects based on hruid of learning path -// Example: http://localhost:3000/learningObject/un_artificiele_intelligentie -router.get('/:hruid', getAllLearningObjects); + +// Route 1: list of object hruids +// Example 1: http://localhost:3000/learningObject?hruid=un_artificiele_intelligentie + +// Route 2: list of object data +// Example 2: http://localhost:3000/learningObject?full=true&hruid=un_artificiele_intelligentie +router.get('/', getAllLearningObjects); + + +// Parameter: hruid of learning object +// Query: language +// Route to fetch data of one learning object based on its hruid +// Example: http://localhost:3000/learningObject/un_ai7 +router.get('/:hruid', getLearningObject); export default router; From 3b689d8e09cd791e864387e0b5f4034aca12a13c Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Sun, 2 Mar 2025 11:05:06 +0100 Subject: [PATCH 18/21] fix: afsplitsing functies objects --- backend/src/controllers/learningObjects.ts | 52 +++++++++--- backend/src/services/learningObjects.ts | 93 ++++++++++++++-------- 2 files changed, 100 insertions(+), 45 deletions(-) diff --git a/backend/src/controllers/learningObjects.ts b/backend/src/controllers/learningObjects.ts index d0df24fd..026d37ef 100644 --- a/backend/src/controllers/learningObjects.ts +++ b/backend/src/controllers/learningObjects.ts @@ -1,29 +1,57 @@ import { Request, Response } from 'express'; -import { getLearningObjectsFromPath } from '../services/learningObjects.js'; +import { + getLearningObjectById, + getLearningObjectIdsFromPath, + getLearningObjectsFromPath +} from '../services/learningObjects.js'; import { FALLBACK_LANG } from '../config.js'; +import {FilteredLearningObject} from "../interfaces/learningPath"; export async function getAllLearningObjects( req: Request, res: Response +): Promise { + try { + const hruid = (req.query.hruid as string); + const full = req.query.full === 'true'; + const language = (req.query.language as string) || FALLBACK_LANG; + + if (!hruid) { + res.status(400).json({ error: 'HRUID query is required.' }); + return; + } + + let learningObjects: FilteredLearningObject[] | string[]; + if (full) + learningObjects = await getLearningObjectsFromPath(hruid,language); + else + learningObjects = await getLearningObjectIdsFromPath(hruid, language) + + res.json(learningObjects); + } catch (error) { + console.error('Error fetching learning objects:', error); + res.status(500).json({ error: 'Internal server error' }); + } +} + + +export async function getLearningObject( + req: Request, + res: Response ): Promise { try { const { hruid } = req.params; const language = (req.query.language as string) || FALLBACK_LANG; - if (!language) { - res.status(400).json({ - error: 'Language query parameter is required.', - }); + if (!hruid) { + res.status(400).json({ error: 'HRUID parameter is required.' }); return; } - const learningObjects = await getLearningObjectsFromPath( - hruid, - language - ); - res.json(learningObjects); - } catch (error) { - console.error('Error fetching learning objects:', error); + const learningObject = await getLearningObjectById(hruid, language); + res.json(learningObject); + } catch (error){ + console.error('Error fetching learning object:', error); res.status(500).json({ error: 'Internal server error' }); } } diff --git a/backend/src/services/learningObjects.ts b/backend/src/services/learningObjects.ts index a2243a9a..b184f1fd 100644 --- a/backend/src/services/learningObjects.ts +++ b/backend/src/services/learningObjects.ts @@ -3,11 +3,11 @@ import { fetchWithLogging } from '../util/apiHelper.js'; import { FilteredLearningObject, LearningObjectMetadata, - LearningObjectNode, + LearningObjectNode, LearningPathResponse, } from '../interfaces/learningPath.js'; import { fetchLearningPaths } from './learningPaths.js'; -function filterLearningObjectMetadata( +function filterData( data: LearningObjectMetadata, htmlUrl: string ): FilteredLearningObject { @@ -34,52 +34,79 @@ function filterLearningObjectMetadata( }; } -export async function getLearningObjectsFromPath( +/** + * Fetches a single learning object by its HRUID + */ +export async function getLearningObjectById( hruid: string, language: string -): Promise { +): Promise { + const metadataUrl = `${DWENGO_API_BASE}/learningObject/getMetadata?hruid=${hruid}&language=${language}`; + const metadata = await fetchWithLogging( + metadataUrl, + `Metadata for Learning Object HRUID "${hruid}" (language ${language})` + ); + + if (!metadata) { + console.error(`⚠️ WARNING: Learning object "${hruid}" not found.`); + return null; + } + + const htmlUrl = `${DWENGO_API_BASE}/learningObject/getRaw?hruid=${hruid}&language=${language}`; + return filterData(metadata, htmlUrl); +} + +/** + * Generic function to fetch learning objects (full data or just HRUIDs) + */ +async function fetchLearningObjects( + hruid: string, + full: boolean, + language: string +): Promise { try { - const learningPathResponse = await fetchLearningPaths( + const learningPathResponse: LearningPathResponse = await fetchLearningPaths( [hruid], language, `Learning path for HRUID "${hruid}"` ); - if ( - !learningPathResponse.success || - !learningPathResponse.data || - learningPathResponse.data.length === 0 - ) { - console.error( - `⚠️ WARNING: Learning path "${hruid}" exists but contains no learning objects.` - ); + if (!learningPathResponse.success || !learningPathResponse.data?.length) { + console.error(`⚠️ WARNING: Learning path "${hruid}" exists but contains no learning objects.`); return []; } const nodes: LearningObjectNode[] = learningPathResponse.data[0].nodes; + if (!full) { + return nodes.map(node => node.learningobject_hruid); + } + return await Promise.all( - nodes.map(async (node) => { - const metadataUrl = `${DWENGO_API_BASE}/learningObject/getMetadata?hruid=${node.learningobject_hruid}&version=${node.version}&language=${language}`; - const metadata = await fetchWithLogging( - metadataUrl, - `Metadata for Learning Object HRUID "${node.learningobject_hruid}" (version ${node.version}, language ${language})` - ); - - if (!metadata) { - return null; - } - - const htmlUrl = `${DWENGO_API_BASE}/learningObject/getRaw?hruid=${node.learningobject_hruid}&version=${node.version}&language=${language}`; - return filterLearningObjectMetadata(metadata, htmlUrl); - }) - ).then((objects) => { - return objects.filter((obj): obj is FilteredLearningObject => { - return obj !== null; - }); - }); + nodes.map(async (node) => getLearningObjectById(node.learningobject_hruid, language)) + ).then((objects) => objects.filter((obj): obj is FilteredLearningObject => obj !== null)); } catch (error) { - console.error('Error fetching learning objects:', error); + console.error('❌ Error fetching learning objects:', error); return []; } } + +/** + * Fetch full learning object data (metadata) + */ +export async function getLearningObjectsFromPath( + hruid: string, + language: string +): Promise { + return await fetchLearningObjects(hruid, true, language) as FilteredLearningObject[]; +} + +/** + * Fetch only learning object HRUIDs + */ +export async function getLearningObjectIdsFromPath( + hruid: string, + language: string +): Promise { + return await fetchLearningObjects(hruid, false, language) as string[]; +} From 6bf4193da9015efb162ccbca1ce1da9784f71221 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Sun, 2 Mar 2025 12:10:59 +0100 Subject: [PATCH 19/21] fix: queries in routes van leer paden --- backend/src/routes/learningPaths.ts | 40 ++++++++++++----------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/backend/src/routes/learningPaths.ts b/backend/src/routes/learningPaths.ts index e196c0d3..ce580745 100644 --- a/backend/src/routes/learningPaths.ts +++ b/backend/src/routes/learningPaths.ts @@ -1,33 +1,27 @@ import express from 'express'; -import { - getLearningPaths, - getLearningPathsByTheme, - searchLearningPaths, -} from '../controllers/learningPaths.js'; +import { getLearningPaths } from '../controllers/learningPaths.js'; const router = express.Router(); // DWENGO learning paths -// Unified route for fetching learning paths -// Route 1: Query: hruid (list), language -// Fetch learning paths based on hruid list -// Example 1: http://localhost:3000/learningPath?hruids=pn_werking&hruids=art1 - -// Route 2: no query +// Route 1: no query // Fetch all learning paths -// Example 2: http://localhost:3000/learningPath ( +// Example 1: http://localhost:3000/learningPath + +// Unified route for fetching learning paths +// Route 2: Query: hruid (list), language +// Fetch learning paths based on hruid list +// Example 2: http://localhost:3000/learningPath?hruid=pn_werking&hruid=art1 + +// Query: search, language +// Route to fetch learning paths based on a searchterm +// Example 3: http://localhost:3000/learningPath?search=robot + +// Query: theme, anguage +// Route to fetch learning paths based on a theme +// Example: http://localhost:3000/learningPath?theme=kiks + router.get('/', getLearningPaths); -// Query: language -// Route to fetch learning paths based on a searchterm -// Example: http://localhost:3000/learningPath/search?query=robot -router.get('/search', searchLearningPaths); - -// Arg: theme id -// Query: language -// Route to fetch learning paths based on a theme -// Example: http://localhost:3000/learningPath/theme/kiks -router.get('/theme/:theme', getLearningPathsByTheme); - export default router; From 308661d72b817ab69dee830bdb2b1d01bff4c542 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Sun, 2 Mar 2025 12:11:32 +0100 Subject: [PATCH 20/21] fix: splits controller en service beter op met querries --- backend/src/controllers/learningPaths.ts | 117 +++++------------------ backend/src/services/learningPaths.ts | 12 +++ 2 files changed, 36 insertions(+), 93 deletions(-) diff --git a/backend/src/controllers/learningPaths.ts b/backend/src/controllers/learningPaths.ts index c819b072..8868d7de 100644 --- a/backend/src/controllers/learningPaths.ts +++ b/backend/src/controllers/learningPaths.ts @@ -1,113 +1,44 @@ import { Request, Response } from 'express'; import { themes } from '../data/themes.js'; -import { DWENGO_API_BASE, FALLBACK_LANG } from '../config.js'; -import { fetchWithLogging } from '../util/apiHelper.js'; -import { fetchLearningPaths } from '../services/learningPaths.js'; -import { LearningPath } from '../interfaces/learningPath'; - +import { FALLBACK_LANG } from '../config.js'; +import { fetchLearningPaths, searchLearningPaths } from '../services/learningPaths.js'; /** - * Fetch learning paths based on HRUIDs or return all if no HRUIDs are provided. - * - If `hruids` are given -> fetch specific learning paths. - * - If `hruids` is missing -> return all available learning paths. + * Fetch learning paths based on query parameters. */ export async function getLearningPaths( req: Request, res: Response -): Promise { +): Promise { try { - const hruids = req.query.hruids; // Can be string or array + const hruids = req.query.hruid; + const themeKey = (req.query.theme as string); + const searchQuery = (req.query.search as string); const language = (req.query.language as string) || FALLBACK_LANG; - let hruidList: string[]; + let hruidList = []; if (hruids) { - hruidList = Array.isArray(hruids) - ? hruids.map(String) - : [String(hruids)]; + hruidList = Array.isArray(hruids) ? hruids.map(String) : [String(hruids)]; + } else if (themeKey) { + const theme = themes.find((t) => t.title === themeKey); + if (theme) { + hruidList = theme.hruids; + } else { + res.status(404).json({ error: `Theme "${themeKey}" not found.` }); + return; + } + } else if (searchQuery) { + const searchResults = await searchLearningPaths(searchQuery, language); + res.json(searchResults); + return; } else { - // If no hruids are provided, fetch ALL learning paths - hruidList = themes.flatMap((theme) => { - return theme.hruids; - }); + hruidList = themes.flatMap((theme) => theme.hruids); } - const learningPaths = await fetchLearningPaths( - hruidList, - language, - `HRUIDs: ${hruidList.join(', ')}` - ); - - res.json(learningPaths); + const learningPaths = await fetchLearningPaths(hruidList, language, `HRUIDs: ${hruidList.join(', ')}`); + res.json(learningPaths.data); } catch (error) { console.error('❌ Unexpected error fetching learning paths:', error); res.status(500).json({ error: 'Internal server error' }); } } - -/** - * Fetch all learning paths for a specific theme. - */ -export async function getLearningPathsByTheme( - req: Request, - res: Response -): Promise { - try { - const themeKey = req.params.theme; - const language = (req.query.language as string) || FALLBACK_LANG; - - const theme = themes.find((t) => { - return t.title === themeKey; - }); - if (!theme) { - console.error(`⚠️ WARNING: Theme "${themeKey}" not found.`); - res.status(404).json({ error: 'Theme not found' }); - return; - } - - const response = await fetchLearningPaths( - theme.hruids, - language, - `theme "${themeKey}"` - ); - res.json({ - theme: themeKey, - hruids: theme.hruids, - ...response, - }); - } catch (error) { - console.error( - '❌ Unexpected error fetching learning paths by theme:', - error - ); - } -} - -/** - * Search learning paths by query. - */ -export async function searchLearningPaths( - req: Request, - res: Response -): Promise { - try { - const query = req.query.query as string; - const language = (req.query.language as string) || FALLBACK_LANG; - - if (!query) { - res.status(400).json({ error: 'Missing search query' }); - return; - } - - const apiUrl = `${DWENGO_API_BASE}/learningPath/search`; - const params = { all: query, language }; - - const searchResults = await fetchWithLogging( - apiUrl, - `Search learning paths with query "${query}"`, - params - ); - res.json(searchResults ?? []); - } catch (error) { - console.error('❌ Unexpected error searching learning paths:', error); - } -} diff --git a/backend/src/services/learningPaths.ts b/backend/src/services/learningPaths.ts index 734207db..d8ad90a2 100644 --- a/backend/src/services/learningPaths.ts +++ b/backend/src/services/learningPaths.ts @@ -44,3 +44,15 @@ export async function fetchLearningPaths( data: learningPaths, }; } + + +export async function searchLearningPaths( + query: string, + language: string +): Promise { + const apiUrl = `${DWENGO_API_BASE}/learningPath/search`; + const params = { all: query, language }; + + const searchResults = await fetchWithLogging(apiUrl, `Search learning paths with query "${query}"`, params); + return searchResults ?? []; +} From e762621287808422337ba5aa59fc2f88e60defe7 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Sun, 2 Mar 2025 12:18:08 +0100 Subject: [PATCH 21/21] fix: format + lint --- backend/src/controllers/learningObjects.ts | 21 +++++---- backend/src/controllers/learningPaths.ts | 40 ++++++++++++----- backend/src/routes/learningObjects.ts | 6 ++- backend/src/services/learningObjects.ts | 50 ++++++++++++++++------ backend/src/services/learningPaths.ts | 9 ++-- 5 files changed, 87 insertions(+), 39 deletions(-) diff --git a/backend/src/controllers/learningObjects.ts b/backend/src/controllers/learningObjects.ts index 026d37ef..4295326a 100644 --- a/backend/src/controllers/learningObjects.ts +++ b/backend/src/controllers/learningObjects.ts @@ -2,17 +2,17 @@ import { Request, Response } from 'express'; import { getLearningObjectById, getLearningObjectIdsFromPath, - getLearningObjectsFromPath + getLearningObjectsFromPath, } from '../services/learningObjects.js'; import { FALLBACK_LANG } from '../config.js'; -import {FilteredLearningObject} from "../interfaces/learningPath"; +import { FilteredLearningObject } from '../interfaces/learningPath'; export async function getAllLearningObjects( req: Request, res: Response ): Promise { try { - const hruid = (req.query.hruid as string); + const hruid = req.query.hruid as string; const full = req.query.full === 'true'; const language = (req.query.language as string) || FALLBACK_LANG; @@ -22,10 +22,14 @@ export async function getAllLearningObjects( } let learningObjects: FilteredLearningObject[] | string[]; - if (full) - learningObjects = await getLearningObjectsFromPath(hruid,language); - else - learningObjects = await getLearningObjectIdsFromPath(hruid, language) + if (full) { + learningObjects = await getLearningObjectsFromPath(hruid, language); + } else { + learningObjects = await getLearningObjectIdsFromPath( + hruid, + language + ); + } res.json(learningObjects); } catch (error) { @@ -34,7 +38,6 @@ export async function getAllLearningObjects( } } - export async function getLearningObject( req: Request, res: Response @@ -50,7 +53,7 @@ export async function getLearningObject( const learningObject = await getLearningObjectById(hruid, language); res.json(learningObject); - } catch (error){ + } catch (error) { console.error('Error fetching learning object:', error); res.status(500).json({ error: 'Internal server error' }); } diff --git a/backend/src/controllers/learningPaths.ts b/backend/src/controllers/learningPaths.ts index 8868d7de..903451be 100644 --- a/backend/src/controllers/learningPaths.ts +++ b/backend/src/controllers/learningPaths.ts @@ -1,41 +1,59 @@ import { Request, Response } from 'express'; import { themes } from '../data/themes.js'; import { FALLBACK_LANG } from '../config.js'; -import { fetchLearningPaths, searchLearningPaths } from '../services/learningPaths.js'; +import { + fetchLearningPaths, + searchLearningPaths, +} from '../services/learningPaths.js'; /** * Fetch learning paths based on query parameters. */ export async function getLearningPaths( req: Request, res: Response -): Promise { +): Promise { try { const hruids = req.query.hruid; - const themeKey = (req.query.theme as string); - const searchQuery = (req.query.search as string); + const themeKey = req.query.theme as string; + const searchQuery = req.query.search as string; const language = (req.query.language as string) || FALLBACK_LANG; - let hruidList = []; + let hruidList; if (hruids) { - hruidList = Array.isArray(hruids) ? hruids.map(String) : [String(hruids)]; + hruidList = Array.isArray(hruids) + ? hruids.map(String) + : [String(hruids)]; } else if (themeKey) { - const theme = themes.find((t) => t.title === themeKey); + const theme = themes.find((t) => { + return t.title === themeKey; + }); if (theme) { hruidList = theme.hruids; } else { - res.status(404).json({ error: `Theme "${themeKey}" not found.` }); + res.status(404).json({ + error: `Theme "${themeKey}" not found.`, + }); return; } } else if (searchQuery) { - const searchResults = await searchLearningPaths(searchQuery, language); + const searchResults = await searchLearningPaths( + searchQuery, + language + ); res.json(searchResults); return; } else { - hruidList = themes.flatMap((theme) => theme.hruids); + hruidList = themes.flatMap((theme) => { + return theme.hruids; + }); } - const learningPaths = await fetchLearningPaths(hruidList, language, `HRUIDs: ${hruidList.join(', ')}`); + const learningPaths = await fetchLearningPaths( + hruidList, + language, + `HRUIDs: ${hruidList.join(', ')}` + ); res.json(learningPaths.data); } catch (error) { console.error('❌ Unexpected error fetching learning paths:', error); diff --git a/backend/src/routes/learningObjects.ts b/backend/src/routes/learningObjects.ts index 42e72bca..416602b5 100644 --- a/backend/src/routes/learningObjects.ts +++ b/backend/src/routes/learningObjects.ts @@ -1,5 +1,8 @@ import express from 'express'; -import {getAllLearningObjects, getLearningObject} from '../controllers/learningObjects.js'; +import { + getAllLearningObjects, + getLearningObject, +} from '../controllers/learningObjects.js'; const router = express.Router(); @@ -15,7 +18,6 @@ const router = express.Router(); // Example 2: http://localhost:3000/learningObject?full=true&hruid=un_artificiele_intelligentie router.get('/', getAllLearningObjects); - // Parameter: hruid of learning object // Query: language // Route to fetch data of one learning object based on its hruid diff --git a/backend/src/services/learningObjects.ts b/backend/src/services/learningObjects.ts index b184f1fd..d1d34ad2 100644 --- a/backend/src/services/learningObjects.ts +++ b/backend/src/services/learningObjects.ts @@ -3,7 +3,8 @@ import { fetchWithLogging } from '../util/apiHelper.js'; import { FilteredLearningObject, LearningObjectMetadata, - LearningObjectNode, LearningPathResponse, + LearningObjectNode, + LearningPathResponse, } from '../interfaces/learningPath.js'; import { fetchLearningPaths } from './learningPaths.js'; @@ -40,7 +41,7 @@ function filterData( export async function getLearningObjectById( hruid: string, language: string -): Promise { +): Promise { const metadataUrl = `${DWENGO_API_BASE}/learningObject/getMetadata?hruid=${hruid}&language=${language}`; const metadata = await fetchWithLogging( metadataUrl, @@ -65,26 +66,43 @@ async function fetchLearningObjects( language: string ): Promise { try { - const learningPathResponse: LearningPathResponse = await fetchLearningPaths( - [hruid], - language, - `Learning path for HRUID "${hruid}"` - ); + const learningPathResponse: LearningPathResponse = + await fetchLearningPaths( + [hruid], + language, + `Learning path for HRUID "${hruid}"` + ); - if (!learningPathResponse.success || !learningPathResponse.data?.length) { - console.error(`⚠️ WARNING: Learning path "${hruid}" exists but contains no learning objects.`); + if ( + !learningPathResponse.success || + !learningPathResponse.data?.length + ) { + console.error( + `⚠️ WARNING: Learning path "${hruid}" exists but contains no learning objects.` + ); return []; } const nodes: LearningObjectNode[] = learningPathResponse.data[0].nodes; if (!full) { - return nodes.map(node => node.learningobject_hruid); + return nodes.map((node) => { + return node.learningobject_hruid; + }); } return await Promise.all( - nodes.map(async (node) => getLearningObjectById(node.learningobject_hruid, language)) - ).then((objects) => objects.filter((obj): obj is FilteredLearningObject => obj !== null)); + nodes.map(async (node) => { + return getLearningObjectById( + node.learningobject_hruid, + language + ); + }) + ).then((objects) => { + return objects.filter((obj): obj is FilteredLearningObject => { + return obj !== null; + }); + }); } catch (error) { console.error('❌ Error fetching learning objects:', error); return []; @@ -98,7 +116,11 @@ export async function getLearningObjectsFromPath( hruid: string, language: string ): Promise { - return await fetchLearningObjects(hruid, true, language) as FilteredLearningObject[]; + return (await fetchLearningObjects( + hruid, + true, + language + )) as FilteredLearningObject[]; } /** @@ -108,5 +130,5 @@ export async function getLearningObjectIdsFromPath( hruid: string, language: string ): Promise { - return await fetchLearningObjects(hruid, false, language) as string[]; + return (await fetchLearningObjects(hruid, false, language)) as string[]; } diff --git a/backend/src/services/learningPaths.ts b/backend/src/services/learningPaths.ts index d8ad90a2..2a9f15a3 100644 --- a/backend/src/services/learningPaths.ts +++ b/backend/src/services/learningPaths.ts @@ -45,14 +45,17 @@ export async function fetchLearningPaths( }; } - export async function searchLearningPaths( query: string, language: string -): Promise { +): Promise { const apiUrl = `${DWENGO_API_BASE}/learningPath/search`; const params = { all: query, language }; - const searchResults = await fetchWithLogging(apiUrl, `Search learning paths with query "${query}"`, params); + const searchResults = await fetchWithLogging( + apiUrl, + `Search learning paths with query "${query}"`, + params + ); return searchResults ?? []; }