fix: Problemen met PUT op leerpaden en verschillende kleinere problemen
This commit is contained in:
		
							parent
							
								
									2db5d77296
								
							
						
					
					
						commit
						96821c40ab
					
				
					 21 changed files with 205 additions and 103 deletions
				
			
		|  | @ -68,13 +68,14 @@ export async function getLearningPaths(req: Request, res: Response): Promise<voi | |||
| function postOrPutLearningPath(isPut: boolean): (req: AuthenticatedRequest, res: Response) => Promise<void> { | ||||
|     return async (req, res) => { | ||||
|         const path: LearningPath = req.body; | ||||
|         const teacher = await getTeacher(req.auth!.username); | ||||
|         if (isPut) { | ||||
|             if (req.params.hruid !== path.hruid || req.params.language !== path.language) { | ||||
|                 throw new BadRequestException("id_not_matching_query_params"); | ||||
|             } | ||||
|             await learningPathService.deleteLearningPath({hruid: path.hruid, language: path.language as Language}); | ||||
|         } | ||||
|         const teacher = await getTeacher(req.auth!.username); | ||||
|         res.json(await learningPathService.createNewLearningPath(path, [teacher], isPut)); | ||||
|         res.json(await learningPathService.createNewLearningPath(path, [teacher])); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -8,7 +8,10 @@ import { EntityAlreadyExistsException } from '../../exceptions/entity-already-ex | |||
| 
 | ||||
| export class LearningPathRepository extends DwengoEntityRepository<LearningPath> { | ||||
|     public async findByHruidAndLanguage(hruid: string, language: Language): Promise<LearningPath | null> { | ||||
|         return this.findOne({ hruid: hruid, language: language }, { populate: ['nodes', 'nodes.transitions'] }); | ||||
|         return this.findOne( | ||||
|             { hruid: hruid, language: language }, | ||||
|             { populate: ['nodes', 'nodes.transitions', 'admins'] } | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | @ -24,7 +27,7 @@ export class LearningPathRepository extends DwengoEntityRepository<LearningPath> | |||
|                 language: language, | ||||
|                 $or: [{ title: { $like: `%${query}%` } }, { description: { $like: `%${query}%` } }], | ||||
|             }, | ||||
|             populate: ['nodes', 'nodes.transitions'], | ||||
|             populate: ['nodes', 'nodes.transitions', 'admins'], | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  | @ -37,7 +40,8 @@ export class LearningPathRepository extends DwengoEntityRepository<LearningPath> | |||
|                 admins: { | ||||
|                     username: adminUsername | ||||
|                 } | ||||
|             } | ||||
|             }, | ||||
|             populate: ['nodes', 'nodes.transitions', 'admins'] | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { Collection, Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property, Rel } from '@mikro-orm/core'; | ||||
| import { Cascade, 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'; | ||||
|  | @ -26,7 +26,7 @@ export class LearningPathNode { | |||
|     @Property({ type: 'bool' }) | ||||
|     startNode!: boolean; | ||||
| 
 | ||||
|     @OneToMany({ entity: () => LearningPathTransition, mappedBy: 'node' }) | ||||
|     @OneToMany({ entity: () => LearningPathTransition, mappedBy: 'node', cascade: [Cascade.ALL] }) | ||||
|     transitions!: Collection<LearningPathTransition>; | ||||
| 
 | ||||
|     @Property({ length: 3 }) | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { Collection, Entity, Enum, ManyToMany, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; | ||||
| import { Cascade, 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'; | ||||
|  | @ -24,6 +24,6 @@ export class LearningPath { | |||
|     @Property({ type: 'blob', nullable: true }) | ||||
|     image: Buffer | null = null; | ||||
| 
 | ||||
|     @OneToMany({ entity: () => LearningPathNode, mappedBy: 'learningPath' }) | ||||
|     @OneToMany({ entity: () => LearningPathNode, mappedBy: 'learningPath', cascade: [Cascade.ALL] }) | ||||
|     nodes: Collection<LearningPathNode> = new Collection<LearningPathNode>(this); | ||||
| } | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| import { Language } from "@dwengo-1/common/util/language"; | ||||
| import learningPathService from "../../../services/learning-paths/learning-path-service"; | ||||
| import { authorize } from "../auth"; | ||||
| import { AuthenticatedRequest } from "../authenticated-request"; | ||||
|  | @ -5,8 +6,8 @@ import { AuthenticationInfo } from "../authentication-info"; | |||
| 
 | ||||
| export const onlyAdminsForLearningPath = authorize(async (auth: AuthenticationInfo, req: AuthenticatedRequest) => { | ||||
|     const adminsForLearningPath = await learningPathService.getAdmins({ | ||||
|         hruid: req.body.hruid, | ||||
|         language: req.body.language | ||||
|         hruid: req.params.hruid, | ||||
|         language: req.params.language as Language | ||||
|     }); | ||||
|     return adminsForLearningPath && adminsForLearningPath.includes(auth.username); | ||||
| }); | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| import express from 'express'; | ||||
| import { deleteLearningPath, getLearningPaths, postLearningPath, putLearningPath } from '../controllers/learning-paths.js'; | ||||
| import { teachersOnly } from '../middleware/auth/auth.js'; | ||||
| import { onlyAdminsForLearningObject } from '../middleware/auth/checks/learning-object-auth-checks.js'; | ||||
| import { onlyAdminsForLearningPath } from '../middleware/auth/checks/learning-path-auth-checks.js'; | ||||
| 
 | ||||
| const router = express.Router(); | ||||
| 
 | ||||
|  | @ -27,7 +27,7 @@ const router = express.Router(); | |||
| router.get('/', getLearningPaths); | ||||
| router.post('/', teachersOnly, postLearningPath) | ||||
| 
 | ||||
| router.put('/:hruid/:language', onlyAdminsForLearningObject, putLearningPath); | ||||
| router.delete('/:hruid/:language', onlyAdminsForLearningObject, deleteLearningPath); | ||||
| router.put('/:hruid/:language', onlyAdminsForLearningPath, putLearningPath); | ||||
| router.delete('/:hruid/:language', onlyAdminsForLearningPath, deleteLearningPath); | ||||
| 
 | ||||
| export default router; | ||||
|  |  | |||
|  | @ -72,6 +72,8 @@ const learningObjectService = { | |||
|             learningObject.hruid = getEnvVar(envVars.UserContentPrefix) + learningObject.hruid; | ||||
|         } | ||||
| 
 | ||||
|         await learningObjectRepository.getEntityManager().flush(); | ||||
| 
 | ||||
|         // Lookup the admin teachers based on their usernames and add them to the admins of the learning object.
 | ||||
|         const teacherRepo = getTeacherRepository(); | ||||
|         const adminTeachers = await Promise.all( | ||||
|  |  | |||
|  | @ -16,6 +16,9 @@ import { Language } from '@dwengo-1/common/util/language'; | |||
| import { Group } from '../../entities/assignments/group.entity'; | ||||
| import { Collection } from '@mikro-orm/core'; | ||||
| import { v4 } from 'uuid'; | ||||
| import { getLogger } from '../../logging/initalize.js'; | ||||
| 
 | ||||
| const logger = getLogger(); | ||||
| 
 | ||||
| /** | ||||
|  * Fetches the corresponding learning object for each of the nodes and creates a map that maps each node to its | ||||
|  | @ -38,8 +41,13 @@ async function getLearningObjectsForNodes(nodes: Collection<LearningPathNode>): | |||
|             ) | ||||
|         ) | ||||
|     ); | ||||
|     if (Array.from(nullableNodesToLearningObjects.values()).some((it) => it === null)) { | ||||
|         throw new Error('At least one of the learning objects on this path could not be found.'); | ||||
| 
 | ||||
|     // Ignore all learning objects that cannot be found such that the rest of the learning path keeps working.
 | ||||
|     for (const [key, value] of nullableNodesToLearningObjects) { | ||||
|         if (value === null) { | ||||
|             logger.warn(`Learning object ${key.learningObjectHruid}/${key.language}/${key.version} not found!`); | ||||
|             nullableNodesToLearningObjects.delete(key); | ||||
|         } | ||||
|     } | ||||
|     return nullableNodesToLearningObjects as Map<LearningPathNode, FilteredLearningObject>; | ||||
| } | ||||
|  | @ -102,7 +110,14 @@ async function convertNode( | |||
|                 !personalizedFor || // If we do not want a personalized learning path, keep all transitions
 | ||||
|                 isTransitionPossible(trans, optionalJsonStringToObject(lastSubmission?.content)) // Otherwise remove all transitions that aren't possible.
 | ||||
|         ) | ||||
|         .map((trans, i) => convertTransition(trans, i, nodesToLearningObjects)); | ||||
|         .map((trans, i) => { | ||||
|             try { | ||||
|                 return convertTransition(trans, i, nodesToLearningObjects) | ||||
|             } catch (_: unknown) { | ||||
|                 logger.error(`Transition could not be resolved: ${JSON.stringify(trans)}`); | ||||
|                 return undefined; // Do not crash on invalid transitions, just ignore them so the rest of the learning path keeps working.
 | ||||
|             } | ||||
|         }).filter(it => it); | ||||
|     return { | ||||
|         _id: learningObject.uuid, | ||||
|         language: learningObject.language, | ||||
|  |  | |||
|  | @ -130,13 +130,12 @@ const learningPathService = { | |||
|      * 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. | ||||
|      * @param allowReplace If this is set to true and there is already a learning path with the same identifier, it is replaced. | ||||
|      * @returns the created learning path. | ||||
|      */ | ||||
|     async createNewLearningPath(dto: LearningPath, admins: TeacherDTO[], allowReplace = false): Promise<LearningPathEntity> { | ||||
|     async createNewLearningPath(dto: LearningPath, admins: TeacherDTO[]): Promise<LearningPathEntity> { | ||||
|         const repo = getLearningPathRepository(); | ||||
|         const path = mapToLearningPath(dto, admins); | ||||
|         await repo.save(path, { preventOverwrite: allowReplace }); | ||||
|         await repo.save(path, { preventOverwrite: true }); | ||||
|         return path; | ||||
|     }, | ||||
| 
 | ||||
|  |  | |||
		Reference in a new issue
	
	 Gerald Schmittinger
						Gerald Schmittinger