feat(backend): Vragen kunnen nu per leerobject, assignment en optioneel groepslid opgevraagd worden
This commit is contained in:
		
							parent
							
								
									64fd66a1de
								
							
						
					
					
						commit
						c863dc627f
					
				
					 7 changed files with 181 additions and 22 deletions
				
			
		|  | @ -1,11 +1,28 @@ | |||
| import { Request, Response } from 'express'; | ||||
| import { createQuestion, deleteQuestion, getAllQuestions, getAnswersByQuestion, getQuestion } from '../services/questions.js'; | ||||
| import { | ||||
|     createQuestion, | ||||
|     deleteQuestion, | ||||
|     getAllQuestions, | ||||
|     getAnswersByQuestion, | ||||
|     getQuestion, | ||||
|     getQuestionsAboutLearningObjectInAssignment | ||||
| } from '../services/questions.js'; | ||||
| import { FALLBACK_LANG, FALLBACK_SEQ_NUM } from '../config.js'; | ||||
| import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; | ||||
| import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question'; | ||||
| import { Language } from '@dwengo-1/common/util/language'; | ||||
| import {AnswerDTO, AnswerId} from "@dwengo-1/common/interfaces/answer"; | ||||
| 
 | ||||
| function getObjectId(req: Request, res: Response): LearningObjectIdentifier | null { | ||||
| interface QuestionPathParams { | ||||
|     hruid: string; | ||||
|     version: string; | ||||
| } | ||||
| 
 | ||||
| interface QuestionQueryParams { | ||||
|     lang: string; | ||||
| } | ||||
| 
 | ||||
| function getObjectId(req: Request<QuestionPathParams, any, any, QuestionQueryParams>, res: Response): LearningObjectIdentifier | null { | ||||
|     const { hruid, version } = req.params; | ||||
|     const lang = req.query.lang; | ||||
| 
 | ||||
|  | @ -21,7 +38,10 @@ function getObjectId(req: Request, res: Response): LearningObjectIdentifier | nu | |||
|     }; | ||||
| } | ||||
| 
 | ||||
| function getQuestionId(req: Request, res: Response): QuestionId | null { | ||||
| interface GetQuestionIdPathParams extends QuestionPathParams { | ||||
|     seq: string; | ||||
| } | ||||
| function getQuestionId(req: Request<GetQuestionIdPathParams, any, any, QuestionQueryParams>, res: Response): QuestionId | null { | ||||
|     const seq = req.params.seq; | ||||
|     const learningObjectIdentifier = getObjectId(req, res); | ||||
| 
 | ||||
|  | @ -35,15 +55,35 @@ function getQuestionId(req: Request, res: Response): QuestionId | null { | |||
|     }; | ||||
| } | ||||
| 
 | ||||
| export async function getAllQuestionsHandler(req: Request, res: Response): Promise<void> { | ||||
| interface GetAllQuestionsQueryParams extends QuestionQueryParams { | ||||
|     classId?: string, | ||||
|     assignmentId?: number, | ||||
|     forStudent?: string, | ||||
|     full?: boolean | ||||
| } | ||||
| 
 | ||||
| export async function getAllQuestionsHandler( | ||||
|     req: Request<QuestionPathParams, QuestionDTO[] | QuestionId[], unknown, GetAllQuestionsQueryParams>, | ||||
|     res: Response | ||||
| ): Promise<void> { | ||||
|     const objectId = getObjectId(req, res); | ||||
|     const full = req.query.full === 'true'; | ||||
|     const full = req.query.full; | ||||
| 
 | ||||
|     if (!objectId) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     const questions = await getAllQuestions(objectId, full); | ||||
|     let questions: QuestionDTO[] | QuestionId[]; | ||||
|     if (req.query.classId && req.query.assignmentId) { | ||||
|         questions = await getQuestionsAboutLearningObjectInAssignment( | ||||
|             objectId, | ||||
|             req.query.classId, | ||||
|             req.query.assignmentId, | ||||
|             full ?? false, | ||||
|             req.query.forStudent | ||||
|         ); | ||||
|     } else { | ||||
|         questions = await getAllQuestions(objectId, full ?? false); | ||||
|     } | ||||
| 
 | ||||
|     if (!questions) { | ||||
|         res.status(404).json({ error: `Questions not found.` }); | ||||
|  | @ -52,7 +92,10 @@ export async function getAllQuestionsHandler(req: Request, res: Response): Promi | |||
|     } | ||||
| } | ||||
| 
 | ||||
| export async function getQuestionHandler(req: Request, res: Response): Promise<void> { | ||||
| export async function getQuestionHandler( | ||||
|     req: Request<GetQuestionIdPathParams, QuestionDTO[] | QuestionId[], unknown, QuestionQueryParams>, | ||||
|     res: Response | ||||
| ): Promise<void> { | ||||
|     const questionId = getQuestionId(req, res); | ||||
| 
 | ||||
|     if (!questionId) { | ||||
|  | @ -68,9 +111,15 @@ export async function getQuestionHandler(req: Request, res: Response): Promise<v | |||
|     } | ||||
| } | ||||
| 
 | ||||
| export async function getQuestionAnswersHandler(req: Request, res: Response): Promise<void> { | ||||
| interface GetQuestionAnswersQueryParams extends QuestionQueryParams { | ||||
|     full: boolean | ||||
| } | ||||
| export async function getQuestionAnswersHandler( | ||||
|     req: Request<GetQuestionIdPathParams, {answers: AnswerDTO[] | AnswerId[]}, unknown, GetQuestionAnswersQueryParams>, | ||||
|     res: Response | ||||
| ): Promise<void> { | ||||
|     const questionId = getQuestionId(req, res); | ||||
|     const full = req.query.full === 'true'; | ||||
|     const full = req.query.full; | ||||
| 
 | ||||
|     if (!questionId) { | ||||
|         return; | ||||
|  | @ -102,7 +151,10 @@ export async function createQuestionHandler(req: Request, res: Response): Promis | |||
|     } | ||||
| } | ||||
| 
 | ||||
| export async function deleteQuestionHandler(req: Request, res: Response): Promise<void> { | ||||
| export async function deleteQuestionHandler( | ||||
|     req: Request<GetQuestionIdPathParams, QuestionDTO, unknown, QuestionQueryParams>, | ||||
|     res: Response | ||||
| ): Promise<void> { | ||||
|     const questionId = getQuestionId(req, res); | ||||
| 
 | ||||
|     if (!questionId) { | ||||
|  |  | |||
|  | @ -1,13 +1,45 @@ | |||
| import { Request, Response } from 'express'; | ||||
| import { createSubmission, deleteSubmission, getSubmission } from '../services/submissions.js'; | ||||
| import { | ||||
|     createSubmission, | ||||
|     deleteSubmission, | ||||
|     getSubmission, | ||||
|     getSubmissionsForLearningObjectAndAssignment | ||||
| } from '../services/submissions.js'; | ||||
| import { SubmissionDTO } from '@dwengo-1/common/interfaces/submission'; | ||||
| import { Language, languageMap } from '@dwengo-1/common/util/language'; | ||||
| import {Submission} from "../entities/assignments/submission.entity"; | ||||
| 
 | ||||
| interface SubmissionParams { | ||||
|     hruid: string; | ||||
|     id: number; | ||||
| } | ||||
| 
 | ||||
| interface SubmissionQuery { | ||||
|     language: string, | ||||
|     version: number; | ||||
| } | ||||
| 
 | ||||
| interface SubmissionsQuery extends SubmissionQuery { | ||||
|     classId: string, | ||||
|     assignmentId: number, | ||||
|     studentUsername?: string | ||||
| } | ||||
| 
 | ||||
| export async function getSubmissionsHandler( | ||||
|     req: Request<SubmissionParams, Submission[], null, SubmissionsQuery>, | ||||
|     res: Response | ||||
| ): Promise<void> { | ||||
|     const loHruid = req.params.hruid; | ||||
|     const lang = languageMap[req.query.language as string] || Language.Dutch; | ||||
|     const version = (req.query.version || 1) as number; | ||||
| 
 | ||||
|     const submissions = await getSubmissionsForLearningObjectAndAssignment( | ||||
|         loHruid, lang, version, req.query.classId, req.query.assignmentId | ||||
|     ); | ||||
| 
 | ||||
|     res.json(submissions); | ||||
| } | ||||
| 
 | ||||
| export async function getSubmissionHandler(req: Request<SubmissionParams>, res: Response): Promise<void> { | ||||
|     const lohruid = req.params.hruid; | ||||
|     const submissionNumber = Number(req.params.id); | ||||
|  |  | |||
|  | @ -6,6 +6,9 @@ export class AssignmentRepository extends DwengoEntityRepository<Assignment> { | |||
|     public async findByClassAndId(within: Class, id: number): Promise<Assignment | null> { | ||||
|         return this.findOne({ within: within, id: id }); | ||||
|     } | ||||
|     public async findByClassIdAndAssignmentId(withinClass: string, id: number): Promise<Assignment | null> { | ||||
|         return this.findOne({ within: { classId: withinClass }, id: id }); | ||||
|     } | ||||
|     public async findAllByResponsibleTeacher(teacherUsername: string): Promise<Assignment[]> { | ||||
|         return this.findAll({ | ||||
|             where: { | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ import { LearningObjectIdentifier } from '../../entities/content/learning-object | |||
| import { Student } from '../../entities/users/student.entity.js'; | ||||
| import { LearningObject } from '../../entities/content/learning-object.entity.js'; | ||||
| import {Group} from "../../entities/assignments/group.entity"; | ||||
| import {Assignment} from "../../entities/assignments/assignment.entity"; | ||||
| 
 | ||||
| export class QuestionRepository extends DwengoEntityRepository<Question> { | ||||
|     public async createQuestion(question: { loId: LearningObjectIdentifier; author: Student; inGroup: Group, content: string }): Promise<Question> { | ||||
|  | @ -64,4 +65,34 @@ export class QuestionRepository extends DwengoEntityRepository<Question> { | |||
|             orderBy: { timestamp: 'DESC' }, // New to old
 | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Looks up all questions for the given learning object which were asked as part of the given assignment. | ||||
|      * When forStudentUsername is set, only the questions within the given user's group are shown. | ||||
|      */ | ||||
|     public async findAllQuestionsAboutLearningObjectInAssignment( | ||||
|         loId: LearningObjectIdentifier, | ||||
|         assignment: Assignment, | ||||
|         forStudentUsername?: string | ||||
|     ): Promise<Question[]> { | ||||
|         let inGroup = forStudentUsername ? { | ||||
|             assignment, | ||||
|             members: { | ||||
|                 $some: { | ||||
|                     username: forStudentUsername | ||||
|                 } | ||||
|             } | ||||
|         } : { | ||||
|             assignment | ||||
|         }; | ||||
| 
 | ||||
|         return this.findAll({ | ||||
|             where: { | ||||
|                 learningObjectHruid: loId.hruid, | ||||
|                 learningObjectLanguage: loId.language, | ||||
|                 learningObjectVersion: loId.version, | ||||
|                 inGroup | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,13 +1,14 @@ | |||
| import express from 'express'; | ||||
| import { createSubmissionHandler, deleteSubmissionHandler, getSubmissionHandler } from '../controllers/submissions.js'; | ||||
| import { | ||||
|     createSubmissionHandler, | ||||
|     deleteSubmissionHandler, | ||||
|     getSubmissionHandler, | ||||
|     getSubmissionsHandler | ||||
| } from '../controllers/submissions.js'; | ||||
| const router = express.Router({ mergeParams: true }); | ||||
| 
 | ||||
| // Root endpoint used to search objects
 | ||||
| router.get('/', (_req, res) => { | ||||
|     res.json({ | ||||
|         submissions: ['0', '1'], | ||||
|     }); | ||||
| }); | ||||
| router.get('/', getSubmissionsHandler); | ||||
| 
 | ||||
| router.post('/:id', createSubmissionHandler); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| import { | ||||
|     getAnswerRepository, | ||||
|     getAnswerRepository, getAssignmentRepository, | ||||
|     getClassRepository, | ||||
|     getGroupRepository, | ||||
|     getQuestionRepository | ||||
|  | @ -16,6 +16,25 @@ import { AnswerDTO, AnswerId } from '@dwengo-1/common/interfaces/answer'; | |||
| import { AssignmentDTO } from "@dwengo-1/common/interfaces/assignment"; | ||||
| import { mapToAssignment } from "../interfaces/assignment"; | ||||
| 
 | ||||
| export async function getQuestionsAboutLearningObjectInAssignment( | ||||
|     loId: LearningObjectIdentifier, | ||||
|     classId: string, | ||||
|     assignmentId: number, | ||||
|     full: boolean, | ||||
|     studentUsername?: string | ||||
| ): Promise<QuestionDTO[] | QuestionId[]> { | ||||
|     const assignment = await getAssignmentRepository() | ||||
|         .findByClassIdAndAssignmentId(classId, assignmentId); | ||||
| 
 | ||||
|     const questions = await getQuestionRepository() | ||||
|         .findAllQuestionsAboutLearningObjectInAssignment(loId, assignment!, studentUsername); | ||||
| 
 | ||||
|     if (full) | ||||
|         return questions.map(q => mapToQuestionDTO(q)); | ||||
|     else | ||||
|         return questions.map(q => mapToQuestionDTOId(q)); | ||||
| } | ||||
| 
 | ||||
| export async function getAllQuestions(id: LearningObjectIdentifier, full: boolean): Promise<QuestionDTO[] | QuestionId[]> { | ||||
|     const questionRepository: QuestionRepository = getQuestionRepository(); | ||||
|     const questions = await questionRepository.findAllQuestionsAboutLearningObject(id); | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { getSubmissionRepository } from '../data/repositories.js'; | ||||
| import {getAssignmentRepository, getSubmissionRepository} from '../data/repositories.js'; | ||||
| import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; | ||||
| import { mapToSubmission, mapToSubmissionDTO } from '../interfaces/submission.js'; | ||||
| import { SubmissionDTO } from '@dwengo-1/common/interfaces/submission'; | ||||
|  | @ -55,3 +55,24 @@ export async function deleteSubmission( | |||
| 
 | ||||
|     return submission; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Returns all the submissions made by on behalf of any group the given student is in. | ||||
|  */ | ||||
| export async function getSubmissionsForLearningObjectAndAssignment( | ||||
|     learningObjectHruid: string, | ||||
|     language: Language, | ||||
|     version: number, | ||||
|     classId: string, | ||||
|     assignmentId: number, | ||||
|     studentUsername?: string | ||||
| ): Promise<SubmissionDTO[]> { | ||||
|     const loId = new LearningObjectIdentifier(learningObjectHruid, language, version); | ||||
|     const assignment = await getAssignmentRepository() | ||||
|                                                 .findByClassIdAndAssignmentId(classId, assignmentId); | ||||
| 
 | ||||
|     const submissions = await getSubmissionRepository() | ||||
|                     .findAllSubmissionsForLearningObjectAndAssignment(loId, assignment!, studentUsername); | ||||
| 
 | ||||
|     return submissions.map(s => mapToSubmissionDTO(s)); | ||||
| } | ||||
|  |  | |||
		Reference in a new issue
	
	 Gerald Schmittinger
						Gerald Schmittinger