diff --git a/backend/src/controllers/questions.ts b/backend/src/controllers/questions.ts index 54b50fa9..da1d020e 100644 --- a/backend/src/controllers/questions.ts +++ b/backend/src/controllers/questions.ts @@ -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, 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, 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 { +interface GetAllQuestionsQueryParams extends QuestionQueryParams { + classId?: string, + assignmentId?: number, + forStudent?: string, + full?: boolean +} + +export async function getAllQuestionsHandler( + req: Request, + res: Response +): Promise { 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 { +export async function getQuestionHandler( + req: Request, + res: Response +): Promise { const questionId = getQuestionId(req, res); if (!questionId) { @@ -68,9 +111,15 @@ export async function getQuestionHandler(req: Request, res: Response): Promise { +interface GetQuestionAnswersQueryParams extends QuestionQueryParams { + full: boolean +} +export async function getQuestionAnswersHandler( + req: Request, + res: Response +): Promise { 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 { +export async function deleteQuestionHandler( + req: Request, + res: Response +): Promise { const questionId = getQuestionId(req, res); if (!questionId) { diff --git a/backend/src/controllers/submissions.ts b/backend/src/controllers/submissions.ts index 239eb6d7..3a5aa391 100644 --- a/backend/src/controllers/submissions.ts +++ b/backend/src/controllers/submissions.ts @@ -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, + res: Response +): Promise { + 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, res: Response): Promise { const lohruid = req.params.hruid; const submissionNumber = Number(req.params.id); diff --git a/backend/src/data/assignments/assignment-repository.ts b/backend/src/data/assignments/assignment-repository.ts index 296e67fd..c6766af9 100644 --- a/backend/src/data/assignments/assignment-repository.ts +++ b/backend/src/data/assignments/assignment-repository.ts @@ -6,6 +6,9 @@ export class AssignmentRepository extends DwengoEntityRepository { public async findByClassAndId(within: Class, id: number): Promise { return this.findOne({ within: within, id: id }); } + public async findByClassIdAndAssignmentId(withinClass: string, id: number): Promise { + return this.findOne({ within: { classId: withinClass }, id: id }); + } public async findAllByResponsibleTeacher(teacherUsername: string): Promise { return this.findAll({ where: { diff --git a/backend/src/data/questions/question-repository.ts b/backend/src/data/questions/question-repository.ts index 51df5afe..f36b1074 100644 --- a/backend/src/data/questions/question-repository.ts +++ b/backend/src/data/questions/question-repository.ts @@ -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 { public async createQuestion(question: { loId: LearningObjectIdentifier; author: Student; inGroup: Group, content: string }): Promise { @@ -64,4 +65,34 @@ export class QuestionRepository extends DwengoEntityRepository { 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 { + let inGroup = forStudentUsername ? { + assignment, + members: { + $some: { + username: forStudentUsername + } + } + } : { + assignment + }; + + return this.findAll({ + where: { + learningObjectHruid: loId.hruid, + learningObjectLanguage: loId.language, + learningObjectVersion: loId.version, + inGroup + } + }); + } } diff --git a/backend/src/routes/submissions.ts b/backend/src/routes/submissions.ts index 8e9831b9..930e1a59 100644 --- a/backend/src/routes/submissions.ts +++ b/backend/src/routes/submissions.ts @@ -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); diff --git a/backend/src/services/questions.ts b/backend/src/services/questions.ts index 0bf5c734..95c0f6fd 100644 --- a/backend/src/services/questions.ts +++ b/backend/src/services/questions.ts @@ -1,5 +1,5 @@ import { - getAnswerRepository, + getAnswerRepository, getAssignmentRepository, getClassRepository, getGroupRepository, getQuestionRepository @@ -13,8 +13,27 @@ import { LearningObjectIdentifier } from '../entities/content/learning-object-id import { mapToStudent } from '../interfaces/student.js'; import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question'; import { AnswerDTO, AnswerId } from '@dwengo-1/common/interfaces/answer'; -import {AssignmentDTO} from "@dwengo-1/common/interfaces/assignment"; -import {mapToAssignment} from "../interfaces/assignment"; +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 { + 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 { const questionRepository: QuestionRepository = getQuestionRepository(); diff --git a/backend/src/services/submissions.ts b/backend/src/services/submissions.ts index 1d8a7874..08a19481 100644 --- a/backend/src/services/submissions.ts +++ b/backend/src/services/submissions.ts @@ -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 { + 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)); +}