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 { 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 { FALLBACK_LANG, FALLBACK_SEQ_NUM } from '../config.js'; | ||||||
| import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; | import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; | ||||||
| import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question'; | import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question'; | ||||||
| import { Language } from '@dwengo-1/common/util/language'; | 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 { hruid, version } = req.params; | ||||||
|     const lang = req.query.lang; |     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 seq = req.params.seq; | ||||||
|     const learningObjectIdentifier = getObjectId(req, res); |     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 objectId = getObjectId(req, res); | ||||||
|     const full = req.query.full === 'true'; |     const full = req.query.full; | ||||||
| 
 | 
 | ||||||
|     if (!objectId) { |     if (!objectId) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 |     let questions: QuestionDTO[] | QuestionId[]; | ||||||
|     const questions = await getAllQuestions(objectId, full); |     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) { |     if (!questions) { | ||||||
|         res.status(404).json({ error: `Questions not found.` }); |         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); |     const questionId = getQuestionId(req, res); | ||||||
| 
 | 
 | ||||||
|     if (!questionId) { |     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 questionId = getQuestionId(req, res); | ||||||
|     const full = req.query.full === 'true'; |     const full = req.query.full; | ||||||
| 
 | 
 | ||||||
|     if (!questionId) { |     if (!questionId) { | ||||||
|         return; |         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); |     const questionId = getQuestionId(req, res); | ||||||
| 
 | 
 | ||||||
|     if (!questionId) { |     if (!questionId) { | ||||||
|  |  | ||||||
|  | @ -1,13 +1,45 @@ | ||||||
| import { Request, Response } from 'express'; | 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 { SubmissionDTO } from '@dwengo-1/common/interfaces/submission'; | ||||||
| import { Language, languageMap } from '@dwengo-1/common/util/language'; | import { Language, languageMap } from '@dwengo-1/common/util/language'; | ||||||
|  | import {Submission} from "../entities/assignments/submission.entity"; | ||||||
| 
 | 
 | ||||||
| interface SubmissionParams { | interface SubmissionParams { | ||||||
|     hruid: string; |     hruid: string; | ||||||
|     id: number; |     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> { | export async function getSubmissionHandler(req: Request<SubmissionParams>, res: Response): Promise<void> { | ||||||
|     const lohruid = req.params.hruid; |     const lohruid = req.params.hruid; | ||||||
|     const submissionNumber = Number(req.params.id); |     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> { |     public async findByClassAndId(within: Class, id: number): Promise<Assignment | null> { | ||||||
|         return this.findOne({ within: within, id: id }); |         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[]> { |     public async findAllByResponsibleTeacher(teacherUsername: string): Promise<Assignment[]> { | ||||||
|         return this.findAll({ |         return this.findAll({ | ||||||
|             where: { |             where: { | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ import { LearningObjectIdentifier } from '../../entities/content/learning-object | ||||||
| import { Student } from '../../entities/users/student.entity.js'; | import { Student } from '../../entities/users/student.entity.js'; | ||||||
| import { LearningObject } from '../../entities/content/learning-object.entity.js'; | import { LearningObject } from '../../entities/content/learning-object.entity.js'; | ||||||
| import {Group} from "../../entities/assignments/group.entity"; | import {Group} from "../../entities/assignments/group.entity"; | ||||||
|  | import {Assignment} from "../../entities/assignments/assignment.entity"; | ||||||
| 
 | 
 | ||||||
| export class QuestionRepository extends DwengoEntityRepository<Question> { | export class QuestionRepository extends DwengoEntityRepository<Question> { | ||||||
|     public async createQuestion(question: { loId: LearningObjectIdentifier; author: Student; inGroup: Group, content: string }): Promise<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
 |             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 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 }); | const router = express.Router({ mergeParams: true }); | ||||||
| 
 | 
 | ||||||
| // Root endpoint used to search objects
 | // Root endpoint used to search objects
 | ||||||
| router.get('/', (_req, res) => { | router.get('/', getSubmissionsHandler); | ||||||
|     res.json({ |  | ||||||
|         submissions: ['0', '1'], |  | ||||||
|     }); |  | ||||||
| }); |  | ||||||
| 
 | 
 | ||||||
| router.post('/:id', createSubmissionHandler); | router.post('/:id', createSubmissionHandler); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import { | import { | ||||||
|     getAnswerRepository, |     getAnswerRepository, getAssignmentRepository, | ||||||
|     getClassRepository, |     getClassRepository, | ||||||
|     getGroupRepository, |     getGroupRepository, | ||||||
|     getQuestionRepository |     getQuestionRepository | ||||||
|  | @ -16,6 +16,25 @@ import { AnswerDTO, AnswerId } from '@dwengo-1/common/interfaces/answer'; | ||||||
| import { AssignmentDTO } from "@dwengo-1/common/interfaces/assignment"; | import { AssignmentDTO } from "@dwengo-1/common/interfaces/assignment"; | ||||||
| import { mapToAssignment } from "../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[]> { | export async function getAllQuestions(id: LearningObjectIdentifier, full: boolean): Promise<QuestionDTO[] | QuestionId[]> { | ||||||
|     const questionRepository: QuestionRepository = getQuestionRepository(); |     const questionRepository: QuestionRepository = getQuestionRepository(); | ||||||
|     const questions = await questionRepository.findAllQuestionsAboutLearningObject(id); |     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 { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; | ||||||
| import { mapToSubmission, mapToSubmissionDTO } from '../interfaces/submission.js'; | import { mapToSubmission, mapToSubmissionDTO } from '../interfaces/submission.js'; | ||||||
| import { SubmissionDTO } from '@dwengo-1/common/interfaces/submission'; | import { SubmissionDTO } from '@dwengo-1/common/interfaces/submission'; | ||||||
|  | @ -55,3 +55,24 @@ export async function deleteSubmission( | ||||||
| 
 | 
 | ||||||
|     return submission; |     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