fix: opslitsing learningpath controller met extra service + api helper
This commit is contained in:
		
							parent
							
								
									91eb374b7e
								
							
						
					
					
						commit
						f7d6cbce65
					
				
					 4 changed files with 98 additions and 116 deletions
				
			
		|  | @ -1,100 +1,68 @@ | ||||||
| import { Request, Response } from 'express'; | import { Request, Response } from 'express'; | ||||||
| import axios from 'axios'; |  | ||||||
| import { themes } from '../data/themes.js'; | import { themes } from '../data/themes.js'; | ||||||
| import { DWENGO_API_BASE } from '../config/config.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. |  * Fetch learning paths based on HRUIDs or return all if no HRUIDs are provided. | ||||||
|  * This function sends a request to the Dwengo API with the provided HRUIDs. |  * - If `hruids` are given -> fetch specific learning paths. | ||||||
|  |  * - If `hruids` is missing -> return all available learning paths. | ||||||
|  */ |  */ | ||||||
| export async function getLearningPathsFromIds( | export async function getLearningPaths(req: Request, res: Response): Promise<void> { | ||||||
|     req: Request, |  | ||||||
|     res: Response |  | ||||||
| ): Promise<void> { |  | ||||||
|     try { |     try { | ||||||
|         const { hruids } = req.query; |         const hruids = req.query.hruids; // Can be string or array
 | ||||||
|         const language = (req.query.language as string) || 'nl'; // Default to Dutch
 |         const language = (req.query.language as string) || 'nl'; | ||||||
| 
 | 
 | ||||||
|         if (!hruids) { |         let hruidList: string[] = []; | ||||||
|             res.status(400).json({ | 
 | ||||||
|                 error: 'Missing required parameter: hruids', |         if (hruids) { | ||||||
|             }); |             hruidList = Array.isArray(hruids) ? hruids.map(String) : [String(hruids)]; | ||||||
|             return; |         } 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 learningPaths = await fetchLearningPaths(hruidList, language, `HRUIDs: ${hruidList.join(', ')}`); | ||||||
|         const hruidList = Array.isArray(hruids) ? hruids : [hruids]; |  | ||||||
| 
 | 
 | ||||||
|         // Request learning paths from Dwengo API
 |         res.json(learningPaths); | ||||||
|         const response = await axios.get( |  | ||||||
|             `${DWENGO_API_BASE}/learningPath/getPathsFromIdList`, |  | ||||||
|             { |  | ||||||
|                 params: { |  | ||||||
|                     pathIdList: JSON.stringify({ hruids: hruidList }), |  | ||||||
|                     language, |  | ||||||
|                 }, |  | ||||||
|             } |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         res.json(response.data); |  | ||||||
|     } catch (error) { |     } 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' }); |         res.status(500).json({ error: 'Internal server error' }); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * Fetch all learning paths for a specific theme. |  * 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( | export async function getLearningPathsByTheme(req: Request, res: Response): Promise<void> { | ||||||
|     req: Request, |  | ||||||
|     res: Response |  | ||||||
| ): Promise<void> { |  | ||||||
|     try { |     try { | ||||||
|         const themeKey = req.params.theme; |         const themeKey = req.params.theme; | ||||||
|         const language = (req.query.language as string) || 'nl'; // Default to Dutch
 |         const language = (req.query.language as string) || 'nl'; | ||||||
| 
 |  | ||||||
|         // Find the theme by its title
 |  | ||||||
|         const theme = themes.find((t) => { |  | ||||||
|             return t.title === themeKey; |  | ||||||
|         }); |  | ||||||
| 
 | 
 | ||||||
|  |         const theme = themes.find((t) => t.title === themeKey); | ||||||
|         if (!theme) { |         if (!theme) { | ||||||
|  |             console.error(`⚠️ WARNING: Theme "${themeKey}" not found.`); | ||||||
|             res.status(404).json({ error: 'Theme not found' }); |             res.status(404).json({ error: 'Theme not found' }); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Extract HRUIDs from the theme
 |         const response = await fetchLearningPaths(theme.hruids, language, `theme "${themeKey}"`); | ||||||
|         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({ |         res.json({ | ||||||
|             theme: themeKey, |             theme: themeKey, | ||||||
|             hruids: hruidList, |             hruids: theme.hruids, | ||||||
|             learningPaths: response.data, |             ...response, | ||||||
|         }); |         }); | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
|         console.error('Error fetching learning paths for theme:', error); |         console.error('❌ Unexpected error fetching learning paths by theme:', error); | ||||||
|         res.status(500).json({ error: 'Internal server error' }); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function searchLearningPaths( | /** | ||||||
|     req: Request, |  * Search learning paths by query. | ||||||
|     res: Response |  */ | ||||||
| ): Promise<void> { | export async function searchLearningPaths(req: Request, res: Response): Promise<void> { | ||||||
|     try { |     try { | ||||||
|         const query = req.query.query as string; |         const query = req.query.query as string; | ||||||
|         const language = (req.query.language as string) || 'nl'; |         const language = (req.query.language as string) || 'nl'; | ||||||
|  | @ -104,51 +72,12 @@ export async function searchLearningPaths( | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const response = await axios.get( |         const apiUrl = `${DWENGO_API_BASE}/learningPath/search`; | ||||||
|             `${DWENGO_API_BASE}/learningPath/search`, |         const params = { all: query, language }; | ||||||
|             { |  | ||||||
|                 params: { all: query, language }, |  | ||||||
|             } |  | ||||||
|         ); |  | ||||||
| 
 | 
 | ||||||
|         res.json(response.data); |         const searchResults = await fetchWithLogging<any>(apiUrl, `Search learning paths with query "${query}"`, params); | ||||||
|  |         res.json(searchResults ?? []); | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
|         console.error('Error searching learning paths:', error); |         console.error('❌ Unexpected error searching learning paths:', error); | ||||||
|         res.status(500).json({ error: 'Internal server error' }); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export async function getAllLearningPaths( |  | ||||||
|     req: Request, |  | ||||||
|     res: Response |  | ||||||
| ): Promise<void> { |  | ||||||
|     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' }); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -64,14 +64,15 @@ export async function getLearningObjectsFromPath( | ||||||
|         return await Promise.all( |         return await Promise.all( | ||||||
|             learningPathData.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 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}`; |                 const htmlUrl = `${DWENGO_API_BASE}/learningObject/getRaw?hruid=${node.learningobject_hruid}&version=${node.version}&language=${language}`; | ||||||
| 
 |                 return filterLearningObjectMetadata(metadata, htmlUrl); | ||||||
|                 return filterLearningObjectMetadata( |  | ||||||
|                     metadataResponse.data, |  | ||||||
|                     htmlUrl |  | ||||||
|                 ); |  | ||||||
|             }) |             }) | ||||||
|         ); |         ); | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
|  |  | ||||||
							
								
								
									
										45
									
								
								backend/src/services/learningPaths.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								backend/src/services/learningPaths.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<LearningPathResponse> { | ||||||
|  |     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<any>(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, | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import axios from 'axios'; | import axios, {AxiosRequestConfig} from 'axios'; | ||||||
| 
 | 
 | ||||||
| // !!!! when logger is done -> change
 | // !!!! when logger is done -> change
 | ||||||
| 
 | 
 | ||||||
|  | @ -8,11 +8,18 @@ import axios from 'axios'; | ||||||
|  * |  * | ||||||
|  * @param url The API endpoint to fetch from. |  * @param url The API endpoint to fetch from. | ||||||
|  * @param description A short description of what is being fetched (for logging). |  * @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. |  * @returns The response data if successful, or null if an error occurs. | ||||||
|  */ |  */ | ||||||
| export async function fetchWithLogging<T>(url: string, description: string): Promise<T | null> { | export async function fetchWithLogging<T>( | ||||||
|  |     url: string, | ||||||
|  |     description: string, | ||||||
|  |     params?: Record<string,any> | ||||||
|  | ):  Promise<T | null> { | ||||||
|     try { |     try { | ||||||
|         const response = await axios.get<T>(url); |         const config: AxiosRequestConfig = params ? { params } : {}; | ||||||
|  | 
 | ||||||
|  |         const response = await axios.get<T>(url, config); | ||||||
|         return response.data; |         return response.data; | ||||||
|     } catch (error: any) { |     } catch (error: any) { | ||||||
|         if (error.response) { |         if (error.response) { | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Gabriellvl
						Gabriellvl