From fb3c37ce5a2e84136c146bcd9a861695eb332f5f Mon Sep 17 00:00:00 2001 From: Gerald Schmittinger Date: Wed, 9 Apr 2025 19:51:15 +0200 Subject: [PATCH] fix(backend): Merge-conflicten opgelost. --- backend/src/controllers/questions.ts | 73 ++++++++++--------- backend/src/interfaces/question.ts | 2 +- .../checks/learning-content-auth-checks.ts | 23 ++++++ backend/src/routes/groups.ts | 6 +- backend/src/routes/learning-objects.ts | 9 ++- backend/src/routes/learning-paths.ts | 3 +- backend/src/services/questions.ts | 11 +-- common/src/interfaces/question.ts | 1 + 8 files changed, 79 insertions(+), 49 deletions(-) create mode 100644 backend/src/middleware/auth/checks/learning-content-auth-checks.ts diff --git a/backend/src/controllers/questions.ts b/backend/src/controllers/questions.ts index 282c90b8..cc677dba 100644 --- a/backend/src/controllers/questions.ts +++ b/backend/src/controllers/questions.ts @@ -7,11 +7,12 @@ import { getQuestion, getQuestionsAboutLearningObjectInAssignment, updateQuestion, } from '../services/questions.js'; -import { FALLBACK_LANG, FALLBACK_SEQ_NUM } from '../config.js'; +import {FALLBACK_LANG, FALLBACK_SEQ_NUM, FALLBACK_VERSION_NUM} from '../config.js'; import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; import { QuestionData, QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question'; import { Language } from '@dwengo-1/common/util/language'; import {requireFields} from "./error-helper"; +import {BadRequestException} from "../exceptions/bad-request-exception"; export function getLearningObjectId(hruid: string, version: string, lang: string): LearningObjectIdentifier { return { @@ -28,7 +29,7 @@ export function getQuestionId(learningObjectIdentifier: LearningObjectIdentifier }; } -function getQuestionId(req: Request, res: Response): QuestionId | null { +function getQuestionIdFromRequest(req: Request): QuestionId | null { const seq = req.params.seq; const hruid = req.params.hruid; const version = req.params.version; @@ -39,10 +40,7 @@ function getQuestionId(req: Request, res: Response): QuestionId | null { return null; } - return { - learningObjectIdentifier, - sequenceNumber: seq ? Number(seq) : FALLBACK_SEQ_NUM, - }; + return getQuestionId(learningObjectIdentifier, seq); } export async function getAllQuestionsHandler(req: Request, res: Response): Promise { @@ -52,16 +50,22 @@ export async function getAllQuestionsHandler(req: Request, res: Response): Promi const full = req.query.full === 'true'; requireFields({ hruid }); + const assignmentId = parseInt(req.query.assignmentId as string); + + if (isNaN(assignmentId)) { + throw new BadRequestException("The assignment ID must be a number."); + } + const learningObjectId = getLearningObjectId(hruid, version, language); let questions: QuestionDTO[] | QuestionId[]; if (req.query.classId && req.query.assignmentId) { questions = await getQuestionsAboutLearningObjectInAssignment( learningObjectId, - req.query.classId, - req.query.assignmentId, + req.query.classId as string, + parseInt(req.query.assignmentId as string), full ?? false, - req.query.forStudent + req.query.forStudent as string | undefined ); } else { questions = await getAllQuestions(learningObjectId, full ?? false); @@ -70,40 +74,37 @@ export async function getAllQuestionsHandler(req: Request, res: Response): Promi res.json({ questions }); } - export async function getQuestionHandler(req: Request, res: Response): Promise { - const hruid = req.params.hruid; - const version = req.params.version; - const language = req.query.lang as string; - const seq = req.params.seq; - requireFields({ hruid }); +export async function getQuestionHandler(req: Request, res: Response): Promise { + const hruid = req.params.hruid; + const version = req.params.version; + const language = req.query.lang as string; + const seq = req.params.seq; + requireFields({ hruid }); - const learningObjectId = getLearningObjectId(hruid, version, language); - const questionId = getQuestionId(learningObjectId, seq); + const learningObjectId = getLearningObjectId(hruid, version, language); + const questionId = getQuestionId(learningObjectId, seq); - const question = await getQuestion(questionId); + const question = await getQuestion(questionId); - res.json({ question }); + res.json({ question }); +} + +export async function getQuestionAnswersHandler(req: Request, res: Response): Promise { + const questionId = getQuestionIdFromRequest(req); + const full = req.query.full; + + if (!questionId) { + return; } - export async function getQuestionAnswersHandler( - req: Request, - res: Response - ): Promise { - const questionId = getQuestionId(req, res); - const full = req.query.full; + const answers = await getAnswersByQuestion(questionId, full === "true"); - if (!questionId) { - return; - } - - const answers = await getAnswersByQuestion(questionId, full); - - if (!answers) { - res.status(404).json({ error: `Questions not found` }); - } else { - res.json({ answers: answers }); - } + if (!answers) { + res.status(404).json({ error: `Questions not found` }); + } else { + res.json({ answers: answers }); } +} export async function createQuestionHandler(req: Request, res: Response): Promise { const hruid = req.params.hruid; diff --git a/backend/src/interfaces/question.ts b/backend/src/interfaces/question.ts index b4e58db7..ac334506 100644 --- a/backend/src/interfaces/question.ts +++ b/backend/src/interfaces/question.ts @@ -1,9 +1,9 @@ import { Question } from '../entities/questions/question.entity.js'; import { mapToStudentDTO } from './student.js'; import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question'; -import { LearningObjectIdentifier } from '@dwengo-1/common/interfaces/learning-content'; import { mapToGroupDTOId } from './group'; import { LearningObjectIdentifierDTO } from '@dwengo-1/common/interfaces/learning-content'; +import {LearningObjectIdentifier} from "../entities/content/learning-object-identifier"; function getLearningObjectIdentifier(question: Question): LearningObjectIdentifierDTO { return { diff --git a/backend/src/middleware/auth/checks/learning-content-auth-checks.ts b/backend/src/middleware/auth/checks/learning-content-auth-checks.ts new file mode 100644 index 00000000..57a3021d --- /dev/null +++ b/backend/src/middleware/auth/checks/learning-content-auth-checks.ts @@ -0,0 +1,23 @@ +import {authorize} from "./auth-checks"; +import {AuthenticationInfo} from "../authentication-info"; +import {AuthenticatedRequest} from "../authenticated-request"; +import {getGroup} from "../../../services/groups"; + +/** + * Only allows requests whose learning path personalization query parameters ('forGroup' / 'assignmentNo' / 'classId') + * are + * - either not set + * - or set to a group the user is in, + * - or set to anything if the user is a teacher. + */ +export const onlyAllowPersonalizationForOwnGroup = authorize( + async (auth: AuthenticationInfo, req: AuthenticatedRequest) => { + const {forGroup, assignmentNo, classId} = req.params; + if (forGroup && assignmentNo && classId) { + const group = getGroup(forGroup, parseInt(assignmentNo), classId, false); + + } else { + return true; + } + } +); diff --git a/backend/src/routes/groups.ts b/backend/src/routes/groups.ts index e8e83fb2..93500eb3 100644 --- a/backend/src/routes/groups.ts +++ b/backend/src/routes/groups.ts @@ -1,13 +1,15 @@ import express from 'express'; import { createGroupHandler, getAllGroupsHandler, getGroupHandler, getGroupSubmissionsHandler } from '../controllers/groups.js'; import {onlyAllowIfHasAccessToGroup} from "../middleware/auth/checks/group-auth-checker"; +import {teachersOnly} from "../middleware/auth/checks/auth-checks"; +import {onlyAllowIfHasAccessToAssignment} from "../middleware/auth/checks/assignment-auth-checks"; const router = express.Router({ mergeParams: true }); // Root endpoint used to search objects -router.get('/', getAllGroupsHandler); +router.get('/', onlyAllowIfHasAccessToAssignment, getAllGroupsHandler); -router.post('/', createGroupHandler); +router.post('/', teachersOnly, onlyAllowIfHasAccessToAssignment, createGroupHandler); // Information about a group (members, ... [TODO DOC]) router.get('/:groupid', onlyAllowIfHasAccessToGroup, getGroupHandler); diff --git a/backend/src/routes/learning-objects.ts b/backend/src/routes/learning-objects.ts index 7532765b..fb65d9cd 100644 --- a/backend/src/routes/learning-objects.ts +++ b/backend/src/routes/learning-objects.ts @@ -3,6 +3,7 @@ import { getAllLearningObjects, getAttachment, getLearningObject, getLearningObj import submissionRoutes from './submissions.js'; import questionRoutes from './questions.js'; +import {authenticatedOnly} from "../middleware/auth/checks/auth-checks"; const router = express.Router(); @@ -16,13 +17,13 @@ const router = express.Router(); // Route 2: list of object data // Example 2: http://localhost:3000/learningObject?full=true&hruid=un_artificiele_intelligentie -router.get('/', getAllLearningObjects); +router.get('/', authenticatedOnly, getAllLearningObjects); // Parameter: hruid of learning object // Query: language // Route to fetch data of one learning object based on its hruid // Example: http://localhost:3000/learningObject/un_ai7 -router.get('/:hruid', getLearningObject); +router.get('/:hruid', authenticatedOnly, getLearningObject); router.use('/:hruid/submissions', submissionRoutes); @@ -32,12 +33,12 @@ router.use('/:hruid/:version/questions', questionRoutes); // 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); +router.get('/:hruid/html', authenticatedOnly, getLearningObjectHTML); // Parameter: hruid of learning object, name of attachment. // Query: language, version (optional). // Route to get the raw data of the attachment for one learning object based on its hruid. // Example: http://localhost:3000/learningObject/u_test/attachment/testimage.png -router.get('/:hruid/html/:attachmentName', getAttachment); +router.get('/:hruid/html/:attachmentName', authenticatedOnly, getAttachment); export default router; diff --git a/backend/src/routes/learning-paths.ts b/backend/src/routes/learning-paths.ts index efe17312..ad079551 100644 --- a/backend/src/routes/learning-paths.ts +++ b/backend/src/routes/learning-paths.ts @@ -1,5 +1,6 @@ import express from 'express'; import { getLearningPaths } from '../controllers/learning-paths.js'; +import {authenticatedOnly} from "../middleware/auth/checks/auth-checks"; const router = express.Router(); @@ -22,6 +23,6 @@ const router = express.Router(); // Route to fetch learning paths based on a theme // Example: http://localhost:3000/learningPath?theme=kiks -router.get('/', getLearningPaths); +router.get('/', authenticatedOnly, getLearningPaths); export default router; diff --git a/backend/src/services/questions.ts b/backend/src/services/questions.ts index 45bfa750..9c719472 100644 --- a/backend/src/services/questions.ts +++ b/backend/src/services/questions.ts @@ -11,11 +11,13 @@ import { mapToAnswerDTO, mapToAnswerDTOId } from '../interfaces/answer.js'; import { QuestionRepository } from '../data/questions/question-repository.js'; import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; import { mapToStudent } from '../interfaces/student.js'; -import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question'; +import {QuestionData, QuestionDTO, QuestionId} from '@dwengo-1/common/interfaces/question'; import { AnswerDTO, AnswerId } from '@dwengo-1/common/interfaces/answer'; import {fetchStudent} from "./students"; import {mapToAssignment} from "../interfaces/assignment"; import { NotFoundException } from '../exceptions/not-found-exception.js'; +import {AssignmentDTO} from "@dwengo-1/common/interfaces/assignment"; +import {FALLBACK_VERSION_NUM} from "../config"; export async function getQuestionsAboutLearningObjectInAssignment( loId: LearningObjectIdentifier, @@ -90,10 +92,9 @@ export async function createQuestion(loId: LearningObjectIdentifier, questionDat const author = await fetchStudent(questionData.author!); const content = questionData.content; - const clazz = await getClassRepository().findById((questionDTO.inGroup.assignment as AssignmentDTO).class); - let questionDTO; - const assignment = mapToAssignment(questionDTO.inGroup.assignment as AssignmentDTO, clazz!); - const inGroup = await getGroupRepository().findByAssignmentAndGroupNumber(assignment, questionDTO.inGroup.groupNumber); + const clazz = await getClassRepository().findById((questionData.inGroup.assignment as AssignmentDTO).class); + const assignment = mapToAssignment(questionData.inGroup.assignment as AssignmentDTO, clazz!); + const inGroup = (await getGroupRepository().findByAssignmentAndGroupNumber(assignment, questionData.inGroup.groupNumber))!; const question = await questionRepository.createQuestion({ loId, diff --git a/common/src/interfaces/question.ts b/common/src/interfaces/question.ts index 582e12dd..2d681fc0 100644 --- a/common/src/interfaces/question.ts +++ b/common/src/interfaces/question.ts @@ -13,6 +13,7 @@ export interface QuestionDTO { export interface QuestionData { author?: string; + inGroup: GroupDTO; content: string; }