fix: Problemen met PUT op leerpaden en verschillende kleinere problemen

This commit is contained in:
Gerald Schmittinger 2025-05-13 16:21:06 +02:00
parent 2db5d77296
commit 96821c40ab
21 changed files with 205 additions and 103 deletions

View file

@ -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]));
}
}

View file

@ -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']
});
}

View file

@ -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 })

View file

@ -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);
}

View file

@ -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);
});

View file

@ -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;

View file

@ -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(

View file

@ -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,

View file

@ -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;
},