refactor(backend): Streamlining van de testdata voor leerpaden en leerobjecten + integratie in seed
Hierbij ook testdata functionaliteit toegevoegd om makkelijk nieuwe leerpaden aan te maken.
This commit is contained in:
		
							parent
							
								
									4092f1f617
								
							
						
					
					
						commit
						202cf4e33c
					
				
					 32 changed files with 691 additions and 493 deletions
				
			
		|  | @ -1,6 +1,10 @@ | |||
| import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; | ||||
| import { LearningPath } from '../../entities/content/learning-path.entity.js'; | ||||
| import { Language } from '@dwengo-1/common/util/language'; | ||||
| import {LearningPathNode} from "../../entities/content/learning-path-node.entity"; | ||||
| import {RequiredEntityData} from "@mikro-orm/core"; | ||||
| import {LearningPathTransition} from "../../entities/content/learning-path-transition.entity"; | ||||
| import {EntityAlreadyExistsException} from "../../exceptions/entity-already-exists-exception"; | ||||
| 
 | ||||
| export class LearningPathRepository extends DwengoEntityRepository<LearningPath> { | ||||
|     public async findByHruidAndLanguage(hruid: string, language: Language): Promise<LearningPath | null> { | ||||
|  | @ -23,4 +27,33 @@ export class LearningPathRepository extends DwengoEntityRepository<LearningPath> | |||
|             populate: ['nodes', 'nodes.transitions'], | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     public createNode( | ||||
|         nodeData: RequiredEntityData<LearningPathNode, never, false> | ||||
|     ): LearningPathNode { | ||||
|         return this.em.create(LearningPathNode, nodeData); | ||||
|     } | ||||
| 
 | ||||
|     public createTransition( | ||||
|         transitionData: RequiredEntityData<LearningPathTransition, never, false> | ||||
|     ): LearningPathTransition { | ||||
|         return this.em.create(LearningPathTransition, transitionData) | ||||
|     } | ||||
| 
 | ||||
|     public async saveLearningPathNodesAndTransitions( | ||||
|         path: LearningPath, | ||||
|         nodes: LearningPathNode[], | ||||
|         transitions: LearningPathTransition[], | ||||
|         options?: {preventOverwrite?: boolean} | ||||
|     ): Promise<void> { | ||||
|         if (options?.preventOverwrite && (await this.findOne(path))) { | ||||
|             throw new EntityAlreadyExistsException( | ||||
|                 "A learning path with this hruid/language combination already exists." | ||||
|             ); | ||||
|         } | ||||
|         const em = this.getEntityManager(); | ||||
|         await em.persistAndFlush(path); | ||||
|         await Promise.all(nodes.map(it => em.persistAndFlush(it))); | ||||
|         await Promise.all(transitions.map(it => em.persistAndFlush(it))); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property, Rel } from '@mikro-orm/core'; | ||||
| import {Collection, Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property, Rel} from '@mikro-orm/core'; | ||||
| import { LearningPath } from './learning-path.entity.js'; | ||||
| import { LearningPathTransition } from './learning-path-transition.entity.js'; | ||||
| import { Language } from '@dwengo-1/common/util/language'; | ||||
|  | @ -27,7 +27,7 @@ export class LearningPathNode { | |||
|     startNode!: boolean; | ||||
| 
 | ||||
|     @OneToMany({ entity: () => LearningPathTransition, mappedBy: 'node' }) | ||||
|     transitions: LearningPathTransition[] = []; | ||||
|     transitions: Collection<LearningPathTransition> = new Collection<LearningPathTransition>(this); | ||||
| 
 | ||||
|     @Property({ length: 3 }) | ||||
|     createdAt: Date = new Date(); | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { Entity, Enum, ManyToMany, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; | ||||
| import {Collection, Entity, Enum, ManyToMany, OneToMany, PrimaryKey, Property} from '@mikro-orm/core'; | ||||
| import { Teacher } from '../users/teacher.entity.js'; | ||||
| import { LearningPathRepository } from '../../data/content/learning-path-repository.js'; | ||||
| import { LearningPathNode } from './learning-path-node.entity.js'; | ||||
|  | @ -25,5 +25,5 @@ export class LearningPath { | |||
|     image: Buffer | null = null; | ||||
| 
 | ||||
|     @OneToMany({ entity: () => LearningPathNode, mappedBy: 'learningPath' }) | ||||
|     nodes: LearningPathNode[] = []; | ||||
|     nodes: Collection<LearningPathNode> = new Collection<LearningPathNode>(this); | ||||
| } | ||||
|  |  | |||
|  | @ -14,13 +14,14 @@ import { | |||
| } from '@dwengo-1/common/interfaces/learning-content'; | ||||
| import { Language } from '@dwengo-1/common/util/language'; | ||||
| import {Group} from "../../entities/assignments/group.entity"; | ||||
| import {Collection} from "@mikro-orm/core"; | ||||
| 
 | ||||
| /** | ||||
|  * Fetches the corresponding learning object for each of the nodes and creates a map that maps each node to its | ||||
|  * corresponding learning object. | ||||
|  * @param nodes The nodes to find the learning object for. | ||||
|  */ | ||||
| async function getLearningObjectsForNodes(nodes: LearningPathNode[]): Promise<Map<LearningPathNode, FilteredLearningObject>> { | ||||
| async function getLearningObjectsForNodes(nodes: Collection<LearningPathNode>): Promise<Map<LearningPathNode, FilteredLearningObject>> { | ||||
|     // Fetching the corresponding learning object for each of the nodes and creating a map that maps each node to
 | ||||
|     // Its corresponding learning object.
 | ||||
|     const nullableNodesToLearningObjects = new Map<LearningPathNode, FilteredLearningObject | null>( | ||||
|  | @ -208,7 +209,7 @@ const databaseLearningPathProvider: LearningPathProvider = { | |||
| 
 | ||||
|         const searchResults = await learningPathRepo.findByQueryStringAndLanguage(query, language); | ||||
|         return await Promise.all(searchResults.map(async (result, index) => convertLearningPath(result, index, personalizedFor))); | ||||
|     }, | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| export default databaseLearningPathProvider; | ||||
|  |  | |||
|  | @ -1,13 +1,79 @@ | |||
| import dwengoApiLearningPathProvider from './dwengo-api-learning-path-provider.js'; | ||||
| import databaseLearningPathProvider from './database-learning-path-provider.js'; | ||||
| import { envVars, getEnvVar } from '../../util/envVars.js'; | ||||
| import { LearningPath, LearningPathResponse } from '@dwengo-1/common/interfaces/learning-content'; | ||||
| import {LearningObjectNode, LearningPath, LearningPathResponse} from '@dwengo-1/common/interfaces/learning-content'; | ||||
| import { Language } from '@dwengo-1/common/util/language'; | ||||
| import {Group} from "../../entities/assignments/group.entity"; | ||||
| import {LearningPath as LearningPathEntity} from "../../entities/content/learning-path.entity"; | ||||
| import {getLearningPathRepository} from "../../data/repositories"; | ||||
| import {LearningPathNode} from "../../entities/content/learning-path-node.entity"; | ||||
| import {LearningPathTransition} from "../../entities/content/learning-path-transition.entity"; | ||||
| import {base64ToArrayBuffer} from "../../util/base64-buffer-conversion"; | ||||
| import {TeacherDTO} from "@dwengo-1/common/interfaces/teacher"; | ||||
| import {mapToTeacher} from "../../interfaces/teacher"; | ||||
| import {Collection} from "@mikro-orm/core"; | ||||
| 
 | ||||
| const userContentPrefix = getEnvVar(envVars.UserContentPrefix); | ||||
| const allProviders = [dwengoApiLearningPathProvider, databaseLearningPathProvider]; | ||||
| 
 | ||||
| export function mapToLearningPath( | ||||
|     dto: LearningPath, adminsDto: TeacherDTO[] | ||||
| ): LearningPathEntity { | ||||
|     const admins = adminsDto.map(admin => mapToTeacher(admin)); | ||||
|     const repo = getLearningPathRepository(); | ||||
|     const path = repo.create({ | ||||
|         hruid: dto.hruid, | ||||
|         language: dto.language as Language, | ||||
|         description: dto.description, | ||||
|         title: dto.title, | ||||
|         admins, | ||||
|         image: dto.image ? Buffer.from(base64ToArrayBuffer(dto.image)) : null | ||||
|     }); | ||||
|     const nodes = dto.nodes.map((nodeDto: LearningObjectNode, i: number) => | ||||
|         repo.createNode({ | ||||
|             learningPath: path, | ||||
|             learningObjectHruid: nodeDto.learningobject_hruid, | ||||
|             language: nodeDto.language, | ||||
|             version: nodeDto.version, | ||||
|             startNode: nodeDto.start_node ?? false, | ||||
|             nodeNumber: i, | ||||
|             createdAt: new Date(), | ||||
|             updatedAt: new Date() | ||||
|         }) | ||||
|     ); | ||||
|     dto.nodes.forEach(nodeDto => { | ||||
|         const fromNode = nodes.find(it => | ||||
|             it.learningObjectHruid === nodeDto.learningobject_hruid | ||||
|             && it.language === nodeDto.language | ||||
|             && it.version === nodeDto.version | ||||
|         )!; | ||||
|         const transitions = nodeDto.transitions.map((transDto, i) => { | ||||
|             const toNode = nodes.find(it => | ||||
|                 it.learningObjectHruid === transDto.next.hruid | ||||
|                 && it.language === transDto.next.language | ||||
|                 && it.version === transDto.next.version | ||||
|             ); | ||||
| 
 | ||||
|             if (toNode) { | ||||
|                 return repo.createTransition({ | ||||
|                     transitionNumber: i, | ||||
|                     node: fromNode, | ||||
|                     next: toNode, | ||||
|                     condition: transDto.condition ?? "true" | ||||
|                 }); | ||||
|             } else { | ||||
|                 return undefined; | ||||
|             } | ||||
|         }).filter(it => it).map(it => it!); | ||||
| 
 | ||||
|         fromNode.transitions = new Collection<LearningPathTransition>(transitions); | ||||
|     }); | ||||
| 
 | ||||
|     path.nodes = new Collection<LearningPathNode>(nodes); | ||||
| 
 | ||||
|     return path; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Service providing access to data about learning paths from the appropriate data source (database or Dwengo-api) | ||||
|  */ | ||||
|  | @ -54,6 +120,17 @@ const learningPathService = { | |||
|         ); | ||||
|         return providerResponses.flat(); | ||||
|     }, | ||||
| 
 | ||||
|     /** | ||||
|      * Add a new learning path to the database. | ||||
|      * @param dto Learning path DTO from which the learning path will be created. | ||||
|      * @param admins Teachers who should become an admin of the learning path. | ||||
|      */ | ||||
|     async createNewLearningPath(dto: LearningPath, admins: TeacherDTO[]): Promise<void> { | ||||
|         const repo = getLearningPathRepository(); | ||||
|         const path = mapToLearningPath(dto, admins); | ||||
|         await repo.save(path, {preventOverwrite: true}) | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| export default learningPathService; | ||||
|  |  | |||
							
								
								
									
										12
									
								
								backend/src/util/base64-buffer-conversion.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								backend/src/util/base64-buffer-conversion.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | |||
| /** | ||||
|  * Convert a Base64-encoded string into a buffer with the same data. | ||||
|  * @param base64 The Base64 encoded string. | ||||
|  */ | ||||
| export function base64ToArrayBuffer(base64: string) { | ||||
|     var binaryString = atob(base64); | ||||
|     var bytes = new Uint8Array(binaryString.length); | ||||
|     for (var i = 0; i < binaryString.length; i++) { | ||||
|         bytes[i] = binaryString.charCodeAt(i); | ||||
|     } | ||||
|     return bytes.buffer; | ||||
| } | ||||
		Reference in a new issue
	
	 Gerald Schmittinger
						Gerald Schmittinger