feat(backend): Added endpoint to fetch HTML version of learning object (from Dwengo backend)
Also refactored a bit to make this easier.
This commit is contained in:
		
							parent
							
								
									770c5c9879
								
							
						
					
					
						commit
						18ee991ce3
					
				
					 16 changed files with 264 additions and 178 deletions
				
			
		|  | @ -3,8 +3,8 @@ import { initORM } from './orm.js'; | |||
| import { EnvVars, getNumericEnvVar } from './util/envvars.js'; | ||||
| 
 | ||||
| import themeRoutes from './routes/themes.js'; | ||||
| import learningPathRoutes from './routes/learningPaths.js'; | ||||
| import learningObjectRoutes from './routes/learningObjects.js'; | ||||
| import learningPathRoutes from './routes/learning-paths.js'; | ||||
| import learningObjectRoutes from './routes/learning-objects.js'; | ||||
| 
 | ||||
| import studentRouter from './routes/student.js'; | ||||
| import groupRouter from './routes/group.js'; | ||||
|  |  | |||
|  | @ -5,6 +5,8 @@ | |||
| // Load .env file
 | ||||
| // Dotenv.config();
 | ||||
| 
 | ||||
| export const DWENGO_API_BASE = 'https://dwengo.org/backend/api'; | ||||
| import {EnvVars, getEnvVar} from "./util/envvars"; | ||||
| 
 | ||||
| export const FALLBACK_LANG = 'nl'; | ||||
| export const DWENGO_API_BASE = getEnvVar(EnvVars.LearningContentRepoApiBaseUrl); | ||||
| 
 | ||||
| export const FALLBACK_LANG = getEnvVar(EnvVars.FallbackLanguage); | ||||
|  |  | |||
							
								
								
									
										62
									
								
								backend/src/controllers/learning-objects.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								backend/src/controllers/learning-objects.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,62 @@ | |||
| import { Request, Response } from 'express'; | ||||
| import { FALLBACK_LANG } from '../config.js'; | ||||
| import {FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier} from '../interfaces/learning-content'; | ||||
| import learningObjectService from "../services/learning-content/learning-object-service"; | ||||
| import {EnvVars, getEnvVar} from "../util/envvars"; | ||||
| import {Language} from "../entities/content/language"; | ||||
| import {BadRequestException} from "../exceptions"; | ||||
| 
 | ||||
| function getLearningObjectIdentifierFromRequest(req: Request): LearningObjectIdentifier { | ||||
|     if (!req.params.hruid) { | ||||
|         throw new BadRequestException("HRUID is required."); | ||||
|     } | ||||
|     return { | ||||
|         hruid: req.params.hruid as string, | ||||
|         language: (req.query.language || getEnvVar(EnvVars.FallbackLanguage)) as Language, | ||||
|         version: req.query.version as string | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| function getLearningPathIdentifierFromRequest(req: Request): LearningPathIdentifier { | ||||
|     if (!req.query.hruid) { | ||||
|         throw new BadRequestException("HRUID is required."); | ||||
|     } | ||||
|     return { | ||||
|         hruid: req.params.hruid as string, | ||||
|         language: (req.query.language as Language) || FALLBACK_LANG | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export async function getAllLearningObjects( | ||||
|     req: Request, | ||||
|     res: Response | ||||
| ): Promise<void> { | ||||
|     const learningPathId = getLearningPathIdentifierFromRequest(req); | ||||
|     const full = req.query.full; | ||||
| 
 | ||||
|     let learningObjects: FilteredLearningObject[] | string[]; | ||||
|     if (full) { | ||||
|         learningObjects = await learningObjectService.getLearningObjectsFromPath(learningPathId); | ||||
|     } else { | ||||
|         learningObjects = await learningObjectService.getLearningObjectIdsFromPath(learningPathId); | ||||
|     } | ||||
| 
 | ||||
|     res.json(learningObjects); | ||||
| } | ||||
| 
 | ||||
| export async function getLearningObject( | ||||
|     req: Request, | ||||
|     res: Response | ||||
| ): Promise<void> { | ||||
|     const learningObjectId = getLearningObjectIdentifierFromRequest(req); | ||||
| 
 | ||||
|     const learningObject = await learningObjectService.getLearningObjectById(learningObjectId); | ||||
|     res.json(learningObject); | ||||
| } | ||||
| 
 | ||||
| export async function getLearningObjectHTML(req: Request, res: Response): Promise<void> { | ||||
|     const learningObjectId = getLearningObjectIdentifierFromRequest(req); | ||||
| 
 | ||||
|     const learningObject = await learningObjectService.getLearningObjectHTML(learningObjectId); | ||||
|     res.send(learningObject); | ||||
| } | ||||
							
								
								
									
										53
									
								
								backend/src/controllers/learning-paths.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								backend/src/controllers/learning-paths.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,53 @@ | |||
| import { Request, Response } from 'express'; | ||||
| import { themes } from '../data/themes.js'; | ||||
| import { FALLBACK_LANG } from '../config.js'; | ||||
| import learningPathService from "../services/learning-content/learning-path-service"; | ||||
| import {NotFoundException} from "../exceptions"; | ||||
| 
 | ||||
| /** | ||||
|  * Fetch learning paths based on query parameters. | ||||
|  */ | ||||
| export async function getLearningPaths( | ||||
|     req: Request, | ||||
|     res: Response | ||||
| ): Promise<void> { | ||||
|     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; | ||||
| 
 | ||||
|     if (hruids) { | ||||
|         hruidList = Array.isArray(hruids) | ||||
|             ? hruids.map(String) | ||||
|             : [String(hruids)]; | ||||
|     } else if (themeKey) { | ||||
|         const theme = themes.find((t) => { | ||||
|             return t.title === themeKey; | ||||
|         }); | ||||
|         if (theme) { | ||||
|             hruidList = theme.hruids; | ||||
|         } else { | ||||
|             throw new NotFoundException(`Theme "${themeKey}" not found.`); | ||||
|         } | ||||
|     } else if (searchQuery) { | ||||
|         const searchResults = await learningPathService.searchLearningPaths( | ||||
|             searchQuery, | ||||
|             language | ||||
|         ); | ||||
|         res.json(searchResults); | ||||
|         return; | ||||
|     } else { | ||||
|         hruidList = themes.flatMap((theme) => { | ||||
|             return theme.hruids; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     const learningPaths = await learningPathService.fetchLearningPaths( | ||||
|         hruidList, | ||||
|         language, | ||||
|         `HRUIDs: ${hruidList.join(', ')}` | ||||
|     ); | ||||
|     res.json(learningPaths.data); | ||||
| } | ||||
|  | @ -1,56 +0,0 @@ | |||
| import { Request, Response } from 'express'; | ||||
| import { FALLBACK_LANG } from '../config.js'; | ||||
| import { FilteredLearningObject } from '../interfaces/learningContent'; | ||||
| import learningObjectService from "../services/learning-content/learning-object-service"; | ||||
| 
 | ||||
| export async function getAllLearningObjects( | ||||
|     req: Request, | ||||
|     res: Response | ||||
| ): Promise<void> { | ||||
|     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 learningObjectService.getLearningObjectsFromPath(hruid, language); | ||||
|         } else { | ||||
|             learningObjects = await learningObjectService.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<void> { | ||||
|     try { | ||||
|         const { hruid } = req.params; | ||||
|         const language = (req.query.language as string) || FALLBACK_LANG; | ||||
| 
 | ||||
|         if (!hruid) { | ||||
|             res.status(400).json({ error: 'HRUID parameter is required.' }); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const learningObject = await learningObjectService.getLearningObjectById(hruid, language); | ||||
|         res.json(learningObject); | ||||
|     } catch (error) { | ||||
|         console.error('Error fetching learning object:', error); | ||||
|         res.status(500).json({ error: 'Internal server error' }); | ||||
|     } | ||||
| } | ||||
|  | @ -1,59 +0,0 @@ | |||
| import { Request, Response } from 'express'; | ||||
| import { themes } from '../data/themes.js'; | ||||
| import { FALLBACK_LANG } from '../config.js'; | ||||
| import learningPathService from "../services/learning-content/learning-path-service"; | ||||
| /** | ||||
|  * Fetch learning paths based on query parameters. | ||||
|  */ | ||||
| export async function getLearningPaths( | ||||
|     req: Request, | ||||
|     res: Response | ||||
| ): Promise<void> { | ||||
|     try { | ||||
|         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; | ||||
| 
 | ||||
|         if (hruids) { | ||||
|             hruidList = Array.isArray(hruids) | ||||
|                 ? hruids.map(String) | ||||
|                 : [String(hruids)]; | ||||
|         } else if (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.`, | ||||
|                 }); | ||||
|                 return; | ||||
|             } | ||||
|         } else if (searchQuery) { | ||||
|             const searchResults = await learningPathService.searchLearningPaths( | ||||
|                 searchQuery, | ||||
|                 language | ||||
|             ); | ||||
|             res.json(searchResults); | ||||
|             return; | ||||
|         } else { | ||||
|             hruidList = themes.flatMap((theme) => { | ||||
|                 return theme.hruids; | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         const learningPaths = await learningPathService.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' }); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										21
									
								
								backend/src/exceptions.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								backend/src/exceptions.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | |||
| /** | ||||
|  * Exception for HTTP 400 Bad Request | ||||
|  */ | ||||
| export class BadRequestException extends Error { | ||||
|     public status = 400; | ||||
| 
 | ||||
|     constructor(error: string) { | ||||
|         super(error); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Exception for HTTP 404 Not Found | ||||
|  */ | ||||
| export class NotFoundException extends Error { | ||||
|     public status = 404; | ||||
| 
 | ||||
|     constructor(error: string) { | ||||
|         super(error); | ||||
|     } | ||||
| } | ||||
|  | @ -1,3 +1,5 @@ | |||
| import {Language} from "../entities/content/language"; | ||||
| 
 | ||||
| export interface Transition { | ||||
|     default: boolean; | ||||
|     _id: string; | ||||
|  | @ -9,6 +11,12 @@ export interface Transition { | |||
|     }; | ||||
| } | ||||
| 
 | ||||
| export interface LearningObjectIdentifier { | ||||
|     hruid: string; | ||||
|     language: Language; | ||||
|     version?: string; | ||||
| } | ||||
| 
 | ||||
| export interface LearningObjectNode { | ||||
|     _id: string; | ||||
|     learningobject_hruid: string; | ||||
|  | @ -20,7 +28,7 @@ export interface LearningObjectNode { | |||
|     updatedAt: string; | ||||
| } | ||||
| 
 | ||||
| export interface LearningContent { | ||||
| export interface LearningPath { | ||||
|     _id: string; | ||||
|     language: string; | ||||
|     hruid: string; | ||||
|  | @ -37,6 +45,11 @@ export interface LearningContent { | |||
|     __order: number; | ||||
| } | ||||
| 
 | ||||
| export interface LearningPathIdentifier { | ||||
|     hruid: string; | ||||
|     language: Language; | ||||
| } | ||||
| 
 | ||||
| export interface EducationalGoal { | ||||
|     source: string; | ||||
|     id: string; | ||||
|  | @ -93,6 +106,6 @@ export interface FilteredLearningObject { | |||
| export interface LearningPathResponse { | ||||
|     success: boolean; | ||||
|     source: string; | ||||
|     data: LearningContent[] | null; | ||||
|     data: LearningPath[] | null; | ||||
|     message?: string; | ||||
| } | ||||
|  | @ -1,8 +1,8 @@ | |||
| import express from 'express'; | ||||
| import { | ||||
|     getAllLearningObjects, | ||||
|     getLearningObject, | ||||
| } from '../controllers/learningObjects.js'; | ||||
|     getLearningObject, getLearningObjectHTML, | ||||
| } from '../controllers/learning-objects.js'; | ||||
| 
 | ||||
| const router = express.Router(); | ||||
| 
 | ||||
|  | @ -24,4 +24,10 @@ router.get('/', getAllLearningObjects); | |||
| // Example: http://localhost:3000/learningObject/un_ai7
 | ||||
| router.get('/:hruid', getLearningObject); | ||||
| 
 | ||||
| // Parameter: hruid of learning object
 | ||||
| // Query: language, version (optional)
 | ||||
| // Route to fetch the HTML rendering of one learning object based on its hruid.
 | ||||
| // Example: http://localhost:3000/learningObject/un_ai7/html
 | ||||
| router.get('/:hruid/html', getLearningObjectHTML); | ||||
| 
 | ||||
| export default router; | ||||
|  | @ -1,5 +1,5 @@ | |||
| import express from 'express'; | ||||
| import { getLearningPaths } from '../controllers/learningPaths.js'; | ||||
| import { getLearningPaths } from '../controllers/learning-paths.js'; | ||||
| 
 | ||||
| const router = express.Router(); | ||||
| 
 | ||||
|  | @ -1,11 +1,11 @@ | |||
| import { DWENGO_API_BASE } from '../../../config.js'; | ||||
| import { fetchWithLogging } from '../../../util/apiHelper.js'; | ||||
| import { | ||||
|     FilteredLearningObject, | ||||
|     FilteredLearningObject, LearningObjectIdentifier, | ||||
|     LearningObjectMetadata, | ||||
|     LearningObjectNode, | ||||
|     LearningObjectNode, LearningPathIdentifier, | ||||
|     LearningPathResponse, | ||||
| } from '../../../interfaces/learningContent.js'; | ||||
| } from '../../../interfaces/learning-content.js'; | ||||
| import dwengoApiLearningPathProvider from './dwengo-api-learning-path-provider.js'; | ||||
| import {LearningObjectProvider} from "../learning-object-provider"; | ||||
| 
 | ||||
|  | @ -13,11 +13,9 @@ import {LearningObjectProvider} from "../learning-object-provider"; | |||
|  * Helper function to convert the learning object metadata retrieved from the API to a FilteredLearningObject which | ||||
|  * our API should return. | ||||
|  * @param data | ||||
|  * @param htmlUrl | ||||
|  */ | ||||
| function filterData( | ||||
|     data: LearningObjectMetadata, | ||||
|     htmlUrl: string | ||||
|     data: LearningObjectMetadata | ||||
| ): FilteredLearningObject { | ||||
|     return { | ||||
|         key: data.hruid, // Hruid learningObject (not path)
 | ||||
|  | @ -25,7 +23,7 @@ function filterData( | |||
|         uuid: data.uuid, | ||||
|         version: data.version, | ||||
|         title: data.title, | ||||
|         htmlUrl, // Url to fetch html content
 | ||||
|         htmlUrl: `/learningObject/${data.hruid}/html?language=${data.language}&version=${data.version}`, // Url to fetch html content
 | ||||
|         language: data.language, | ||||
|         difficulty: data.difficulty, | ||||
|         estimatedTime: data.estimated_time, | ||||
|  | @ -43,19 +41,18 @@ function filterData( | |||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Generic helper function to fetch learning objects (full data or just HRUIDs) | ||||
|  * Generic helper function to fetch all learning objects from a given path (full data or just HRUIDs) | ||||
|  */ | ||||
| async function fetchLearningObjects( | ||||
|     hruid: string, | ||||
|     full: boolean, | ||||
|     language: string | ||||
|     learningPathId: LearningPathIdentifier, | ||||
|     full: boolean | ||||
| ): Promise<FilteredLearningObject[] | string[]> { | ||||
|     try { | ||||
|         const learningPathResponse: LearningPathResponse = | ||||
|             await dwengoApiLearningPathProvider.fetchLearningPaths( | ||||
|                 [hruid], | ||||
|                 language, | ||||
|                 `Learning path for HRUID "${hruid}"` | ||||
|                 [learningPathId.hruid], | ||||
|                 learningPathId.language, | ||||
|                 `Learning path for HRUID "${learningPathId.hruid}"` | ||||
|             ); | ||||
| 
 | ||||
|         if ( | ||||
|  | @ -63,7 +60,7 @@ async function fetchLearningObjects( | |||
|             !learningPathResponse.data?.length | ||||
|         ) { | ||||
|             console.error( | ||||
|                 `⚠️ WARNING: Learning path "${hruid}" exists but contains no learning objects.` | ||||
|                 `⚠️ WARNING: Learning path "${learningPathId.hruid}" exists but contains no learning objects.` | ||||
|             ); | ||||
|             return []; | ||||
|         } | ||||
|  | @ -78,10 +75,10 @@ async function fetchLearningObjects( | |||
| 
 | ||||
|         return await Promise.all( | ||||
|             nodes.map(async (node) => { | ||||
|                 return dwengoApiLearningObjectProvider.getLearningObjectById( | ||||
|                     node.learningobject_hruid, | ||||
|                     language | ||||
|                 ); | ||||
|                 return dwengoApiLearningObjectProvider.getLearningObjectById({ | ||||
|                     hruid: node.learningobject_hruid, | ||||
|                     language: learningPathId.language | ||||
|                 }); | ||||
|             }) | ||||
|         ).then((objects) => { | ||||
|             return objects.filter((obj): obj is FilteredLearningObject => { | ||||
|  | @ -99,46 +96,62 @@ const dwengoApiLearningObjectProvider: LearningObjectProvider = { | |||
|      * Fetches a single learning object by its HRUID | ||||
|      */ | ||||
|     async getLearningObjectById( | ||||
|         hruid: string, | ||||
|         language: string | ||||
|         id: LearningObjectIdentifier | ||||
|     ): Promise<FilteredLearningObject | null> { | ||||
|         const metadataUrl = `${DWENGO_API_BASE}/learningObject/getMetadata?hruid=${hruid}&language=${language}`; | ||||
|         let metadataUrl = `${DWENGO_API_BASE}/learningObject/getMetadata`; | ||||
|         const metadata = await fetchWithLogging<LearningObjectMetadata>( | ||||
|             metadataUrl, | ||||
|             `Metadata for Learning Object HRUID "${hruid}" (language ${language})` | ||||
|             `Metadata for Learning Object HRUID "${id.hruid}" (language ${id.language})`, | ||||
|             { | ||||
|                 params: id | ||||
|             } | ||||
|         ); | ||||
| 
 | ||||
|         if (!metadata) { | ||||
|             console.error(`⚠️ WARNING: Learning object "${hruid}" not found.`); | ||||
|             console.error(`⚠️ WARNING: Learning object "${id.hruid}" not found.`); | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         const htmlUrl = `${DWENGO_API_BASE}/learningObject/getRaw?hruid=${hruid}&language=${language}`; | ||||
|         return filterData(metadata, htmlUrl); | ||||
|         return filterData(metadata); | ||||
|     }, | ||||
| 
 | ||||
|     /** | ||||
|      * Fetch full learning object data (metadata) | ||||
|      */ | ||||
|     async getLearningObjectsFromPath( | ||||
|         hruid: string, | ||||
|         language: string | ||||
|     ): Promise<FilteredLearningObject[]> { | ||||
|     async getLearningObjectsFromPath(id: LearningPathIdentifier): Promise<FilteredLearningObject[]> { | ||||
|         return (await fetchLearningObjects( | ||||
|             hruid, | ||||
|             id, | ||||
|             true, | ||||
|             language | ||||
|         )) as FilteredLearningObject[]; | ||||
|     }, | ||||
| 
 | ||||
|     /** | ||||
|      * Fetch only learning object HRUIDs | ||||
|      */ | ||||
|     async getLearningObjectIdsFromPath( | ||||
|         hruid: string, | ||||
|         language: string | ||||
|     ): Promise<string[]> { | ||||
|         return (await fetchLearningObjects(hruid, false, language)) as string[]; | ||||
|     async getLearningObjectIdsFromPath(id: LearningPathIdentifier): Promise<string[]> { | ||||
|         return (await fetchLearningObjects(id, false)) as string[]; | ||||
|     }, | ||||
| 
 | ||||
|     /** | ||||
|      * Obtain a HTML-rendering of the learning object with the given identifier (as a string). For learning objects | ||||
|      * from the Dwengo API, this means passing through the HTML rendering from there. | ||||
|      */ | ||||
|     async getLearningObjectHTML(id: LearningObjectIdentifier): Promise<string | null> { | ||||
|         const htmlUrl = `${DWENGO_API_BASE}/learningObject/getRaw`; | ||||
|         const html = await fetchWithLogging<string>( | ||||
|             htmlUrl, | ||||
|             `Metadata for Learning Object HRUID "${id.hruid}" (language ${id.language})`, | ||||
|             { | ||||
|                 params: id | ||||
|             } | ||||
|         ); | ||||
| 
 | ||||
|         if (!html) { | ||||
|             console.error(`⚠️ WARNING: Learning object "${id.hruid}" not found.`); | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         return html; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ const dwengoApiLearningPathProvider: LearningPathProvider = { | |||
|         const learningPaths = await fetchWithLogging<LearningPath[]>( | ||||
|             apiUrl, | ||||
|             `Learning paths for ${source}`, | ||||
|             params | ||||
|             { params } | ||||
|         ); | ||||
| 
 | ||||
|         if (!learningPaths || learningPaths.length === 0) { | ||||
|  | @ -56,7 +56,7 @@ const dwengoApiLearningPathProvider: LearningPathProvider = { | |||
|         const searchResults = await fetchWithLogging<LearningPath[]>( | ||||
|             apiUrl, | ||||
|             `Search learning paths with query "${query}"`, | ||||
|             params | ||||
|             { params } | ||||
|         ); | ||||
|         return searchResults ?? []; | ||||
|     } | ||||
|  |  | |||
|  | @ -1,18 +1,27 @@ | |||
| import {FilteredLearningObject} from "../../interfaces/learningContent"; | ||||
| import { | ||||
|     FilteredLearningObject, | ||||
|     LearningObjectIdentifier, | ||||
|     LearningPathIdentifier | ||||
| } from "../../interfaces/learning-content"; | ||||
| 
 | ||||
| export interface LearningObjectProvider { | ||||
|     /** | ||||
|      * Fetches a single learning object by its HRUID | ||||
|      */ | ||||
|     getLearningObjectById(hruid: string, language: string): Promise<FilteredLearningObject | null>; | ||||
|     getLearningObjectById(id: LearningObjectIdentifier): Promise<FilteredLearningObject | null>; | ||||
| 
 | ||||
|     /** | ||||
|      * Fetch full learning object data (metadata) | ||||
|      */ | ||||
|     getLearningObjectsFromPath(hruid: string, language: string): Promise<FilteredLearningObject[]>; | ||||
|     getLearningObjectsFromPath(id: LearningPathIdentifier): Promise<FilteredLearningObject[]>; | ||||
| 
 | ||||
|     /** | ||||
|      * Fetch only learning object HRUIDs | ||||
|      */ | ||||
|     getLearningObjectIdsFromPath(hruid: string, language: string): Promise<string[]>; | ||||
|     getLearningObjectIdsFromPath(id: LearningPathIdentifier): Promise<string[]>; | ||||
| 
 | ||||
|     /** | ||||
|      * Obtain a HTML-rendering of the learning object with the given identifier (as a string). | ||||
|      */ | ||||
|     getLearningObjectHTML(id: LearningObjectIdentifier): Promise<string | null>; | ||||
| } | ||||
|  |  | |||
|  | @ -1,5 +1,14 @@ | |||
| import {FilteredLearningObject} from "../../interfaces/learningContent"; | ||||
| import { | ||||
|     FilteredLearningObject, | ||||
|     LearningObjectIdentifier, | ||||
|     LearningPathIdentifier | ||||
| } from "../../interfaces/learning-content"; | ||||
| import dwengoApiLearningObjectProvider from "./dwengo-api/dwengo-api-learning-object-provider"; | ||||
| import {LearningObjectProvider} from "./learning-object-provider"; | ||||
| 
 | ||||
| function getProvider(id: LearningObjectIdentifier): LearningObjectProvider { | ||||
|     return dwengoApiLearningObjectProvider | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Service providing access to data about learning objects from the appropriate data source (database or Dwengo-api) | ||||
|  | @ -8,22 +17,29 @@ const learningObjectService = { | |||
|     /** | ||||
|      * Fetches a single learning object by its HRUID | ||||
|      */ | ||||
|     getLearningObjectById(hruid: string, language: string): Promise<FilteredLearningObject | null> { | ||||
|         return dwengoApiLearningObjectProvider.getLearningObjectById(hruid, language); | ||||
|     getLearningObjectById(id: LearningObjectIdentifier): Promise<FilteredLearningObject | null> { | ||||
|         return getProvider(id).getLearningObjectById(id); | ||||
|     }, | ||||
| 
 | ||||
|     /** | ||||
|      * Fetch full learning object data (metadata) | ||||
|      */ | ||||
|     getLearningObjectsFromPath(hruid: string, language: string): Promise<FilteredLearningObject[]> { | ||||
|         return dwengoApiLearningObjectProvider.getLearningObjectsFromPath(hruid, language); | ||||
|     getLearningObjectsFromPath(id: LearningPathIdentifier): Promise<FilteredLearningObject[]> { | ||||
|         return getProvider(id).getLearningObjectsFromPath(id); | ||||
|     }, | ||||
| 
 | ||||
|     /** | ||||
|      * Fetch only learning object HRUIDs | ||||
|      */ | ||||
|     getLearningObjectIdsFromPath(hruid: string, language: string): Promise<string[]> { | ||||
|         return dwengoApiLearningObjectProvider.getLearningObjectIdsFromPath(hruid, language); | ||||
|     getLearningObjectIdsFromPath(id: LearningPathIdentifier): Promise<string[]> { | ||||
|         return getProvider(id).getLearningObjectIdsFromPath(id); | ||||
|     }, | ||||
| 
 | ||||
|     /** | ||||
|      * Obtain a HTML-rendering of the learning object with the given identifier (as a string). | ||||
|      */ | ||||
|     getLearningObjectHTML(id: LearningObjectIdentifier): Promise<string | null> { | ||||
|         return getProvider(id).getLearningObjectHTML(id); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -8,17 +8,21 @@ import axios, { AxiosRequestConfig } from 'axios'; | |||
|  * | ||||
|  * @param url The API endpoint to fetch from. | ||||
|  * @param description A short description of what is being fetched (for logging). | ||||
|  * @param params | ||||
|  * @param options Contains further options such as params (the query params) and responseType (whether the response | ||||
|  *                should be parsed as JSON ("json") or whether it should be returned as plain text ("text") | ||||
|  * @returns The response data if successful, or null if an error occurs. | ||||
|  */ | ||||
| export async function fetchWithLogging<T>( | ||||
|     url: string, | ||||
|     description: string, | ||||
|     params?: Record<string, any> | ||||
|     options?: { | ||||
|         params?: Record<string, any>, | ||||
|         query?: Record<string, any>, | ||||
|         responseType?: "json" | "text", | ||||
|     } | ||||
| ): Promise<T | null> { | ||||
|     try { | ||||
|         const config: AxiosRequestConfig = params ? { params } : {}; | ||||
| 
 | ||||
|         const config: AxiosRequestConfig = options || {}; | ||||
|         const response = await axios.get<T>(url, config); | ||||
|         return response.data; | ||||
|     } catch (error: any) { | ||||
|  |  | |||
|  | @ -11,6 +11,8 @@ export const EnvVars: { [key: string]: EnvVar } = { | |||
|     DbUsername: { key: DB_PREFIX + 'USERNAME', required: true }, | ||||
|     DbPassword: { key: DB_PREFIX + 'PASSWORD', required: true }, | ||||
|     DbUpdate: { key: DB_PREFIX + 'UPDATE', defaultValue: false }, | ||||
|     LearningContentRepoApiBaseUrl: { key: PREFIX + "LEARNING_CONTENT_REPO_API_BASE_URL", defaultValue: "https://dwengo.org/backend/api"}, | ||||
|     FallbackLanguage: { key: PREFIX + "FALLBACK_LANGUAGE", defaultValue: "nl" }, | ||||
| } as const; | ||||
| 
 | ||||
| /** | ||||
|  |  | |||
		Reference in a new issue
	
	 Gerald Schmittinger
						Gerald Schmittinger