From 62711f323dc8a3ffbc0e21e83392f7fc1b83cc2a Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sun, 30 Mar 2025 19:24:52 +0200 Subject: [PATCH 01/43] feat: assignment/questions endpoint toegevoegd --- backend/src/controllers/classes.ts | 4 +- backend/src/controllers/groups.ts | 1 + .../src/data/questions/question-repository.ts | 9 +++ backend/src/routes/groups.ts | 9 +-- backend/src/services/assignments.ts | 59 +++++++++++++------ backend/src/services/classes.ts | 29 ++++----- backend/src/services/groups.ts | 51 +++++----------- 7 files changed, 82 insertions(+), 80 deletions(-) diff --git a/backend/src/controllers/classes.ts b/backend/src/controllers/classes.ts index 7526f7c4..ba11da8e 100644 --- a/backend/src/controllers/classes.ts +++ b/backend/src/controllers/classes.ts @@ -1,5 +1,5 @@ import { Request, Response } from 'express'; -import { createClass, getAllClasses, getClass, getClassStudents, getClassStudentsIds, getClassTeacherInvitations } from '../services/classes.js'; +import { createClass, getAllClasses, getClass, getClassStudents, getClassTeacherInvitations } from '../services/classes.js'; import { ClassDTO } from '../interfaces/class.js'; export async function getAllClassesHandler(req: Request, res: Response): Promise { @@ -47,7 +47,7 @@ export async function getClassStudentsHandler(req: Request, res: Response): Prom const classId = req.params.id; const full = req.query.full === 'true'; - const students = full ? await getClassStudents(classId) : await getClassStudentsIds(classId); + const students = getClassStudents(classId, full); res.json({ students: students, diff --git a/backend/src/controllers/groups.ts b/backend/src/controllers/groups.ts index 38d5d5d0..58624186 100644 --- a/backend/src/controllers/groups.ts +++ b/backend/src/controllers/groups.ts @@ -12,6 +12,7 @@ interface GroupParams { export async function getGroupHandler(req: Request, res: Response): Promise { const classId = req.params.classid; const full = req.query.full === 'true'; + const assignmentId = +req.params.assignmentid; if (isNaN(assignmentId)) { diff --git a/backend/src/data/questions/question-repository.ts b/backend/src/data/questions/question-repository.ts index 9207e1dd..3fc92df1 100644 --- a/backend/src/data/questions/question-repository.ts +++ b/backend/src/data/questions/question-repository.ts @@ -3,6 +3,7 @@ import { Question } from '../../entities/questions/question.entity.js'; import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js'; import { Student } from '../../entities/users/student.entity.js'; import { LearningObject } from '../../entities/content/learning-object.entity.js'; +import { Assignment } from '../../entities/assignments/assignment.entity.js'; export class QuestionRepository extends DwengoEntityRepository { public createQuestion(question: { loId: LearningObjectIdentifier; author: Student; content: string }): Promise { @@ -54,4 +55,12 @@ export class QuestionRepository extends DwengoEntityRepository { orderBy: { timestamp: 'ASC' }, }); } + + public findAllByAssignment(assignment: Assignment): Promise { + return this.find({ + author: assignment.groups.flatMap(group => group.members), + learningObjectHruid: assignment.learningPathHruid, + learningObjectLanguage: assignment.learningPathLanguage, + }); + } } diff --git a/backend/src/routes/groups.ts b/backend/src/routes/groups.ts index 0c9692b0..81532a90 100644 --- a/backend/src/routes/groups.ts +++ b/backend/src/routes/groups.ts @@ -11,13 +11,6 @@ router.post('/', createGroupHandler); // Information about a group (members, ... [TODO DOC]) router.get('/:groupid', getGroupHandler); -router.get('/:groupid', getGroupSubmissionsHandler); - -// The list of questions a group has made -router.get('/:id/questions', (req, res) => { - res.json({ - questions: ['0'], - }); -}); +router.get('/:groupid/submissions', getGroupSubmissionsHandler); export default router; diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts index a21a96fa..05e8099a 100644 --- a/backend/src/services/assignments.ts +++ b/backend/src/services/assignments.ts @@ -1,7 +1,23 @@ -import { getAssignmentRepository, getClassRepository, getGroupRepository, getSubmissionRepository } from '../data/repositories.js'; +import { getAssignmentRepository, getClassRepository, getGroupRepository, getQuestionRepository, getSubmissionRepository } from '../data/repositories.js'; +import { Assignment } from '../entities/assignments/assignment.entity.js'; import { AssignmentDTO, mapToAssignment, mapToAssignmentDTO, mapToAssignmentDTOId } from '../interfaces/assignment.js'; +import { mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId } from '../interfaces/question.js'; import { mapToSubmissionDTO, mapToSubmissionDTOId, SubmissionDTO, SubmissionDTOId } from '../interfaces/submission.js'; +export async function fetchAssignment(classid: string, assignmentNumber: number): Promise { + const classRepository = getClassRepository(); + const cls = await classRepository.findById(classid); + + if (!cls) { + return null; + } + + const assignmentRepository = getAssignmentRepository(); + const assignment = await assignmentRepository.findByClassAndId(cls, assignmentNumber); + + return assignment; +} + export async function getAllAssignments(classid: string, full: boolean): Promise { const classRepository = getClassRepository(); const cls = await classRepository.findById(classid); @@ -43,15 +59,7 @@ export async function createAssignment(classid: string, assignmentData: Assignme } export async function getAssignment(classid: string, id: number): Promise { - const classRepository = getClassRepository(); - const cls = await classRepository.findById(classid); - - if (!cls) { - return null; - } - - const assignmentRepository = getAssignmentRepository(); - const assignment = await assignmentRepository.findByClassAndId(cls, id); + const assignment = await fetchAssignment(classid, id); if (!assignment) { return null; @@ -65,15 +73,7 @@ export async function getAssignmentsSubmissions( assignmentNumber: number, full: boolean ): Promise { - const classRepository = getClassRepository(); - const cls = await classRepository.findById(classid); - - if (!cls) { - return []; - } - - const assignmentRepository = getAssignmentRepository(); - const assignment = await assignmentRepository.findByClassAndId(cls, assignmentNumber); + const assignment = await fetchAssignment(classid, assignmentNumber); if (!assignment) { return []; @@ -91,3 +91,24 @@ export async function getAssignmentsSubmissions( return submissions.map(mapToSubmissionDTOId); } + +export async function getAssignmentsQuestions( + classid: string, + assignmentNumber: number, + full: boolean +): Promise { + const assignment = await fetchAssignment(classid, assignmentNumber); + + if (!assignment) { + return []; + } + + const questionRepository = getQuestionRepository(); + const questions = await questionRepository.findAllByAssignment(assignment); + + if (full) { + return questions.map(mapToQuestionDTO); + } + + return questions.map(mapToQuestionDTO).map(mapToQuestionId); // mapToQuestionId should be updated +} \ No newline at end of file diff --git a/backend/src/services/classes.ts b/backend/src/services/classes.ts index 5b1e3cfc..dbf6c63d 100644 --- a/backend/src/services/classes.ts +++ b/backend/src/services/classes.ts @@ -1,4 +1,5 @@ import { getClassRepository, getStudentRepository, getTeacherInvitationRepository, getTeacherRepository } from '../data/repositories.js'; +import { Class } from '../entities/classes/class.entity.js'; import { ClassDTO, mapToClassDTO } from '../interfaces/class.js'; import { mapToStudentDTO, StudentDTO } from '../interfaces/student.js'; import { mapToTeacherInvitationDTO, mapToTeacherInvitationDTOIds, TeacherInvitationDTO } from '../interfaces/teacher-invitation.js'; @@ -6,6 +7,13 @@ import { getLogger } from '../logging/initalize.js'; const logger = getLogger(); +async function fetchClass(classid: string): Promise { + const classRepository = getClassRepository(); + const cls = await classRepository.findById(classid); + + return cls; +} + export async function getAllClasses(full: boolean): Promise { const classRepository = getClassRepository(); const classes = await classRepository.find({}, { populate: ['students', 'teachers'] }); @@ -47,8 +55,7 @@ export async function createClass(classData: ClassDTO): Promise } export async function getClass(classId: string): Promise { - const classRepository = getClassRepository(); - const cls = await classRepository.findById(classId); + const cls = await fetchClass(classId); if (!cls) { return null; @@ -57,24 +64,18 @@ export async function getClass(classId: string): Promise { return mapToClassDTO(cls); } -async function fetchClassStudents(classId: string): Promise { - const classRepository = getClassRepository(); - const cls = await classRepository.findById(classId); +export async function getClassStudents(classId: string, full: boolean): Promise { + const cls = await fetchClass(classId); if (!cls) { return []; } - return cls.students.map(mapToStudentDTO); -} + if (full) { + return cls.students.map(mapToStudentDTO); + } -export async function getClassStudents(classId: string): Promise { - return await fetchClassStudents(classId); -} - -export async function getClassStudentsIds(classId: string): Promise { - const students: StudentDTO[] = await fetchClassStudents(classId); - return students.map((student) => student.username); + return cls.students.map((student) => student.username); } export async function getClassTeacherInvitations(classId: string, full: boolean): Promise { diff --git a/backend/src/services/groups.ts b/backend/src/services/groups.ts index 4a1cbbf0..7c5afb82 100644 --- a/backend/src/services/groups.ts +++ b/backend/src/services/groups.ts @@ -1,24 +1,20 @@ +import { assign } from '@mikro-orm/core'; import { getAssignmentRepository, getClassRepository, getGroupRepository, + getQuestionRepository, getStudentRepository, getSubmissionRepository, } from '../data/repositories.js'; import { Group } from '../entities/assignments/group.entity.js'; import { GroupDTO, mapToGroupDTO, mapToGroupDTOId } from '../interfaces/group.js'; import { mapToSubmissionDTO, mapToSubmissionDTOId, SubmissionDTO, SubmissionDTOId } from '../interfaces/submission.js'; +import { fetchAssignment } from './assignments.js'; +import { mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId } from '../interfaces/question.js'; -export async function getGroup(classId: string, assignmentNumber: number, groupNumber: number, full: boolean): Promise { - const classRepository = getClassRepository(); - const cls = await classRepository.findById(classId); - - if (!cls) { - return null; - } - - const assignmentRepository = getAssignmentRepository(); - const assignment = await assignmentRepository.findByClassAndId(cls, assignmentNumber); +async function fetchGroup(classId: string, assignmentNumber: number, groupNumber: number): Promise { + const assignment = await fetchAssignment(classId, assignmentNumber); if (!assignment) { return null; @@ -27,6 +23,12 @@ export async function getGroup(classId: string, assignmentNumber: number, groupN const groupRepository = getGroupRepository(); const group = await groupRepository.findByAssignmentAndGroupNumber(assignment, groupNumber); + return group; +} + +export async function getGroup(classId: string, assignmentNumber: number, groupNumber: number, full: boolean): Promise { + const group = await fetchGroup(classId, assignmentNumber, groupNumber); + if (!group) { return null; } @@ -76,15 +78,7 @@ export async function createGroup(groupData: GroupDTO, classid: string, assignme } export async function getAllGroups(classId: string, assignmentNumber: number, full: boolean): Promise { - const classRepository = getClassRepository(); - const cls = await classRepository.findById(classId); - - if (!cls) { - return []; - } - - const assignmentRepository = getAssignmentRepository(); - const assignment = await assignmentRepository.findByClassAndId(cls, assignmentNumber); + const assignment = await fetchAssignment(classId, assignmentNumber); if (!assignment) { return []; @@ -94,8 +88,6 @@ export async function getAllGroups(classId: string, assignmentNumber: number, fu const groups = await groupRepository.findAllGroupsForAssignment(assignment); if (full) { - console.log('full'); - console.log(groups); return groups.map(mapToGroupDTO); } @@ -108,22 +100,7 @@ export async function getGroupSubmissions( groupNumber: number, full: boolean ): Promise { - const classRepository = getClassRepository(); - const cls = await classRepository.findById(classId); - - if (!cls) { - return []; - } - - const assignmentRepository = getAssignmentRepository(); - const assignment = await assignmentRepository.findByClassAndId(cls, assignmentNumber); - - if (!assignment) { - return []; - } - - const groupRepository = getGroupRepository(); - const group = await groupRepository.findByAssignmentAndGroupNumber(assignment, groupNumber); + const group = await fetchGroup(classId, assignmentNumber, groupNumber); if (!group) { return []; From 7ae2f1de0c187a6388f0c55fc1d1710f28b0391c Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Mon, 31 Mar 2025 12:39:22 +0200 Subject: [PATCH 02/43] feat: DELETE voor class geimplenteerd --- backend/src/services/classes.ts | 35 +++++++++++++++----------------- backend/src/services/teachers.ts | 20 +++++++----------- 2 files changed, 23 insertions(+), 32 deletions(-) diff --git a/backend/src/services/classes.ts b/backend/src/services/classes.ts index dbf6c63d..f2cb79e8 100644 --- a/backend/src/services/classes.ts +++ b/backend/src/services/classes.ts @@ -1,5 +1,6 @@ import { getClassRepository, getStudentRepository, getTeacherInvitationRepository, getTeacherRepository } from '../data/repositories.js'; import { Class } from '../entities/classes/class.entity.js'; +import { NotFoundException } from '../exceptions/not-found-exception.js'; import { ClassDTO, mapToClassDTO } from '../interfaces/class.js'; import { mapToStudentDTO, StudentDTO } from '../interfaces/student.js'; import { mapToTeacherInvitationDTO, mapToTeacherInvitationDTOIds, TeacherInvitationDTO } from '../interfaces/teacher-invitation.js'; @@ -7,10 +8,14 @@ import { getLogger } from '../logging/initalize.js'; const logger = getLogger(); -async function fetchClass(classid: string): Promise { +async function fetchClass(classid: string): Promise { const classRepository = getClassRepository(); const cls = await classRepository.findById(classid); + if (!cls) { + throw new NotFoundException("Class not found"); + } + return cls; } @@ -18,16 +23,18 @@ export async function getAllClasses(full: boolean): Promise cls.classId!); } +export async function getClass(classId: string): Promise { + const cls = await fetchClass(classId); + + return mapToClassDTO(cls); +} + export async function createClass(classData: ClassDTO): Promise { const teacherRepository = getTeacherRepository(); const teacherUsernames = classData.teachers || []; @@ -54,12 +61,11 @@ export async function createClass(classData: ClassDTO): Promise } } -export async function getClass(classId: string): Promise { +export async function deleteClass(classId: string): Promise { const cls = await fetchClass(classId); - if (!cls) { - return null; - } + const classRepository = getClassRepository(); + await classRepository.deleteById(classId); return mapToClassDTO(cls); } @@ -67,10 +73,6 @@ export async function getClass(classId: string): Promise { export async function getClassStudents(classId: string, full: boolean): Promise { const cls = await fetchClass(classId); - if (!cls) { - return []; - } - if (full) { return cls.students.map(mapToStudentDTO); } @@ -79,12 +81,7 @@ export async function getClassStudents(classId: string, full: boolean): Promise< } export async function getClassTeacherInvitations(classId: string, full: boolean): Promise { - const classRepository = getClassRepository(); - const cls = await classRepository.findById(classId); - - if (!cls) { - return []; - } + const cls = await fetchClass(classId); const teacherInvitationRepository = getTeacherInvitationRepository(); const invitations = await teacherInvitationRepository.findAllInvitationsForClass(cls); diff --git a/backend/src/services/teachers.ts b/backend/src/services/teachers.ts index fced2b61..cf2743e9 100644 --- a/backend/src/services/teachers.ts +++ b/backend/src/services/teachers.ts @@ -76,28 +76,22 @@ export async function getClassesByTeacher(username: string, full: boolean): Prom return classes.map((cls) => cls.id); } -export async function fetchStudentsByTeacher(username: string): Promise { +export async function getStudentsByTeacher(username: string, full: boolean): Promise { const classes = (await getClassesByTeacher(username, false)) as string[]; if (!classes) { return null; } - return (await Promise.all(classes.map(async (id) => getClassStudents(id)))).flat(); -} - -export async function getStudentsByTeacher(username: string, full: boolean): Promise { - const students = await fetchStudentsByTeacher(username); - - if (!students) { - return null; - } - + // workaround + let students; if (full) { - return students; + students = (await Promise.all(classes.map(async (id) => await getClassStudents(id, full) as StudentDTO[]))).flat(); + } else { + students = (await Promise.all(classes.map(async (id) => await getClassStudents(id, full) as string[]))).flat(); } - return students.map((student) => student.username); + return students; } export async function fetchTeacherQuestions(username: string): Promise { From 6290d3dd9b3f469d598b24dd2b7e4771031d0fcf Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Tue, 1 Apr 2025 17:02:42 +0200 Subject: [PATCH 03/43] feat: get all submissions route toegevoegd --- backend/src/controllers/submissions.ts | 57 +++++++++++-------- .../data/assignments/submission-repository.ts | 8 +++ backend/src/routes/submissions.ts | 8 +-- backend/src/services/submissions.ts | 27 +++++---- 4 files changed, 59 insertions(+), 41 deletions(-) diff --git a/backend/src/controllers/submissions.ts b/backend/src/controllers/submissions.ts index 512ac22e..5fa154b1 100644 --- a/backend/src/controllers/submissions.ts +++ b/backend/src/controllers/submissions.ts @@ -1,33 +1,41 @@ import { Request, Response } from 'express'; -import { createSubmission, deleteSubmission, getSubmission } from '../services/submissions.js'; +import { createSubmission, deleteSubmission, getAllSubmissions, getSubmission } from '../services/submissions.js'; import { Language, languageMap } from '../entities/content/language.js'; import { SubmissionDTO } from '../interfaces/submission'; +import { BadRequestException } from '../exceptions/bad-request-exception.js'; +import { NotFoundException } from '../exceptions/not-found-exception.js'; +import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; -interface SubmissionParams { - hruid: string; - id: number; -} -export async function getSubmissionHandler(req: Request, res: Response): Promise { - const lohruid = req.params.hruid; + +export async function getSubmissionHandler(req: Request, res: Response): Promise { const submissionNumber = +req.params.id; - + if (isNaN(submissionNumber)) { - res.status(400).json({ error: 'Submission number is not a number' }); - return; + throw new BadRequestException('Submission number must be a number'); } - + + const lohruid = req.params.hruid; const lang = languageMap[req.query.language as string] || Language.Dutch; const version = (req.query.version || 1) as number; - const submission = await getSubmission(lohruid, lang, version, submissionNumber); + const loId = new LearningObjectIdentifier(lohruid, lang, version); - if (!submission) { - res.status(404).json({ error: 'Submission not found' }); - return; - } + const submission = await getSubmission(loId, submissionNumber); - res.json(submission); + res.json({ submission }); +} + +export async function getAllSubmissionsHandler(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 loId = new LearningObjectIdentifier(lohruid, lang, version); + + const submissions = await getAllSubmissions(loId); + + res.json({ submissions }); } export async function createSubmissionHandler(req: Request, res: Response) { @@ -40,22 +48,23 @@ export async function createSubmissionHandler(req: Request, res: Response) { return; } - res.json(submission); + res.json({ submission }); } export async function deleteSubmissionHandler(req: Request, res: Response) { - const hruid = req.params.hruid; const submissionNumber = +req.params.id; - + + const hruid = req.params.hruid; const lang = languageMap[req.query.language as string] || Language.Dutch; const version = (req.query.version || 1) as number; - const submission = await deleteSubmission(hruid, lang, version, submissionNumber); + const loId = new LearningObjectIdentifier(hruid, lang, version); + + const submission = await deleteSubmission(loId, submissionNumber); if (!submission) { - res.status(404).json({ error: 'Submission not found' }); - return; + throw new NotFoundException('Could not delete submission'); } - res.json(submission); + res.json({ submission }); } diff --git a/backend/src/data/assignments/submission-repository.ts b/backend/src/data/assignments/submission-repository.ts index 251823fa..25a66956 100644 --- a/backend/src/data/assignments/submission-repository.ts +++ b/backend/src/data/assignments/submission-repository.ts @@ -14,6 +14,14 @@ export class SubmissionRepository extends DwengoEntityRepository { }); } + public findByLearningObject(loId: LearningObjectIdentifier): Promise { + return this.find({ + learningObjectHruid: loId.hruid, + learningObjectLanguage: loId.language, + learningObjectVersion: loId.version, + }); + } + public findMostRecentSubmissionForStudent(loId: LearningObjectIdentifier, submitter: Student): Promise { return this.findOne( { diff --git a/backend/src/routes/submissions.ts b/backend/src/routes/submissions.ts index 4db93027..7c91de52 100644 --- a/backend/src/routes/submissions.ts +++ b/backend/src/routes/submissions.ts @@ -1,13 +1,9 @@ import express from 'express'; -import { createSubmissionHandler, deleteSubmissionHandler, getSubmissionHandler } from '../controllers/submissions.js'; +import { createSubmissionHandler, deleteSubmissionHandler, getAllSubmissionsHandler, getSubmissionHandler } 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('/', getAllSubmissionsHandler); router.post('/:id', createSubmissionHandler); diff --git a/backend/src/services/submissions.ts b/backend/src/services/submissions.ts index 0e1ad9ac..f9f42349 100644 --- a/backend/src/services/submissions.ts +++ b/backend/src/services/submissions.ts @@ -1,26 +1,32 @@ import { getGroupRepository, getSubmissionRepository } from '../data/repositories.js'; import { Language } from '../entities/content/language.js'; import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; +import { NotFoundException } from '../exceptions/not-found-exception.js'; import { mapToSubmission, mapToSubmissionDTO, SubmissionDTO } from '../interfaces/submission.js'; export async function getSubmission( - learningObjectHruid: string, - language: Language, - version: number, + loId: LearningObjectIdentifier, submissionNumber: number -): Promise { - const loId = new LearningObjectIdentifier(learningObjectHruid, language, version); - +): Promise { const submissionRepository = getSubmissionRepository(); const submission = await submissionRepository.findSubmissionByLearningObjectAndSubmissionNumber(loId, submissionNumber); if (!submission) { - return null; + throw new NotFoundException('Could not find submission'); } return mapToSubmissionDTO(submission); } +export async function getAllSubmissions( + loId: LearningObjectIdentifier, +): Promise { + const submissionRepository = getSubmissionRepository(); + const submissions = await submissionRepository.findByLearningObject(loId); + + return submissions.map(mapToSubmissionDTO); +} + export async function createSubmission(submissionDTO: SubmissionDTO) { const submissionRepository = getSubmissionRepository(); const submission = mapToSubmission(submissionDTO); @@ -35,16 +41,15 @@ export async function createSubmission(submissionDTO: SubmissionDTO) { return mapToSubmissionDTO(submission); } -export async function deleteSubmission(learningObjectHruid: string, language: Language, version: number, submissionNumber: number) { +export async function deleteSubmission(loId: LearningObjectIdentifier, submissionNumber: number) { const submissionRepository = getSubmissionRepository(); - const submission = getSubmission(learningObjectHruid, language, version, submissionNumber); + const submission = getSubmission(loId, submissionNumber); if (!submission) { - return null; + throw new NotFoundException('Could not delete submission because it does not exist'); } - const loId = new LearningObjectIdentifier(learningObjectHruid, language, version); await submissionRepository.deleteSubmissionByLearningObjectAndSubmissionNumber(loId, submissionNumber); return submission; From da5cb7d02d26142b563a88e820ed431cec9881db Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Tue, 1 Apr 2025 17:06:26 +0200 Subject: [PATCH 04/43] feat: delete op class geimplementeerd --- backend/src/controllers/classes.ts | 14 +++++++++++--- backend/src/routes/classes.ts | 3 ++- backend/src/services/classes.ts | 4 ++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/backend/src/controllers/classes.ts b/backend/src/controllers/classes.ts index ba11da8e..acf19476 100644 --- a/backend/src/controllers/classes.ts +++ b/backend/src/controllers/classes.ts @@ -1,6 +1,7 @@ import { Request, Response } from 'express'; -import { createClass, getAllClasses, getClass, getClassStudents, getClassTeacherInvitations } from '../services/classes.js'; +import { createClass, deleteClass, getAllClasses, getClass, getClassStudents, getClassTeacherInvitations } from '../services/classes.js'; import { ClassDTO } from '../interfaces/class.js'; +import { NotFoundException } from '../exceptions/not-found-exception.js'; export async function getAllClassesHandler(req: Request, res: Response): Promise { const full = req.query.full === 'true'; @@ -28,7 +29,7 @@ export async function createClassHandler(req: Request, res: Response): Promise { @@ -40,7 +41,14 @@ export async function getClassHandler(req: Request, res: Response): Promise { + const classId = req.params.id; + const cls = await deleteClass(classId); + + res.json({ cls }); } export async function getClassStudentsHandler(req: Request, res: Response): Promise { diff --git a/backend/src/routes/classes.ts b/backend/src/routes/classes.ts index e0972988..dc434fb0 100644 --- a/backend/src/routes/classes.ts +++ b/backend/src/routes/classes.ts @@ -15,9 +15,10 @@ router.get('/', getAllClassesHandler); router.post('/', createClassHandler); -// Information about an class with id 'id' router.get('/:id', getClassHandler); +router.delete('/:id', deleteClassHandler); + router.get('/:id/teacher-invitations', getTeacherInvitationsHandler); router.get('/:id/students', getClassStudentsHandler); diff --git a/backend/src/services/classes.ts b/backend/src/services/classes.ts index f2cb79e8..dcf6a432 100644 --- a/backend/src/services/classes.ts +++ b/backend/src/services/classes.ts @@ -64,6 +64,10 @@ export async function createClass(classData: ClassDTO): Promise export async function deleteClass(classId: string): Promise { const cls = await fetchClass(classId); + if (!cls) { + throw new NotFoundException('Could not delete class because it does not exist'); + } + const classRepository = getClassRepository(); await classRepository.deleteById(classId); From c79a295e683d6e84037a02bb08b477b0f44ddf02 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Tue, 1 Apr 2025 22:57:30 +0200 Subject: [PATCH 05/43] feat: werken aan delete voor assignment --- backend/src/controllers/assignments.ts | 14 ++++---------- backend/src/services/assignments.ts | 17 +++++++++-------- backend/src/services/classes.ts | 2 +- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index 16dbb310..b104c27c 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -2,13 +2,7 @@ import { Request, Response } from 'express'; import { createAssignment, getAllAssignments, getAssignment, getAssignmentsSubmissions } from '../services/assignments.js'; import { AssignmentDTO } from '../interfaces/assignment.js'; -// Typescript is annoy with with parameter forwarding from class.ts -interface AssignmentParams { - classid: string; - id: string; -} - -export async function getAllAssignmentsHandler(req: Request, res: Response): Promise { +export async function getAllAssignmentsHandler(req: Request, res: Response): Promise { const classid = req.params.classid; const full = req.query.full === 'true'; @@ -19,7 +13,7 @@ export async function getAllAssignmentsHandler(req: Request, r }); } -export async function createAssignmentHandler(req: Request, res: Response): Promise { +export async function createAssignmentHandler(req: Request, res: Response): Promise { const classid = req.params.classid; const assignmentData = req.body as AssignmentDTO; @@ -40,7 +34,7 @@ export async function createAssignmentHandler(req: Request, re res.status(201).json(assignment); } -export async function getAssignmentHandler(req: Request, res: Response): Promise { +export async function getAssignmentHandler(req: Request, res: Response): Promise { const id = +req.params.id; const classid = req.params.classid; @@ -59,7 +53,7 @@ export async function getAssignmentHandler(req: Request, res: res.json(assignment); } -export async function getAssignmentsSubmissionsHandler(req: Request, res: Response): Promise { +export async function getAssignmentsSubmissionsHandler(req: Request, res: Response): Promise { const classid = req.params.classid; const assignmentNumber = +req.params.id; const full = req.query.full === 'true'; diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts index 05e8099a..f468b879 100644 --- a/backend/src/services/assignments.ts +++ b/backend/src/services/assignments.ts @@ -1,30 +1,31 @@ import { getAssignmentRepository, getClassRepository, getGroupRepository, getQuestionRepository, getSubmissionRepository } from '../data/repositories.js'; import { Assignment } from '../entities/assignments/assignment.entity.js'; +import { NotFoundException } from '../exceptions/not-found-exception.js'; import { AssignmentDTO, mapToAssignment, mapToAssignmentDTO, mapToAssignmentDTOId } from '../interfaces/assignment.js'; import { mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId } from '../interfaces/question.js'; import { mapToSubmissionDTO, mapToSubmissionDTOId, SubmissionDTO, SubmissionDTOId } from '../interfaces/submission.js'; +import { fetchClass } from './classes.js'; -export async function fetchAssignment(classid: string, assignmentNumber: number): Promise { +export async function fetchAssignment(classid: string, assignmentNumber: number): Promise { const classRepository = getClassRepository(); const cls = await classRepository.findById(classid); if (!cls) { - return null; + throw new NotFoundException('Could not find assignment\'s class'); } const assignmentRepository = getAssignmentRepository(); const assignment = await assignmentRepository.findByClassAndId(cls, assignmentNumber); + if (!assignment) { + throw new NotFoundException('Could not find assignment'); + } + return assignment; } export async function getAllAssignments(classid: string, full: boolean): Promise { - const classRepository = getClassRepository(); - const cls = await classRepository.findById(classid); - - if (!cls) { - return []; - } + const cls = await fetchClass(classid); const assignmentRepository = getAssignmentRepository(); const assignments = await assignmentRepository.findAllAssignmentsInClass(cls); diff --git a/backend/src/services/classes.ts b/backend/src/services/classes.ts index dcf6a432..7735b08d 100644 --- a/backend/src/services/classes.ts +++ b/backend/src/services/classes.ts @@ -8,7 +8,7 @@ import { getLogger } from '../logging/initalize.js'; const logger = getLogger(); -async function fetchClass(classid: string): Promise { +export async function fetchClass(classid: string): Promise { const classRepository = getClassRepository(); const cls = await classRepository.findById(classid); From 2c4bc644fd673689d8d5ca00f52bc8f74b22ba32 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Sun, 6 Apr 2025 10:40:34 +0200 Subject: [PATCH 06/43] fix: assignment errors en return json --- backend/src/controllers/assignments.ts | 50 +++++++------------ backend/src/services/assignments.ts | 68 +++++++++----------------- 2 files changed, 43 insertions(+), 75 deletions(-) diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index 1520fc10..2750e3d8 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -1,6 +1,8 @@ import { Request, Response } from 'express'; import { createAssignment, getAllAssignments, getAssignment, getAssignmentsSubmissions } from '../services/assignments.js'; import { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment'; +import {requireFields} from "./error-helper"; +import {BadRequestException} from "../exceptions/bad-request-exception"; // Typescript is annoying with parameter forwarding from class.ts interface AssignmentParams { @@ -9,69 +11,55 @@ interface AssignmentParams { } export async function getAllAssignmentsHandler(req: Request, res: Response): Promise { - const classid = req.params.classid; + const classId = req.params.classid; const full = req.query.full === 'true'; + requireFields({ classId }); - const assignments = await getAllAssignments(classid, full); + const assignments = await getAllAssignments(classId, full); - res.json({ - assignments: assignments, - }); + res.json({ assignments }); } export async function createAssignmentHandler(req: Request, res: Response): Promise { const classid = req.params.classid; - const assignmentData = req.body as AssignmentDTO; + const description = req.body.description; + const language = req.body.language; + const learningPath = req.body.learningPath; + const title = req.body.title; - if (!assignmentData.description || !assignmentData.language || !assignmentData.learningPath || !assignmentData.title) { - res.status(400).json({ - error: 'Missing one or more required fields: title, description, learningPath, language', - }); - return; - } + requireFields({ description, language, learningPath, title }); + const assignmentData = req.body as AssignmentDTO; const assignment = await createAssignment(classid, assignmentData); - if (!assignment) { - res.status(500).json({ error: 'Could not create assignment ' }); - return; - } - - res.status(201).json(assignment); + res.json({ assignment }); } export async function getAssignmentHandler(req: Request, res: Response): Promise { const id = Number(req.params.id); const classid = req.params.classid; + requireFields({ id, classid }); if (isNaN(id)) { - res.status(400).json({ error: 'Assignment id must be a number' }); - return; + throw new BadRequestException("Assignment id should be a number") } const assignment = await getAssignment(classid, id); - if (!assignment) { - res.status(404).json({ error: 'Assignment not found' }); - return; - } - - res.json(assignment); + res.json({ assignment }); } export async function getAssignmentsSubmissionsHandler(req: Request, res: Response): Promise { const classid = req.params.classid; const assignmentNumber = Number(req.params.id); const full = req.query.full === 'true'; + requireFields({ assignmentNumber, classid }); if (isNaN(assignmentNumber)) { - res.status(400).json({ error: 'Assignment id must be a number' }); - return; + throw new BadRequestException("Assignment id should be a number") } const submissions = await getAssignmentsSubmissions(classid, assignmentNumber, full); - res.json({ - submissions: submissions, - }); + res.json({ submissions }); } diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts index e86b69b2..fa8e2380 100644 --- a/backend/src/services/assignments.ts +++ b/backend/src/services/assignments.ts @@ -3,15 +3,12 @@ import { mapToAssignment, mapToAssignmentDTO, mapToAssignmentDTOId } from '../in import { mapToSubmissionDTO, mapToSubmissionDTOId } from '../interfaces/submission.js'; import { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment'; import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission'; -import { getLogger } from '../logging/initalize.js'; +import {fetchClass} from "./classes"; +import {Assignment} from "../entities/assignments/assignment.entity"; +import {NotFoundException} from "../exceptions/not-found-exception"; export async function getAllAssignments(classid: string, full: boolean): Promise { - const classRepository = getClassRepository(); - const cls = await classRepository.findById(classid); - - if (!cls) { - return []; - } + const cls = await fetchClass(classid); const assignmentRepository = getAssignmentRepository(); const assignments = await assignmentRepository.findAllAssignmentsInClass(cls); @@ -23,43 +20,34 @@ export async function getAllAssignments(classid: string, full: boolean): Promise return assignments.map(mapToAssignmentDTOId); } -export async function createAssignment(classid: string, assignmentData: AssignmentDTO): Promise { - const classRepository = getClassRepository(); - const cls = await classRepository.findById(classid); - - if (!cls) { - return null; - } +export async function createAssignment(classid: string, assignmentData: AssignmentDTO): Promise { + const cls = await fetchClass(classid); const assignment = mapToAssignment(assignmentData, cls); const assignmentRepository = getAssignmentRepository(); - try { - const newAssignment = assignmentRepository.create(assignment); - await assignmentRepository.save(newAssignment); + const newAssignment = assignmentRepository.create(assignment); + await assignmentRepository.save(newAssignment, {preventOverwrite: true}); + + return mapToAssignmentDTO(newAssignment); - return mapToAssignmentDTO(newAssignment); - } catch (e) { - getLogger().error(e); - return null; - } } -export async function getAssignment(classid: string, id: number): Promise { - const classRepository = getClassRepository(); - const cls = await classRepository.findById(classid); - - if (!cls) { - return null; - } +export async function fetchAssignment(classid: string, id: number): Promise { + const cls = await fetchClass(classid); const assignmentRepository = getAssignmentRepository(); const assignment = await assignmentRepository.findByClassAndId(cls, id); - if (!assignment) { - return null; + if (!assignment){ + throw new NotFoundException('Assignment with id not found'); } + return assignment; +} + +export async function getAssignment(classid: string, id: number): Promise { + const assignment = await fetchAssignment(classid, id); return mapToAssignmentDTO(assignment); } @@ -68,23 +56,15 @@ export async function getAssignmentsSubmissions( assignmentNumber: number, full: boolean ): Promise { - const classRepository = getClassRepository(); - const cls = await classRepository.findById(classid); - - if (!cls) { - return []; - } - - const assignmentRepository = getAssignmentRepository(); - const assignment = await assignmentRepository.findByClassAndId(cls, assignmentNumber); - - if (!assignment) { - return []; - } + const assignment = await fetchAssignment(classid, assignmentNumber); const groupRepository = getGroupRepository(); const groups = await groupRepository.findAllGroupsForAssignment(assignment); + if (groups.length === 0){ + throw new NotFoundException('No groups for assignment found'); + } + const submissionRepository = getSubmissionRepository(); const submissions = (await Promise.all(groups.map(async (group) => submissionRepository.findAllSubmissionsForGroup(group)))).flat(); From 29824c549ed90da314a8f1c29b985e3f3fd0b021 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Sun, 6 Apr 2025 16:26:28 +0200 Subject: [PATCH 07/43] fix: classes error handeling en return json --- backend/src/controllers/classes.ts | 61 +++++++++++----------- backend/src/services/classes.ts | 82 ++++++++++-------------------- 2 files changed, 59 insertions(+), 84 deletions(-) diff --git a/backend/src/controllers/classes.ts b/backend/src/controllers/classes.ts index a041bf22..8e9efdcb 100644 --- a/backend/src/controllers/classes.ts +++ b/backend/src/controllers/classes.ts @@ -1,44 +1,37 @@ import { Request, Response } from 'express'; -import { createClass, getAllClasses, getClass, getClassStudents, getClassStudentsIds, getClassTeacherInvitations } from '../services/classes.js'; +import { + createClass, + getAllClasses, + getClass, + getClassStudents, + getClassTeacherInvitations, + getClassTeachers +} from '../services/classes.js'; import { ClassDTO } from '@dwengo-1/common/interfaces/class'; +import {requireFields} from "./error-helper"; export async function getAllClassesHandler(req: Request, res: Response): Promise { const full = req.query.full === 'true'; const classes = await getAllClasses(full); - res.json({ - classes: classes, - }); + res.json({ classes }); } export async function createClassHandler(req: Request, res: Response): Promise { + const displayName= req.body.displayName; + requireFields({ displayName }); + const classData = req.body as ClassDTO; - - if (!classData.displayName) { - res.status(400).json({ - error: 'Missing one or more required fields: displayName', - }); - return; - } - const cls = await createClass(classData); - if (!cls) { - res.status(500).json({ error: 'Something went wrong while creating class' }); - return; - } - - res.status(201).json(cls); + res.json({ cls }); } export async function getClassHandler(req: Request, res: Response): Promise { const classId = req.params.id; - const cls = await getClass(classId); + requireFields({ classId }); - if (!cls) { - res.status(404).json({ error: 'Class not found' }); - return; - } + const cls = await getClass(classId); res.json(cls); } @@ -46,21 +39,29 @@ export async function getClassHandler(req: Request, res: Response): Promise { const classId = req.params.id; const full = req.query.full === 'true'; + requireFields({ classId }); - const students = full ? await getClassStudents(classId) : await getClassStudentsIds(classId); + const students = await getClassStudents(classId, full); - res.json({ - students: students, - }); + res.json({ students }); +} + +export async function getClassTeachersHandler(req: Request, res: Response): Promise { + const classId = req.params.id; + const full = req.query.full === 'true'; + requireFields({ classId }); + + const teachers = await getClassTeachers(classId, full); + + res.json({ teachers }); } export async function getTeacherInvitationsHandler(req: Request, res: Response): Promise { const classId = req.params.id; const full = req.query.full === 'true'; + requireFields({ classId }); const invitations = await getClassTeacherInvitations(classId, full); - res.json({ - invitations: invitations, - }); + res.json({ invitations }); } diff --git a/backend/src/services/classes.ts b/backend/src/services/classes.ts index 754277cf..d4e4defa 100644 --- a/backend/src/services/classes.ts +++ b/backend/src/services/classes.ts @@ -8,8 +8,10 @@ import { Class } from '../entities/classes/class.entity.js'; import { ClassDTO } from '@dwengo-1/common/interfaces/class'; import { TeacherInvitationDTO } from '@dwengo-1/common/interfaces/teacher-invitation'; import { StudentDTO } from '@dwengo-1/common/interfaces/student'; - -const logger = getLogger(); +import {fetchTeacher} from "./teachers"; +import {fetchStudent} from "./students"; +import {TeacherDTO} from "@dwengo-1/common/interfaces/teacher"; +import {mapToTeacherDTO} from "../interfaces/teacher"; export async function fetchClass(classId: string): Promise { const classRepository = getClassRepository(); @@ -26,84 +28,56 @@ export async function getAllClasses(full: boolean): Promise cls.classId!); } -export async function createClass(classData: ClassDTO): Promise { - const teacherRepository = getTeacherRepository(); +export async function createClass(classData: ClassDTO): Promise { const teacherUsernames = classData.teachers || []; - const teachers = (await Promise.all(teacherUsernames.map(async (id) => teacherRepository.findByUsername(id)))).filter( - (teacher) => teacher !== null - ); + const teachers = (await Promise.all(teacherUsernames.map(async (id) => fetchTeacher(id) ))); - const studentRepository = getStudentRepository(); const studentUsernames = classData.students || []; - const students = (await Promise.all(studentUsernames.map(async (id) => studentRepository.findByUsername(id)))).filter( - (student) => student !== null - ); + const students = (await Promise.all(studentUsernames.map(async (id) => fetchStudent(id) ))); const classRepository = getClassRepository(); - try { - const newClass = classRepository.create({ - displayName: classData.displayName, - teachers: teachers, - students: students, - }); - await classRepository.save(newClass); + const newClass = classRepository.create({ + displayName: classData.displayName, + teachers: teachers, + students: students, + }); + await classRepository.save(newClass, {preventOverwrite: true}); - return mapToClassDTO(newClass); - } catch (e) { - logger.error(e); - return null; - } + return mapToClassDTO(newClass); } -export async function getClass(classId: string): Promise { - const classRepository = getClassRepository(); - const cls = await classRepository.findById(classId); - - if (!cls) { - return null; - } - +export async function getClass(classId: string): Promise { + const cls = await fetchClass(classId); return mapToClassDTO(cls); } -async function fetchClassStudents(classId: string): Promise { - const classRepository = getClassRepository(); - const cls = await classRepository.findById(classId); +export async function getClassStudents(classId: string, full: boolean): Promise { + const cls = await fetchClass(classId); - if (!cls) { - return []; + if (full){ + return cls.students.map(mapToStudentDTO); } - - return cls.students.map(mapToStudentDTO); + return cls.students.map((student) => student.username); } -export async function getClassStudents(classId: string): Promise { - return await fetchClassStudents(classId); -} +export async function getClassTeachers(classId: string, full: boolean): Promise { + const cls = await fetchClass(classId); -export async function getClassStudentsIds(classId: string): Promise { - const students: StudentDTO[] = await fetchClassStudents(classId); - return students.map((student) => student.username); + if (full){ + return cls.teachers.map(mapToTeacherDTO); + } + return cls.teachers.map((student) => student.username); } export async function getClassTeacherInvitations(classId: string, full: boolean): Promise { - const classRepository = getClassRepository(); - const cls = await classRepository.findById(classId); - - if (!cls) { - return []; - } + const cls = await fetchClass(classId); const teacherInvitationRepository = getTeacherInvitationRepository(); const invitations = await teacherInvitationRepository.findAllInvitationsForClass(cls); From db3c531038dc6a5dba67d76db943b9a195b8d683 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Sun, 6 Apr 2025 16:26:52 +0200 Subject: [PATCH 08/43] feat: get teachers van class --- backend/src/routes/classes.ts | 4 +++- frontend/src/controllers/classes.ts | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/backend/src/routes/classes.ts b/backend/src/routes/classes.ts index e0972988..6b6d4f5d 100644 --- a/backend/src/routes/classes.ts +++ b/backend/src/routes/classes.ts @@ -3,7 +3,7 @@ import { createClassHandler, getAllClassesHandler, getClassHandler, - getClassStudentsHandler, + getClassStudentsHandler, getClassTeachersHandler, getTeacherInvitationsHandler, } from '../controllers/classes.js'; import assignmentRouter from './assignments.js'; @@ -22,6 +22,8 @@ router.get('/:id/teacher-invitations', getTeacherInvitationsHandler); router.get('/:id/students', getClassStudentsHandler); +router.get('/:id/teachers', getClassTeachersHandler); + router.use('/:classid/assignments', assignmentRouter); export default router; diff --git a/frontend/src/controllers/classes.ts b/frontend/src/controllers/classes.ts index dadf6dec..9c8a667f 100644 --- a/frontend/src/controllers/classes.ts +++ b/frontend/src/controllers/classes.ts @@ -3,6 +3,7 @@ import type { ClassDTO } from "@dwengo-1/common/interfaces/class"; import type { StudentsResponse } from "./students"; import type { AssignmentsResponse } from "./assignments"; import type { TeacherInvitationDTO } from "@dwengo-1/common/interfaces/teacher-invitation"; +import type {TeachersResponse} from "@/controllers/teachers.ts"; export interface ClassesResponse { classes: ClassDTO[] | string[]; @@ -45,6 +46,10 @@ export class ClassController extends BaseController { return this.get(`/${id}/students`, { full }); } + async getTeachers(id: string, full = true): Promise { + return this.get(`/${id}/teachers`, { full }); + } + // TODO async getTeacherInvitations(id: string, full = true): Promise { return this.get(`/${id}/teacher-invitations`, { full }); From 6c3dbc99bb1afb79fa867505e52656c49bada1b6 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Sun, 6 Apr 2025 17:18:13 +0200 Subject: [PATCH 09/43] refactor: joinrequests uit classdto --- backend/src/interfaces/class.ts | 1 - backend/src/services/classes.ts | 5 +++++ backend/src/services/teachers.ts | 4 ++-- common/src/interfaces/class.ts | 1 - 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/backend/src/interfaces/class.ts b/backend/src/interfaces/class.ts index 7b07fcf2..76fa5fd5 100644 --- a/backend/src/interfaces/class.ts +++ b/backend/src/interfaces/class.ts @@ -10,7 +10,6 @@ export function mapToClassDTO(cls: Class): ClassDTO { displayName: cls.displayName, teachers: cls.teachers.map((teacher) => teacher.username), students: cls.students.map((student) => student.username), - joinRequests: [], // TODO }; } diff --git a/backend/src/services/classes.ts b/backend/src/services/classes.ts index d4e4defa..826c0f16 100644 --- a/backend/src/services/classes.ts +++ b/backend/src/services/classes.ts @@ -67,6 +67,11 @@ export async function getClassStudents(classId: string, full: boolean): Promise< return cls.students.map((student) => student.username); } +export async function getClassStudentsDTO(classId: string): Promise { + const cls = await fetchClass(classId); + return cls.students.map(mapToStudentDTO); +} + export async function getClassTeachers(classId: string, full: boolean): Promise { const cls = await fetchClass(classId); diff --git a/backend/src/services/teachers.ts b/backend/src/services/teachers.ts index 1b7643fb..6449f16f 100644 --- a/backend/src/services/teachers.ts +++ b/backend/src/services/teachers.ts @@ -22,7 +22,7 @@ import { Question } from '../entities/questions/question.entity.js'; import { ClassJoinRequestRepository } from '../data/classes/class-join-request-repository.js'; import { Student } from '../entities/users/student.entity.js'; import { NotFoundException } from '../exceptions/not-found-exception.js'; -import { getClassStudents } from './classes.js'; +import {getClassStudents, getClassStudentsDTO} from './classes.js'; import { TeacherDTO } from '@dwengo-1/common/interfaces/teacher'; import { ClassDTO } from '@dwengo-1/common/interfaces/class'; import { StudentDTO } from '@dwengo-1/common/interfaces/student'; @@ -99,7 +99,7 @@ export async function getStudentsByTeacher(username: string, full: boolean): Pro const classIds: string[] = classes.map((cls) => cls.id); - const students: StudentDTO[] = (await Promise.all(classIds.map(async (id) => getClassStudents(id)))).flat(); + const students: StudentDTO[] = (await Promise.all(classIds.map(async (id) => await getClassStudentsDTO(id)))).flat(); if (full) { return students; } diff --git a/common/src/interfaces/class.ts b/common/src/interfaces/class.ts index c35c2dfc..d71e15e6 100644 --- a/common/src/interfaces/class.ts +++ b/common/src/interfaces/class.ts @@ -3,5 +3,4 @@ export interface ClassDTO { displayName: string; teachers: string[]; students: string[]; - joinRequests: string[]; } From 7e250e26494ff8ca1a7fd414e4038b51bd4df98b Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sun, 6 Apr 2025 17:55:01 +0200 Subject: [PATCH 10/43] fix: fixed merge errors --- backend/src/data/assignments/submission-repository.ts | 4 ---- backend/src/data/questions/question-repository.ts | 6 +++--- backend/src/routes/groups.ts | 10 ---------- backend/src/routes/submissions.ts | 8 -------- 4 files changed, 3 insertions(+), 25 deletions(-) diff --git a/backend/src/data/assignments/submission-repository.ts b/backend/src/data/assignments/submission-repository.ts index d904e77f..371dd4ef 100644 --- a/backend/src/data/assignments/submission-repository.ts +++ b/backend/src/data/assignments/submission-repository.ts @@ -17,7 +17,6 @@ export class SubmissionRepository extends DwengoEntityRepository { }); } -<<<<<<< HEAD public findByLearningObject(loId: LearningObjectIdentifier): Promise { return this.find({ learningObjectHruid: loId.hruid, @@ -27,9 +26,6 @@ export class SubmissionRepository extends DwengoEntityRepository { } public findMostRecentSubmissionForStudent(loId: LearningObjectIdentifier, submitter: Student): Promise { -======= - public async findMostRecentSubmissionForStudent(loId: LearningObjectIdentifier, submitter: Student): Promise { ->>>>>>> 6c3dbc99bb1afb79fa867505e52656c49bada1b6 return this.findOne( { learningObjectHruid: loId.hruid, diff --git a/backend/src/data/questions/question-repository.ts b/backend/src/data/questions/question-repository.ts index 0055ac78..246b0484 100644 --- a/backend/src/data/questions/question-repository.ts +++ b/backend/src/data/questions/question-repository.ts @@ -56,18 +56,18 @@ export class QuestionRepository extends DwengoEntityRepository { }); } -<<<<<<< HEAD public findAllByAssignment(assignment: Assignment): Promise { return this.find({ author: assignment.groups.flatMap(group => group.members), learningObjectHruid: assignment.learningPathHruid, learningObjectLanguage: assignment.learningPathLanguage, -======= + }); + } + public async findAllByAuthor(author: Student): Promise { return this.findAll({ where: { author }, orderBy: { timestamp: 'DESC' }, // New to old ->>>>>>> 6c3dbc99bb1afb79fa867505e52656c49bada1b6 }); } } diff --git a/backend/src/routes/groups.ts b/backend/src/routes/groups.ts index 231dec2e..81532a90 100644 --- a/backend/src/routes/groups.ts +++ b/backend/src/routes/groups.ts @@ -12,15 +12,5 @@ router.post('/', createGroupHandler); router.get('/:groupid', getGroupHandler); router.get('/:groupid/submissions', getGroupSubmissionsHandler); -<<<<<<< HEAD -======= - -// The list of questions a group has made -router.get('/:id/questions', (_req, res) => { - res.json({ - questions: ['0'], - }); -}); ->>>>>>> 6c3dbc99bb1afb79fa867505e52656c49bada1b6 export default router; diff --git a/backend/src/routes/submissions.ts b/backend/src/routes/submissions.ts index fac0b834..7c91de52 100644 --- a/backend/src/routes/submissions.ts +++ b/backend/src/routes/submissions.ts @@ -3,15 +3,7 @@ import { createSubmissionHandler, deleteSubmissionHandler, getAllSubmissionsHand const router = express.Router({ mergeParams: true }); // Root endpoint used to search objects -<<<<<<< HEAD router.get('/', getAllSubmissionsHandler); -======= -router.get('/', (_req, res) => { - res.json({ - submissions: ['0', '1'], - }); -}); ->>>>>>> 6c3dbc99bb1afb79fa867505e52656c49bada1b6 router.post('/:id', createSubmissionHandler); From 8d99b4f7059a48e95d26b57debfdcfc4c9af0dc3 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sun, 6 Apr 2025 17:59:38 +0200 Subject: [PATCH 11/43] fix: missende import in routes/classes.ts gefixt --- backend/src/routes/classes.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/routes/classes.ts b/backend/src/routes/classes.ts index ace7085b..4ca8b0b9 100644 --- a/backend/src/routes/classes.ts +++ b/backend/src/routes/classes.ts @@ -1,6 +1,7 @@ import express from 'express'; import { createClassHandler, + deleteClassHandler, getAllClassesHandler, getClassHandler, getClassStudentsHandler, getClassTeachersHandler, From d2f5219b696723ac6e10efa6e751812f5ef79589 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sun, 6 Apr 2025 18:17:17 +0200 Subject: [PATCH 12/43] fix: foute benaming in res.json gefixt --- backend/src/controllers/classes.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/controllers/classes.ts b/backend/src/controllers/classes.ts index 743ed001..a0ceff17 100644 --- a/backend/src/controllers/classes.ts +++ b/backend/src/controllers/classes.ts @@ -25,7 +25,7 @@ export async function createClassHandler(req: Request, res: Response): Promise { @@ -34,14 +34,14 @@ export async function getClassHandler(req: Request, res: Response): Promise { const classId = req.params.id; const cls = await deleteClass(classId); - res.json({ cls }); + res.json({ class: cls }); } export async function getClassStudentsHandler(req: Request, res: Response): Promise { From c52bcde3ae10768880205f733a02a2e622e10a3b Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sun, 6 Apr 2025 18:21:37 +0200 Subject: [PATCH 13/43] feat: requirefields in backend controllers (class, assignment, submission, group) --- backend/src/controllers/assignments.ts | 2 +- backend/src/controllers/groups.ts | 66 ++++++++++---------------- backend/src/controllers/submissions.ts | 33 +++++-------- 3 files changed, 38 insertions(+), 63 deletions(-) diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index 7c94e2a7..42c822f0 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -22,8 +22,8 @@ export async function createAssignmentHandler(req: Request, res: Response): Prom const title = req.body.title; requireFields({ description, language, learningPath, title }); - const assignmentData = req.body as AssignmentDTO; + const assignmentData = req.body as AssignmentDTO; const assignment = await createAssignment(classid, assignmentData); res.json({ assignment }); diff --git a/backend/src/controllers/groups.ts b/backend/src/controllers/groups.ts index 989066a6..3faf4ff0 100644 --- a/backend/src/controllers/groups.ts +++ b/backend/src/controllers/groups.ts @@ -1,29 +1,22 @@ import { Request, Response } from 'express'; import { createGroup, getAllGroups, getGroup, getGroupSubmissions } from '../services/groups.js'; import { GroupDTO } from '@dwengo-1/common/interfaces/group'; +import { requireFields } from './error-helper.js'; +import { BadRequestException } from '../exceptions/bad-request-exception.js'; -// Typescript is annoywith with parameter forwarding from class.ts -interface GroupParams { - classid: string; - assignmentid: string; - groupid?: string; -} - -export async function getGroupHandler(req: Request, res: Response): Promise { +export async function getGroupHandler(req: Request, res: Response): Promise { const classId = req.params.classid; + const assignmentId = parseInt(req.params.assignmentid); + const groupId = parseInt(req.params.groupid); const full = req.query.full === 'true'; - const assignmentId = Number(req.params.assignmentid); + requireFields({ classId, assignmentId, groupId }); if (isNaN(assignmentId)) { - res.status(400).json({ error: 'Assignment id must be a number' }); - return; + throw new BadRequestException('Assignment id must be a number'); } - const groupId = Number(req.params.groupid!); // Can't be undefined - if (isNaN(groupId)) { - res.status(400).json({ error: 'Group id must be a number' }); - return; + throw new BadRequestException('Group id must be a number'); } const group = await getGroup(classId, assignmentId, groupId, full); @@ -33,68 +26,57 @@ export async function getGroupHandler(req: Request, res: Response): return; } - res.json(group); + res.json({ group }); } export async function getAllGroupsHandler(req: Request, res: Response): Promise { const classId = req.params.classid; - const full = req.query.full === 'true'; - const assignmentId = Number(req.params.assignmentid); + const full = req.query.full === 'true'; + requireFields({ classId, assignmentId }); if (isNaN(assignmentId)) { - res.status(400).json({ error: 'Assignment id must be a number' }); - return; + throw new BadRequestException('Assignment id must be a number'); } const groups = await getAllGroups(classId, assignmentId, full); - res.json({ - groups: groups, - }); + res.json({ groups }); } export async function createGroupHandler(req: Request, res: Response): Promise { const classid = req.params.classid; const assignmentId = Number(req.params.assignmentid); + requireFields({ classid, assignmentId }); + if (isNaN(assignmentId)) { - res.status(400).json({ error: 'Assignment id must be a number' }); - return; + throw new BadRequestException('Assignment id must be a number'); } const groupData = req.body as GroupDTO; const group = await createGroup(groupData, classid, assignmentId); - if (!group) { - res.status(500).json({ error: 'Something went wrong while creating group' }); - return; - } - - res.status(201).json(group); + res.status(201).json({ group }); } export async function getGroupSubmissionsHandler(req: Request, res: Response): Promise { const classId = req.params.classid; - const full = req.query.full === 'true'; - const assignmentId = Number(req.params.assignmentid); + const groupId = Number(req.params.groupid); + const full = req.query.full === 'true'; + + requireFields({ classId, assignmentId, groupId }); if (isNaN(assignmentId)) { - res.status(400).json({ error: 'Assignment id must be a number' }); - return; + throw new BadRequestException('Assignment id must be a number'); } - const groupId = Number(req.params.groupid); // Can't be undefined - if (isNaN(groupId)) { - res.status(400).json({ error: 'Group id must be a number' }); - return; + throw new BadRequestException('Group id must be a number'); } const submissions = await getGroupSubmissions(classId, assignmentId, groupId, full); - res.json({ - submissions: submissions, - }); + res.json({ submissions }); } diff --git a/backend/src/controllers/submissions.ts b/backend/src/controllers/submissions.ts index 499cbacc..a49992bb 100644 --- a/backend/src/controllers/submissions.ts +++ b/backend/src/controllers/submissions.ts @@ -5,22 +5,22 @@ import { NotFoundException } from '../exceptions/not-found-exception.js'; import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; import { Language, languageMap } from '@dwengo-1/common/util/language'; import { SubmissionDTO } from '@dwengo-1/common/interfaces/submission'; +import { requireFields } from './error-helper.js'; export async function getSubmissionHandler(req: Request, res: Response): Promise { - const submissionNumber = +req.params.id; + const lohruid = req.params.hruid; + const lang = languageMap[req.query.language as string] || Language.Dutch; + const version = (req.query.version || 1) as number; + const submissionNumber = Number(req.params.id); + requireFields({ lohruid, submissionNumber }); if (isNaN(submissionNumber)) { throw new BadRequestException('Submission number must be a number'); } - const lohruid = req.params.hruid; - const lang = languageMap[req.query.language as string] || Language.Dutch; - const version = (req.query.version || 1) as number; - const loId = new LearningObjectIdentifier(lohruid, lang, version); - const submission = await getSubmission(loId, submissionNumber); res.json({ submission }); @@ -30,9 +30,9 @@ export async function getAllSubmissionsHandler(req: Request, res: Response): Pro const lohruid = req.params.hruid; const lang = languageMap[req.query.language as string] || Language.Dutch; const version = (req.query.version || 1) as number; + requireFields({ lohruid }); const loId = new LearningObjectIdentifier(lohruid, lang, version); - const submissions = await getAllSubmissions(loId); res.json({ submissions }); @@ -40,31 +40,24 @@ export async function getAllSubmissionsHandler(req: Request, res: Response): Pro export async function createSubmissionHandler(req: Request, res: Response): Promise { const submissionDTO = req.body as SubmissionDTO; - const submission = await createSubmission(submissionDTO); - if (!submission) { - res.status(400).json({ error: 'Failed to create submission' }); - return; - } - res.json({ submission }); } export async function deleteSubmissionHandler(req: Request, res: Response): Promise { const hruid = req.params.hruid; - const submissionNumber = Number(req.params.id); - const lang = languageMap[req.query.language as string] || Language.Dutch; const version = (req.query.version || 1) as number; + const submissionNumber = Number(req.params.id); + requireFields({ hruid, submissionNumber }); + + if (isNaN(submissionNumber)) { + throw new BadRequestException('Submission number must be a number'); + } const loId = new LearningObjectIdentifier(hruid, lang, version); - const submission = await deleteSubmission(loId, submissionNumber); - if (!submission) { - throw new NotFoundException('Could not delete submission'); - } - res.json({ submission }); } From e562fad385cc745eb7d29718a716d8482fbfbbc1 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sun, 6 Apr 2025 18:32:54 +0200 Subject: [PATCH 14/43] fix: return types enz gefixt services/assignments.ts --- backend/src/services/assignments.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts index 49cc4819..05ebb1d1 100644 --- a/backend/src/services/assignments.ts +++ b/backend/src/services/assignments.ts @@ -44,13 +44,12 @@ export async function createAssignment(classid: string, assignmentData: Assignme const cls = await fetchClass(classid); const assignment = mapToAssignment(assignmentData, cls); + const assignmentRepository = getAssignmentRepository(); - const newAssignment = assignmentRepository.create(assignment); await assignmentRepository.save(newAssignment, {preventOverwrite: true}); return mapToAssignmentDTO(newAssignment); - } export async function getAssignment(classid: string, id: number): Promise { @@ -68,12 +67,10 @@ export async function getAssignmentsSubmissions( const groupRepository = getGroupRepository(); const groups = await groupRepository.findAllGroupsForAssignment(assignment); - if (groups.length === 0){ - throw new NotFoundException('No groups for assignment found'); - } - const submissionRepository = getSubmissionRepository(); - const submissions = (await Promise.all(groups.map(async (group) => submissionRepository.findAllSubmissionsForGroup(group)))).flat(); + const submissions = (await Promise.all( + groups.map(async (group) => submissionRepository.findAllSubmissionsForGroup(group)) + )).flat(); if (full) { return submissions.map(mapToSubmissionDTO); @@ -96,5 +93,5 @@ export async function getAssignmentsQuestions( return questions.map(mapToQuestionDTO); } - return questions.map(mapToQuestionDTO); // mapToQuestionId should be updated + return questions.map(mapToQuestionDTO); } \ No newline at end of file From dde672befd15a2e0517be056728f0ceb637ee63c Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sun, 6 Apr 2025 19:17:04 +0200 Subject: [PATCH 15/43] feat: submission full backend stack opgekuist --- backend/src/controllers/groups.ts | 5 +- backend/src/interfaces/group.ts | 5 +- backend/src/interfaces/submission.ts | 28 +++++----- backend/src/services/classes.ts | 8 ++- backend/src/services/groups.ts | 78 ++++++++++------------------ backend/src/services/submissions.ts | 43 ++++++++------- common/src/interfaces/group.ts | 2 + 7 files changed, 74 insertions(+), 95 deletions(-) diff --git a/backend/src/controllers/groups.ts b/backend/src/controllers/groups.ts index 3faf4ff0..446a6a7e 100644 --- a/backend/src/controllers/groups.ts +++ b/backend/src/controllers/groups.ts @@ -8,7 +8,6 @@ export async function getGroupHandler(req: Request, res: Response): Promise member.username), diff --git a/backend/src/interfaces/submission.ts b/backend/src/interfaces/submission.ts index b4ed4a2b..91882c35 100644 --- a/backend/src/interfaces/submission.ts +++ b/backend/src/interfaces/submission.ts @@ -1,6 +1,9 @@ +import { getSubmissionRepository } from '../data/repositories.js'; +import { Group } from '../entities/assignments/group.entity.js'; import { Submission } from '../entities/assignments/submission.entity.js'; +import { Student } from '../entities/users/student.entity.js'; import { mapToGroupDTO } from './group.js'; -import { mapToStudent, mapToStudentDTO } from './student.js'; +import { mapToStudentDTO } from './student.js'; import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission'; export function mapToSubmissionDTO(submission: Submission): SubmissionDTO { @@ -29,17 +32,14 @@ export function mapToSubmissionDTOId(submission: Submission): SubmissionDTOId { }; } -export function mapToSubmission(submissionDTO: SubmissionDTO): Submission { - const submission = new Submission(); - submission.learningObjectHruid = submissionDTO.learningObjectIdentifier.hruid; - submission.learningObjectLanguage = submissionDTO.learningObjectIdentifier.language; - submission.learningObjectVersion = submissionDTO.learningObjectIdentifier.version!; - // Submission.submissionNumber = submissionDTO.submissionNumber; - submission.submitter = mapToStudent(submissionDTO.submitter); - // Submission.submissionTime = submissionDTO.time; - // Submission.onBehalfOf = submissionDTO.group!; - // TODO fix group - submission.content = submissionDTO.content; - - return submission; +export function mapToSubmission(submissionDTO: SubmissionDTO, submitter: Student, onBehalfOf: Group | undefined): Submission { + return getSubmissionRepository().create({ + learningObjectHruid: submissionDTO.learningObjectIdentifier.hruid, + learningObjectLanguage: submissionDTO.learningObjectIdentifier.language, + learningObjectVersion: submissionDTO.learningObjectIdentifier.version || 1, + submitter: submitter, + submissionTime: new Date(), + content: submissionDTO.content, + onBehalfOf: onBehalfOf, + }); } diff --git a/backend/src/services/classes.ts b/backend/src/services/classes.ts index 92fd158d..4c8d4caf 100644 --- a/backend/src/services/classes.ts +++ b/backend/src/services/classes.ts @@ -26,7 +26,7 @@ export async function fetchClass(classid: string): Promise { export async function getAllClasses(full: boolean): Promise { const classRepository = getClassRepository(); - const classes = await classRepository.find({}, { populate: ['students', 'teachers'] }); + const classes = await classRepository.findAll({ populate: ['students', 'teachers'] }); if (full) { return classes.map(mapToClassDTO); @@ -34,13 +34,12 @@ export async function getAllClasses(full: boolean): Promise cls.classId!); } -export async function getClass(classId: string): Promise { +export async function getClass(classId: string): Promise { const cls = await fetchClass(classId); - return mapToClassDTO(cls); } -export async function createClass(classData: ClassDTO): Promise { +export async function createClass(classData: ClassDTO): Promise { const teacherUsernames = classData.teachers || []; const teachers = (await Promise.all(teacherUsernames.map(async (id) => fetchTeacher(id) ))); @@ -48,7 +47,6 @@ export async function createClass(classData: ClassDTO): Promise const students = (await Promise.all(studentUsernames.map(async (id) => fetchStudent(id) ))); const classRepository = getClassRepository(); - const newClass = classRepository.create({ displayName: classData.displayName, teachers: teachers, diff --git a/backend/src/services/groups.ts b/backend/src/services/groups.ts index 9dfb1596..94f96d54 100644 --- a/backend/src/services/groups.ts +++ b/backend/src/services/groups.ts @@ -14,80 +14,58 @@ import { GroupDTO } from '@dwengo-1/common/interfaces/group'; import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission'; import { getLogger } from '../logging/initalize.js'; import { fetchAssignment } from './assignments.js'; +import { NotFoundException } from '../exceptions/not-found-exception.js'; +import { fetchClass } from './classes.js'; -async function fetchGroup(classId: string, assignmentNumber: number, groupNumber: number): Promise { +export async function fetchGroup(classId: string, assignmentNumber: number, groupNumber: number): Promise { const assignment = await fetchAssignment(classId, assignmentNumber); - if (!assignment) { - return null; - } - const groupRepository = getGroupRepository(); const group = await groupRepository.findByAssignmentAndGroupNumber(assignment, groupNumber); + if (!group) { + throw new NotFoundException('Could not find group'); + } + return group; } -export async function getGroup(classId: string, assignmentNumber: number, groupNumber: number, full: boolean): Promise { +export async function getGroup(classId: string, assignmentNumber: number, groupNumber: number): Promise { const group = await fetchGroup(classId, assignmentNumber, groupNumber); - - if (!group) { - return null; - } - - if (full) { - return mapToGroupDTO(group); - } - - return mapToGroupDTOId(group); + return mapToGroupDTO(group); } -export async function createGroup(groupData: GroupDTO, classid: string, assignmentNumber: number): Promise { +export async function getExistingGroupFromGroupDTO(groupData: GroupDTO) { + const classId = typeof(groupData.class) === 'string' ? groupData.class : groupData.class.id; + const assignmentNumber = typeof(groupData.assignment) === 'number' ? groupData.assignment : groupData.assignment.id; + const groupNumber = groupData.groupNumber; + + return await fetchGroup(classId, assignmentNumber, groupNumber); +} + +export async function createGroup(groupData: GroupDTO, classid: string, assignmentNumber: number): Promise { const studentRepository = getStudentRepository(); - const memberUsernames = (groupData.members as string[]) || []; // TODO check if groupdata.members is a list + const memberUsernames = (groupData.members as string[]) || []; const members = (await Promise.all([...memberUsernames].map(async (id) => studentRepository.findByUsername(id)))).filter( (student) => student !== null ); - getLogger().debug(members); - - const classRepository = getClassRepository(); - const cls = await classRepository.findById(classid); - - if (!cls) { - return null; - } - - const assignmentRepository = getAssignmentRepository(); - const assignment = await assignmentRepository.findByClassAndId(cls, assignmentNumber); - - if (!assignment) { - return null; - } + const assignment = await fetchAssignment(classid, assignmentNumber); const groupRepository = getGroupRepository(); - try { - const newGroup = groupRepository.create({ - assignment: assignment, - members: members, - }); - await groupRepository.save(newGroup); + const newGroup = groupRepository.create({ + assignment: assignment, + members: members, + }); + await groupRepository.save(newGroup); - return newGroup; - } catch (e) { - getLogger().error(e); - return null; - } + return mapToGroupDTO(newGroup); } export async function getAllGroups(classId: string, assignmentNumber: number, full: boolean): Promise { const assignment = await fetchAssignment(classId, assignmentNumber); - if (!assignment) { - return []; - } - const groupRepository = getGroupRepository(); const groups = await groupRepository.findAllGroupsForAssignment(assignment); @@ -106,10 +84,6 @@ export async function getGroupSubmissions( ): Promise { const group = await fetchGroup(classId, assignmentNumber, groupNumber); - if (!group) { - return []; - } - const submissionRepository = getSubmissionRepository(); const submissions = await submissionRepository.findAllSubmissionsForGroup(group); diff --git a/backend/src/services/submissions.ts b/backend/src/services/submissions.ts index e80b53ee..f3ee0f10 100644 --- a/backend/src/services/submissions.ts +++ b/backend/src/services/submissions.ts @@ -4,11 +4,14 @@ import { NotFoundException } from '../exceptions/not-found-exception.js'; import { mapToSubmission, mapToSubmissionDTO } from '../interfaces/submission.js'; import { SubmissionDTO } from '@dwengo-1/common/interfaces/submission'; import { Language } from '@dwengo-1/common/util/language'; +import { fetchStudent } from './students.js'; +import { fetchGroup, getExistingGroupFromGroupDTO } from './groups.js'; +import { Submission } from '../entities/assignments/submission.entity.js'; -export async function getSubmission( +export async function fetchSubmission( loId: LearningObjectIdentifier, - submissionNumber: number -): Promise { + submissionNumber: number, +): Promise { const submissionRepository = getSubmissionRepository(); const submission = await submissionRepository.findSubmissionByLearningObjectAndSubmissionNumber(loId, submissionNumber); @@ -16,6 +19,14 @@ export async function getSubmission( throw new NotFoundException('Could not find submission'); } + return submission; +} + +export async function getSubmission( + loId: LearningObjectIdentifier, + submissionNumber: number +): Promise { + const submission = await fetchSubmission(loId, submissionNumber); return mapToSubmissionDTO(submission); } @@ -28,30 +39,22 @@ export async function getAllSubmissions( return submissions.map(mapToSubmissionDTO); } -export async function createSubmission(submissionDTO: SubmissionDTO): Promise { - const submissionRepository = getSubmissionRepository(); - const submission = mapToSubmission(submissionDTO); +export async function createSubmission(submissionDTO: SubmissionDTO): Promise { + const submitter = await fetchStudent(submissionDTO.submitter.username); + const group = submissionDTO.group ? await getExistingGroupFromGroupDTO(submissionDTO.group) : undefined; - try { - const newSubmission = submissionRepository.create(submission); - await submissionRepository.save(newSubmission); - } catch (_) { - return null; - } + const submissionRepository = getSubmissionRepository(); + const submission = mapToSubmission(submissionDTO, submitter, group); + await submissionRepository.save(submission); return mapToSubmissionDTO(submission); } export async function deleteSubmission(loId: LearningObjectIdentifier, submissionNumber: number): Promise { + const submission = await fetchSubmission(loId, submissionNumber); + const submissionRepository = getSubmissionRepository(); - - const submission = await getSubmission(loId, submissionNumber); - - if (!submission) { - throw new NotFoundException('Could not delete submission because it does not exist'); - } - await submissionRepository.deleteSubmissionByLearningObjectAndSubmissionNumber(loId, submissionNumber); - return submission; + return mapToSubmissionDTO(submission); } diff --git a/common/src/interfaces/group.ts b/common/src/interfaces/group.ts index ca95770a..742f2c75 100644 --- a/common/src/interfaces/group.ts +++ b/common/src/interfaces/group.ts @@ -1,7 +1,9 @@ import { AssignmentDTO } from './assignment'; +import { ClassDTO } from './class'; import { StudentDTO } from './student'; export interface GroupDTO { + class: string | ClassDTO; assignment: number | AssignmentDTO; groupNumber: number; members: string[] | StudentDTO[]; From 441b77b8cdf84b51933b38a3e610135294bc07db Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sun, 6 Apr 2025 19:29:08 +0200 Subject: [PATCH 16/43] fix: requireFields toegevoegd op plaatsen --- backend/src/controllers/assignments.ts | 1 - backend/src/controllers/groups.ts | 1 + backend/src/controllers/submissions.ts | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index 42c822f0..a284360d 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -7,7 +7,6 @@ import {BadRequestException} from "../exceptions/bad-request-exception"; export async function getAllAssignmentsHandler(req: Request, res: Response): Promise { const classId = req.params.classid; const full = req.query.full === 'true'; - requireFields({ classId }); const assignments = await getAllAssignments(classId, full); diff --git a/backend/src/controllers/groups.ts b/backend/src/controllers/groups.ts index 446a6a7e..b5f32d37 100644 --- a/backend/src/controllers/groups.ts +++ b/backend/src/controllers/groups.ts @@ -3,6 +3,7 @@ import { createGroup, getAllGroups, getGroup, getGroupSubmissions } from '../ser import { GroupDTO } from '@dwengo-1/common/interfaces/group'; import { requireFields } from './error-helper.js'; import { BadRequestException } from '../exceptions/bad-request-exception.js'; +import { getLogger } from '../logging/initalize.js'; export async function getGroupHandler(req: Request, res: Response): Promise { const classId = req.params.classid; diff --git a/backend/src/controllers/submissions.ts b/backend/src/controllers/submissions.ts index a49992bb..3e10b0f2 100644 --- a/backend/src/controllers/submissions.ts +++ b/backend/src/controllers/submissions.ts @@ -38,6 +38,7 @@ export async function getAllSubmissionsHandler(req: Request, res: Response): Pro res.json({ submissions }); } +// TODO: gerald moet nog dingen toevoegen aan de databank voor dat dit gefinaliseerd kan worden export async function createSubmissionHandler(req: Request, res: Response): Promise { const submissionDTO = req.body as SubmissionDTO; const submission = await createSubmission(submissionDTO); From d65bb1f4a6034698a81878b9d84e28f94c0f2f68 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sun, 6 Apr 2025 19:52:00 +0200 Subject: [PATCH 17/43] feat: post en delete toegevoegd voor class students en teachers --- backend/src/controllers/classes.ts | 44 ++++++++++++++++++++++++++++++ backend/src/routes/classes.ts | 12 ++++++++ backend/src/services/classes.ts | 38 ++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) diff --git a/backend/src/controllers/classes.ts b/backend/src/controllers/classes.ts index a0ceff17..4e0095af 100644 --- a/backend/src/controllers/classes.ts +++ b/backend/src/controllers/classes.ts @@ -1,7 +1,11 @@ import { Request, Response } from 'express'; import { + addClassStudent, + addClassTeacher, createClass, deleteClass, + deleteClassStudent, + deleteClassTeacher, getAllClasses, getClass, getClassStudents, @@ -73,3 +77,43 @@ export async function getTeacherInvitationsHandler(req: Request, res: Response): res.json({ invitations }); } + +export async function deleteClassStudentHandler(req: Request, res: Response): Promise { + const classId = req.params.id; + const username = req.params.username; + requireFields({ classId, username }); + + const cls = await deleteClassStudent(classId, username); + + res.json({ class: cls }); +} + +export async function deleteClassTeacherHandler(req: Request, res: Response): Promise { + const classId = req.params.id; + const username = req.params.username; + requireFields({ classId, username }); + + const cls = await deleteClassTeacher(classId, username); + + res.json({ class: cls }); +} + +export async function addClassStudentHandler(req: Request, res: Response): Promise { + const classId = req.params.id; + const username = req.body.username; + requireFields({ classId, username }); + + const cls = await addClassStudent(classId, username); + + res.json({ class: cls }); +} + +export async function addClassTeacherHandler(req: Request, res: Response): Promise { + const classId = req.params.id; + const username = req.body.username; + requireFields({ classId, username }); + + const cls = await addClassTeacher(classId, username); + + res.json({ class: cls }); +} diff --git a/backend/src/routes/classes.ts b/backend/src/routes/classes.ts index 4ca8b0b9..aaf95567 100644 --- a/backend/src/routes/classes.ts +++ b/backend/src/routes/classes.ts @@ -1,7 +1,11 @@ import express from 'express'; import { + addClassStudentHandler, + addClassTeacherHandler, createClassHandler, deleteClassHandler, + deleteClassStudentHandler, + deleteClassTeacherHandler, getAllClassesHandler, getClassHandler, getClassStudentsHandler, getClassTeachersHandler, @@ -24,8 +28,16 @@ router.get('/:id/teacher-invitations', getTeacherInvitationsHandler); router.get('/:id/students', getClassStudentsHandler); +router.post('/:id/students', addClassStudentHandler); + +router.delete('/:id/students/:username', deleteClassStudentHandler); + router.get('/:id/teachers', getClassTeachersHandler); +router.post('/:id/teachers', addClassTeacherHandler); + +router.delete('/:id/teachers/:username', deleteClassTeacherHandler); + router.use('/:classid/assignments', assignmentRouter); export default router; diff --git a/backend/src/services/classes.ts b/backend/src/services/classes.ts index 4c8d4caf..9b74397b 100644 --- a/backend/src/services/classes.ts +++ b/backend/src/services/classes.ts @@ -101,3 +101,41 @@ export async function getClassTeacherInvitations(classId: string, full: boolean) return invitations.map(mapToTeacherInvitationDTOIds); } + +export async function deleteClassStudent(classId: string, username: string): Promise { + const cls = await fetchClass(classId); + + const classRepository = getClassRepository(); + classRepository.assign(cls, { students: cls.students.filter((student) => student.username !== username) }); + + return mapToClassDTO(cls); +} + +export async function deleteClassTeacher(classId: string, username: string): Promise { + const cls = await fetchClass(classId); + + const classRepository = getClassRepository(); + classRepository.assign(cls, { teachers: cls.teachers.filter((teacher) => teacher.username !== username) }); + + return mapToClassDTO(cls); +} + +export async function addClassStudent(classId: string, username: string): Promise { + const cls = await fetchClass(classId); + const newStudent = await fetchStudent(username); + + const classRepository = getClassRepository(); + classRepository.assign(cls, { students: [...cls.students, newStudent] }); + + return mapToClassDTO(cls); +} + +export async function addClassTeacher(classId: string, username: string): Promise { + const cls = await fetchClass(classId); + const newTeacher = await fetchTeacher(username); + + const classRepository = getClassRepository(); + classRepository.assign(cls, { teachers: [...cls.teachers, newTeacher] }); + + return mapToClassDTO(cls); +} From 2ec5e02061f30e387c23776278795afa392da9e7 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sun, 6 Apr 2025 19:57:16 +0200 Subject: [PATCH 18/43] feat: DELETE op assignment geimplementeerd --- backend/src/controllers/assignments.ts | 14 +++++++++++++- backend/src/routes/assignments.ts | 5 +++-- backend/src/services/assignments.ts | 10 ++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index a284360d..86e3217e 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -1,5 +1,5 @@ import { Request, Response } from 'express'; -import { createAssignment, getAllAssignments, getAssignment, getAssignmentsSubmissions } from '../services/assignments.js'; +import { createAssignment, deleteAssignment, getAllAssignments, getAssignment, getAssignmentsSubmissions } from '../services/assignments.js'; import { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment'; import {requireFields} from "./error-helper"; import {BadRequestException} from "../exceptions/bad-request-exception"; @@ -42,6 +42,18 @@ export async function getAssignmentHandler(req: Request, res: Response): Promise res.json({ assignment }); } +export async function deleteAssignmentHandler(req: Request, res: Response): Promise { + const id = Number(req.params.id); + const classid = req.params.classid; + requireFields({ id, classid }); + + if (isNaN(id)) { + throw new BadRequestException("Assignment id should be a number"); + } + + const assignment = await deleteAssignment(classid, id); +} + export async function getAssignmentsSubmissionsHandler(req: Request, res: Response): Promise { const classid = req.params.classid; const assignmentNumber = Number(req.params.id); diff --git a/backend/src/routes/assignments.ts b/backend/src/routes/assignments.ts index 3652dcc6..054de407 100644 --- a/backend/src/routes/assignments.ts +++ b/backend/src/routes/assignments.ts @@ -1,6 +1,7 @@ import express from 'express'; import { createAssignmentHandler, + deleteAssignmentHandler, getAllAssignmentsHandler, getAssignmentHandler, getAssignmentsSubmissionsHandler, @@ -9,14 +10,14 @@ import groupRouter from './groups.js'; const router = express.Router({ mergeParams: true }); -// Root endpoint used to search objects router.get('/', getAllAssignmentsHandler); router.post('/', createAssignmentHandler); -// Information about an assignment with id 'id' router.get('/:id', getAssignmentHandler); +router.delete('/:id', deleteAssignmentHandler); + router.get('/:id/submissions', getAssignmentsSubmissionsHandler); router.get('/:id/questions', (_req, res) => { diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts index 05ebb1d1..a2892bed 100644 --- a/backend/src/services/assignments.ts +++ b/backend/src/services/assignments.ts @@ -57,6 +57,16 @@ export async function getAssignment(classid: string, id: number): Promise { + const assignment = await fetchAssignment(classid, id); + const cls = await fetchClass(classid); + + const assignmentRepository = getAssignmentRepository(); + await assignmentRepository.deleteByClassAndId(cls, id); + + return mapToAssignmentDTO(assignment); +} + export async function getAssignmentsSubmissions( classid: string, assignmentNumber: number, From 709d5f019ad041215337cd91dbe126b571b38b58 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sun, 6 Apr 2025 20:03:46 +0200 Subject: [PATCH 19/43] feat: DELETE voor group geimplementeerd --- backend/src/controllers/groups.ts | 28 +++++++++++++++++++--------- backend/src/routes/groups.ts | 5 +++-- backend/src/services/groups.ts | 10 ++++++++++ 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/backend/src/controllers/groups.ts b/backend/src/controllers/groups.ts index b5f32d37..781cd4fd 100644 --- a/backend/src/controllers/groups.ts +++ b/backend/src/controllers/groups.ts @@ -1,14 +1,11 @@ import { Request, Response } from 'express'; -import { createGroup, getAllGroups, getGroup, getGroupSubmissions } from '../services/groups.js'; +import { createGroup, deleteGroup, getAllGroups, getGroup, getGroupSubmissions } from '../services/groups.js'; import { GroupDTO } from '@dwengo-1/common/interfaces/group'; import { requireFields } from './error-helper.js'; import { BadRequestException } from '../exceptions/bad-request-exception.js'; import { getLogger } from '../logging/initalize.js'; -export async function getGroupHandler(req: Request, res: Response): Promise { - const classId = req.params.classid; - const assignmentId = parseInt(req.params.assignmentid); - const groupId = parseInt(req.params.groupid); +function checkGroupFields(classId: any, assignmentId: any, groupId: any) { requireFields({ classId, assignmentId, groupId }); if (isNaN(assignmentId)) { @@ -18,13 +15,26 @@ export async function getGroupHandler(req: Request, res: Response): Promise { + const classId = req.params.classid; + const assignmentId = parseInt(req.params.assignmentid); + const groupId = parseInt(req.params.groupid); + checkGroupFields(classId, assignmentId, groupId); const group = await getGroup(classId, assignmentId, groupId); - if (!group) { - res.status(404).json({ error: 'Group not found' }); - return; - } + res.json({ group }); +} + +export async function deleteGroupHandler(req: Request, res: Response): Promise { + const classId = req.params.classid; + const assignmentId = parseInt(req.params.assignmentid); + const groupId = parseInt(req.params.groupid); + checkGroupFields(classId, assignmentId, groupId); + + const group = await deleteGroup(classId, assignmentId, groupId); res.json({ group }); } diff --git a/backend/src/routes/groups.ts b/backend/src/routes/groups.ts index 81532a90..4a7d6b10 100644 --- a/backend/src/routes/groups.ts +++ b/backend/src/routes/groups.ts @@ -1,5 +1,5 @@ import express from 'express'; -import { createGroupHandler, getAllGroupsHandler, getGroupHandler, getGroupSubmissionsHandler } from '../controllers/groups.js'; +import { createGroupHandler, deleteGroupHandler, getAllGroupsHandler, getGroupHandler, getGroupSubmissionsHandler } from '../controllers/groups.js'; const router = express.Router({ mergeParams: true }); @@ -8,9 +8,10 @@ router.get('/', getAllGroupsHandler); router.post('/', createGroupHandler); -// Information about a group (members, ... [TODO DOC]) router.get('/:groupid', getGroupHandler); +router.delete('/:groupid', deleteGroupHandler); + router.get('/:groupid/submissions', getGroupSubmissionsHandler); export default router; diff --git a/backend/src/services/groups.ts b/backend/src/services/groups.ts index 94f96d54..68784177 100644 --- a/backend/src/services/groups.ts +++ b/backend/src/services/groups.ts @@ -35,6 +35,16 @@ export async function getGroup(classId: string, assignmentNumber: number, groupN return mapToGroupDTO(group); } +export async function deleteGroup(classId: string, assignmentNumber: number, groupNumber: number): Promise { + const group = await fetchGroup(classId, assignmentNumber, groupNumber); + const assignment = await fetchAssignment(classId, assignmentNumber); + + const groupRepository = getGroupRepository(); + await groupRepository.deleteByAssignmentAndGroupNumber(assignment, groupNumber); + + return mapToGroupDTO(group); +} + export async function getExistingGroupFromGroupDTO(groupData: GroupDTO) { const classId = typeof(groupData.class) === 'string' ? groupData.class : groupData.class.id; const assignmentNumber = typeof(groupData.assignment) === 'number' ? groupData.assignment : groupData.assignment.id; From 800d52257ca33756e79c747ec5cfca65c5b6dd41 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sun, 6 Apr 2025 22:02:27 +0200 Subject: [PATCH 20/43] feat: PUT request op assignment geimplementeerd --- backend/src/controllers/assignments.ts | 1 + backend/src/controllers/classes.ts | 13 ++++++++++++- backend/src/interfaces/assignment.ts | 8 ++++---- backend/src/routes/classes.ts | 3 +++ backend/src/services/assignments.ts | 1 + backend/src/services/classes.ts | 17 ++++++++++++++++- 6 files changed, 37 insertions(+), 6 deletions(-) diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index 86e3217e..e04dc801 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -3,6 +3,7 @@ import { createAssignment, deleteAssignment, getAllAssignments, getAssignment, g import { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment'; import {requireFields} from "./error-helper"; import {BadRequestException} from "../exceptions/bad-request-exception"; +import { getLogger } from '../logging/initalize.js'; export async function getAllAssignmentsHandler(req: Request, res: Response): Promise { const classId = req.params.classid; diff --git a/backend/src/controllers/classes.ts b/backend/src/controllers/classes.ts index 4e0095af..c6b71ad2 100644 --- a/backend/src/controllers/classes.ts +++ b/backend/src/controllers/classes.ts @@ -10,7 +10,8 @@ import { getClass, getClassStudents, getClassTeacherInvitations, - getClassTeachers + getClassTeachers, + putClass } from '../services/classes.js'; import { ClassDTO } from '@dwengo-1/common/interfaces/class'; import {requireFields} from "./error-helper"; @@ -41,6 +42,16 @@ export async function getClassHandler(req: Request, res: Response): Promise { + const classId = req.params.id; + requireFields({ classId }); + + const newData = req.body as Partial; + const cls = await putClass(classId, newData); + + res.json({ class: cls }); +} + export async function deleteClassHandler(req: Request, res: Response): Promise { const classId = req.params.id; const cls = await deleteClass(classId); diff --git a/backend/src/interfaces/assignment.ts b/backend/src/interfaces/assignment.ts index d48a9083..09e12dd0 100644 --- a/backend/src/interfaces/assignment.ts +++ b/backend/src/interfaces/assignment.ts @@ -4,28 +4,28 @@ import { Assignment } from '../entities/assignments/assignment.entity.js'; import { Class } from '../entities/classes/class.entity.js'; import { getLogger } from '../logging/initalize.js'; import { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment'; +import { mapToGroupDTO } from './group.js'; export function mapToAssignmentDTOId(assignment: Assignment): AssignmentDTO { return { id: assignment.id!, - class: assignment.within.classId!, + within: assignment.within.classId!, title: assignment.title, description: assignment.description, learningPath: assignment.learningPathHruid, language: assignment.learningPathLanguage, - // Groups: assignment.groups.map(group => group.groupNumber), }; } export function mapToAssignmentDTO(assignment: Assignment): AssignmentDTO { return { id: assignment.id!, - class: assignment.within.classId!, + within: assignment.within.classId!, title: assignment.title, description: assignment.description, learningPath: assignment.learningPathHruid, language: assignment.learningPathLanguage, - // Groups: assignment.groups.map(mapToGroupDTO), + // groups: assignment.groups.map(mapToGroupDTO), }; } diff --git a/backend/src/routes/classes.ts b/backend/src/routes/classes.ts index aaf95567..aa2a4814 100644 --- a/backend/src/routes/classes.ts +++ b/backend/src/routes/classes.ts @@ -10,6 +10,7 @@ import { getClassHandler, getClassStudentsHandler, getClassTeachersHandler, getTeacherInvitationsHandler, + putClassHandler, } from '../controllers/classes.js'; import assignmentRouter from './assignments.js'; @@ -22,6 +23,8 @@ router.post('/', createClassHandler); router.get('/:id', getClassHandler); +router.put('/:id', putClassHandler); + router.delete('/:id', deleteClassHandler); router.get('/:id/teacher-invitations', getTeacherInvitationsHandler); diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts index a2892bed..8ab58070 100644 --- a/backend/src/services/assignments.ts +++ b/backend/src/services/assignments.ts @@ -8,6 +8,7 @@ import { mapToSubmissionDTO, mapToSubmissionDTOId } from '../interfaces/submissi import { fetchClass } from './classes.js'; import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question'; import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission'; +import { getLogger } from '../logging/initalize.js'; export async function fetchAssignment(classid: string, assignmentNumber: number): Promise { const classRepository = getClassRepository(); diff --git a/backend/src/services/classes.ts b/backend/src/services/classes.ts index 9b74397b..f3209c0d 100644 --- a/backend/src/services/classes.ts +++ b/backend/src/services/classes.ts @@ -9,9 +9,10 @@ import { ClassDTO } from '@dwengo-1/common/interfaces/class'; import { TeacherInvitationDTO } from '@dwengo-1/common/interfaces/teacher-invitation'; import { StudentDTO } from '@dwengo-1/common/interfaces/student'; import {fetchTeacher} from "./teachers"; -import {fetchStudent} from "./students"; +import {fetchStudent, getStudent} from "./students"; import {TeacherDTO} from "@dwengo-1/common/interfaces/teacher"; import {mapToTeacherDTO} from "../interfaces/teacher"; +import { EntityDTO } from '@mikro-orm/core'; export async function fetchClass(classid: string): Promise { const classRepository = getClassRepository(); @@ -57,6 +58,16 @@ export async function createClass(classData: ClassDTO): Promise { return mapToClassDTO(newClass); } +export async function putClass(classId: string, classData: Partial): Promise { + const cls = await fetchClass(classId); + + const classRepository = getClassRepository(); + classRepository.assign(cls, classData as Partial>); + await classRepository.getEntityManager().flush(); + + return mapToClassDTO(cls); +} + export async function deleteClass(classId: string): Promise { const cls = await fetchClass(classId); @@ -107,6 +118,7 @@ export async function deleteClassStudent(classId: string, username: string): Pro const classRepository = getClassRepository(); classRepository.assign(cls, { students: cls.students.filter((student) => student.username !== username) }); + await classRepository.getEntityManager().flush(); return mapToClassDTO(cls); } @@ -116,6 +128,7 @@ export async function deleteClassTeacher(classId: string, username: string): Pro const classRepository = getClassRepository(); classRepository.assign(cls, { teachers: cls.teachers.filter((teacher) => teacher.username !== username) }); + await classRepository.getEntityManager().flush(); return mapToClassDTO(cls); } @@ -126,6 +139,7 @@ export async function addClassStudent(classId: string, username: string): Promis const classRepository = getClassRepository(); classRepository.assign(cls, { students: [...cls.students, newStudent] }); + await classRepository.getEntityManager().flush(); return mapToClassDTO(cls); } @@ -136,6 +150,7 @@ export async function addClassTeacher(classId: string, username: string): Promis const classRepository = getClassRepository(); classRepository.assign(cls, { teachers: [...cls.teachers, newTeacher] }); + await classRepository.getEntityManager().flush(); return mapToClassDTO(cls); } From 541e8ab2d534ddc958bb491ad2b440c7933f10f7 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sun, 6 Apr 2025 22:19:15 +0200 Subject: [PATCH 21/43] feat: PUT request geabstraheerd naar service-helper.ts --- backend/src/controllers/assignments.ts | 14 +++++++++++++ backend/src/services/assignments.ts | 11 ++++++++++ backend/src/services/classes.ts | 29 +++++++++++--------------- backend/src/services/service-helper.ts | 20 ++++++++++++++++++ 4 files changed, 57 insertions(+), 17 deletions(-) create mode 100644 backend/src/services/service-helper.ts diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index e04dc801..f70ae4a9 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -43,6 +43,20 @@ export async function getAssignmentHandler(req: Request, res: Response): Promise res.json({ assignment }); } +export async function putAssignmentHandler(req: Request, res: Response): Promise { + const id = Number(req.params.id); + const classid = req.params.classid; + requireFields({ id, classid }); + + if (isNaN(id)) { + throw new BadRequestException("Assignment id should be a number") + } + + const assignment = await putAssignment(classid, id); + + res.json({ assignment }); +} + export async function deleteAssignmentHandler(req: Request, res: Response): Promise { const id = Number(req.params.id); const classid = req.params.classid; diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts index 8ab58070..9f5d9183 100644 --- a/backend/src/services/assignments.ts +++ b/backend/src/services/assignments.ts @@ -9,6 +9,9 @@ import { fetchClass } from './classes.js'; import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question'; import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission'; import { getLogger } from '../logging/initalize.js'; +import { EntityData, EntityDTO, FromEntityType } from '@mikro-orm/core'; +import { DwengoEntityRepository } from '../data/dwengo-entity-repository.js'; +import { putObject } from './service-helper.js'; export async function fetchAssignment(classid: string, assignmentNumber: number): Promise { const classRepository = getClassRepository(); @@ -58,6 +61,14 @@ export async function getAssignment(classid: string, id: number): Promise>): Promise { + const assignment = await fetchAssignment(classid, id); + + await putObject(assignment, assignmentData, getAssignmentRepository()); + + return mapToAssignmentDTO(assignment); +} + export async function deleteAssignment(classid: string, id: number): Promise { const assignment = await fetchAssignment(classid, id); const cls = await fetchClass(classid); diff --git a/backend/src/services/classes.ts b/backend/src/services/classes.ts index f3209c0d..128cdec8 100644 --- a/backend/src/services/classes.ts +++ b/backend/src/services/classes.ts @@ -13,6 +13,7 @@ import {fetchStudent, getStudent} from "./students"; import {TeacherDTO} from "@dwengo-1/common/interfaces/teacher"; import {mapToTeacherDTO} from "../interfaces/teacher"; import { EntityDTO } from '@mikro-orm/core'; +import { putObject } from './service-helper.js'; export async function fetchClass(classid: string): Promise { const classRepository = getClassRepository(); @@ -58,12 +59,10 @@ export async function createClass(classData: ClassDTO): Promise { return mapToClassDTO(newClass); } -export async function putClass(classId: string, classData: Partial): Promise { +export async function putClass(classId: string, classData: Partial>): Promise { const cls = await fetchClass(classId); - const classRepository = getClassRepository(); - classRepository.assign(cls, classData as Partial>); - await classRepository.getEntityManager().flush(); + await putObject(cls, classData, getClassRepository()); return mapToClassDTO(cls); } @@ -116,9 +115,8 @@ export async function getClassTeacherInvitations(classId: string, full: boolean) export async function deleteClassStudent(classId: string, username: string): Promise { const cls = await fetchClass(classId); - const classRepository = getClassRepository(); - classRepository.assign(cls, { students: cls.students.filter((student) => student.username !== username) }); - await classRepository.getEntityManager().flush(); + const newStudents = { students: cls.students.filter((student) => student.username !== username) }; + await putObject(cls, newStudents, getClassRepository()); return mapToClassDTO(cls); } @@ -126,9 +124,8 @@ export async function deleteClassStudent(classId: string, username: string): Pro export async function deleteClassTeacher(classId: string, username: string): Promise { const cls = await fetchClass(classId); - const classRepository = getClassRepository(); - classRepository.assign(cls, { teachers: cls.teachers.filter((teacher) => teacher.username !== username) }); - await classRepository.getEntityManager().flush(); + const newTeachers = { teachers: cls.teachers.filter((teacher) => teacher.username !== username) }; + await putObject(cls, newTeachers, getClassRepository()); return mapToClassDTO(cls); } @@ -137,10 +134,9 @@ export async function addClassStudent(classId: string, username: string): Promis const cls = await fetchClass(classId); const newStudent = await fetchStudent(username); - const classRepository = getClassRepository(); - classRepository.assign(cls, { students: [...cls.students, newStudent] }); - await classRepository.getEntityManager().flush(); - + const newStudents = { students: [...cls.students, newStudent] } + await putObject(cls, newStudents, getClassRepository()); + return mapToClassDTO(cls); } @@ -148,9 +144,8 @@ export async function addClassTeacher(classId: string, username: string): Promis const cls = await fetchClass(classId); const newTeacher = await fetchTeacher(username); - const classRepository = getClassRepository(); - classRepository.assign(cls, { teachers: [...cls.teachers, newTeacher] }); - await classRepository.getEntityManager().flush(); + const newTeachers = { teachers: [...cls.teachers, newTeacher] }; + await putObject(cls, newTeachers, getClassRepository()); return mapToClassDTO(cls); } diff --git a/backend/src/services/service-helper.ts b/backend/src/services/service-helper.ts new file mode 100644 index 00000000..28ff0eb0 --- /dev/null +++ b/backend/src/services/service-helper.ts @@ -0,0 +1,20 @@ +import { EntityDTO, FromEntityType } from "@mikro-orm/core"; +import { DwengoEntityRepository } from "../data/dwengo-entity-repository"; + +/** + * Utility function to perform an PUT on an object. + * + * @param object The object that needs to be changed + * @param data The datafields and their values that will be updated + * @param repo The repository on which this action needs to be performed + * + * @returns Nothing. + */ +export async function putObject( + object: T, + data: Partial>>, + repo: DwengoEntityRepository +): Promise { + repo.assign(object, data); + await repo.getEntityManager().flush(); +} \ No newline at end of file From 8eb468eef03504570be44e47958162635cd302a1 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sun, 6 Apr 2025 22:21:07 +0200 Subject: [PATCH 22/43] fix: errors in vorige commit gefixt --- backend/src/controllers/assignments.ts | 7 +++++-- backend/src/routes/assignments.ts | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index f70ae4a9..42534b3e 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -1,9 +1,11 @@ import { Request, Response } from 'express'; -import { createAssignment, deleteAssignment, getAllAssignments, getAssignment, getAssignmentsSubmissions } from '../services/assignments.js'; +import { createAssignment, deleteAssignment, getAllAssignments, getAssignment, getAssignmentsSubmissions, putAssignment } from '../services/assignments.js'; import { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment'; import {requireFields} from "./error-helper"; import {BadRequestException} from "../exceptions/bad-request-exception"; import { getLogger } from '../logging/initalize.js'; +import { Assignment } from '../entities/assignments/assignment.entity.js'; +import { EntityDTO } from '@mikro-orm/core'; export async function getAllAssignmentsHandler(req: Request, res: Response): Promise { const classId = req.params.classid; @@ -52,7 +54,8 @@ export async function putAssignmentHandler(req: Request, res: Response): Promise throw new BadRequestException("Assignment id should be a number") } - const assignment = await putAssignment(classid, id); + const assignmentData = req.body as Partial>; + const assignment = await putAssignment(classid, id, assignmentData); res.json({ assignment }); } diff --git a/backend/src/routes/assignments.ts b/backend/src/routes/assignments.ts index 054de407..083ee586 100644 --- a/backend/src/routes/assignments.ts +++ b/backend/src/routes/assignments.ts @@ -5,6 +5,7 @@ import { getAllAssignmentsHandler, getAssignmentHandler, getAssignmentsSubmissionsHandler, + putAssignmentHandler, } from '../controllers/assignments.js'; import groupRouter from './groups.js'; @@ -16,6 +17,8 @@ router.post('/', createAssignmentHandler); router.get('/:id', getAssignmentHandler); +router.put('/:id', putAssignmentHandler); + router.delete('/:id', deleteAssignmentHandler); router.get('/:id/submissions', getAssignmentsSubmissionsHandler); From 7968d49c5ed3bbdce1c68d12ca4b709e27055bbb Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sun, 6 Apr 2025 22:21:20 +0200 Subject: [PATCH 23/43] fix: errors in vorige commit gefixt --- common/src/interfaces/assignment.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/interfaces/assignment.ts b/common/src/interfaces/assignment.ts index 8ad1649b..5cb8feff 100644 --- a/common/src/interfaces/assignment.ts +++ b/common/src/interfaces/assignment.ts @@ -2,7 +2,7 @@ import { GroupDTO } from './group'; export interface AssignmentDTO { id: number; - class: string; // Id of class 'within' + within: string; title: string; description: string; learningPath: string; From 4e6f7ccb3d19a32c32ae035382be0b9b24eb0678 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sun, 6 Apr 2025 22:21:53 +0200 Subject: [PATCH 24/43] fix: errors in vorige commit gefixt --- backend/src/controllers/classes.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/controllers/classes.ts b/backend/src/controllers/classes.ts index c6b71ad2..269259aa 100644 --- a/backend/src/controllers/classes.ts +++ b/backend/src/controllers/classes.ts @@ -15,6 +15,8 @@ import { } from '../services/classes.js'; import { ClassDTO } from '@dwengo-1/common/interfaces/class'; import {requireFields} from "./error-helper"; +import { EntityDTO } from '@mikro-orm/core'; +import { Class } from '../entities/classes/class.entity.js'; export async function getAllClassesHandler(req: Request, res: Response): Promise { const full = req.query.full === 'true'; @@ -46,7 +48,7 @@ export async function putClassHandler(req: Request, res: Response): Promise; + const newData = req.body as Partial>; const cls = await putClass(classId, newData); res.json({ class: cls }); From 33f785ebc03185669f9705e47b82020171f6e35a Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sun, 6 Apr 2025 22:27:02 +0200 Subject: [PATCH 25/43] feat: PUT op group geimplementeerd --- backend/src/controllers/groups.ts | 15 ++++++++++++++- backend/src/routes/groups.ts | 4 +++- backend/src/services/assignments.ts | 2 +- backend/src/services/classes.ts | 10 +++++----- backend/src/services/groups.ts | 16 +++++++++++++++- backend/src/services/service-helper.ts | 2 +- 6 files changed, 39 insertions(+), 10 deletions(-) diff --git a/backend/src/controllers/groups.ts b/backend/src/controllers/groups.ts index 781cd4fd..85b0809c 100644 --- a/backend/src/controllers/groups.ts +++ b/backend/src/controllers/groups.ts @@ -1,9 +1,11 @@ import { Request, Response } from 'express'; -import { createGroup, deleteGroup, getAllGroups, getGroup, getGroupSubmissions } from '../services/groups.js'; +import { createGroup, deleteGroup, getAllGroups, getGroup, getGroupSubmissions, putGroup } from '../services/groups.js'; import { GroupDTO } from '@dwengo-1/common/interfaces/group'; import { requireFields } from './error-helper.js'; import { BadRequestException } from '../exceptions/bad-request-exception.js'; import { getLogger } from '../logging/initalize.js'; +import { EntityDTO } from '@mikro-orm/core'; +import { Group } from '../entities/assignments/group.entity.js'; function checkGroupFields(classId: any, assignmentId: any, groupId: any) { requireFields({ classId, assignmentId, groupId }); @@ -28,6 +30,17 @@ export async function getGroupHandler(req: Request, res: Response): Promise { + const classId = req.params.classid; + const assignmentId = parseInt(req.params.assignmentid); + const groupId = parseInt(req.params.groupid); + checkGroupFields(classId, assignmentId, groupId); + + const group = await putGroup(classId, assignmentId, groupId, req.body as Partial>); + + res.json({ group }); +} + export async function deleteGroupHandler(req: Request, res: Response): Promise { const classId = req.params.classid; const assignmentId = parseInt(req.params.assignmentid); diff --git a/backend/src/routes/groups.ts b/backend/src/routes/groups.ts index 4a7d6b10..54c32462 100644 --- a/backend/src/routes/groups.ts +++ b/backend/src/routes/groups.ts @@ -1,5 +1,5 @@ import express from 'express'; -import { createGroupHandler, deleteGroupHandler, getAllGroupsHandler, getGroupHandler, getGroupSubmissionsHandler } from '../controllers/groups.js'; +import { createGroupHandler, deleteGroupHandler, getAllGroupsHandler, getGroupHandler, getGroupSubmissionsHandler, putGroupHandler } from '../controllers/groups.js'; const router = express.Router({ mergeParams: true }); @@ -10,6 +10,8 @@ router.post('/', createGroupHandler); router.get('/:groupid', getGroupHandler); +router.put('/:groupid', putGroupHandler); + router.delete('/:groupid', deleteGroupHandler); router.get('/:groupid/submissions', getGroupSubmissionsHandler); diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts index 9f5d9183..7b5baaf8 100644 --- a/backend/src/services/assignments.ts +++ b/backend/src/services/assignments.ts @@ -64,7 +64,7 @@ export async function getAssignment(classid: string, id: number): Promise>): Promise { const assignment = await fetchAssignment(classid, id); - await putObject(assignment, assignmentData, getAssignmentRepository()); + await putObject(assignment, assignmentData, getAssignmentRepository()); return mapToAssignmentDTO(assignment); } diff --git a/backend/src/services/classes.ts b/backend/src/services/classes.ts index 128cdec8..dfb12aa2 100644 --- a/backend/src/services/classes.ts +++ b/backend/src/services/classes.ts @@ -62,7 +62,7 @@ export async function createClass(classData: ClassDTO): Promise { export async function putClass(classId: string, classData: Partial>): Promise { const cls = await fetchClass(classId); - await putObject(cls, classData, getClassRepository()); + await putObject(cls, classData, getClassRepository()); return mapToClassDTO(cls); } @@ -116,7 +116,7 @@ export async function deleteClassStudent(classId: string, username: string): Pro const cls = await fetchClass(classId); const newStudents = { students: cls.students.filter((student) => student.username !== username) }; - await putObject(cls, newStudents, getClassRepository()); + await putObject(cls, newStudents, getClassRepository()); return mapToClassDTO(cls); } @@ -125,7 +125,7 @@ export async function deleteClassTeacher(classId: string, username: string): Pro const cls = await fetchClass(classId); const newTeachers = { teachers: cls.teachers.filter((teacher) => teacher.username !== username) }; - await putObject(cls, newTeachers, getClassRepository()); + await putObject(cls, newTeachers, getClassRepository()); return mapToClassDTO(cls); } @@ -135,7 +135,7 @@ export async function addClassStudent(classId: string, username: string): Promis const newStudent = await fetchStudent(username); const newStudents = { students: [...cls.students, newStudent] } - await putObject(cls, newStudents, getClassRepository()); + await putObject(cls, newStudents, getClassRepository()); return mapToClassDTO(cls); } @@ -145,7 +145,7 @@ export async function addClassTeacher(classId: string, username: string): Promis const newTeacher = await fetchTeacher(username); const newTeachers = { teachers: [...cls.teachers, newTeacher] }; - await putObject(cls, newTeachers, getClassRepository()); + await putObject(cls, newTeachers, getClassRepository()); return mapToClassDTO(cls); } diff --git a/backend/src/services/groups.ts b/backend/src/services/groups.ts index 68784177..442c1aec 100644 --- a/backend/src/services/groups.ts +++ b/backend/src/services/groups.ts @@ -1,4 +1,4 @@ -import { assign } from '@mikro-orm/core'; +import { assign, EntityDTO } from '@mikro-orm/core'; import { getAssignmentRepository, getClassRepository, @@ -16,6 +16,7 @@ import { getLogger } from '../logging/initalize.js'; import { fetchAssignment } from './assignments.js'; import { NotFoundException } from '../exceptions/not-found-exception.js'; import { fetchClass } from './classes.js'; +import { putObject } from './service-helper.js'; export async function fetchGroup(classId: string, assignmentNumber: number, groupNumber: number): Promise { const assignment = await fetchAssignment(classId, assignmentNumber); @@ -35,6 +36,19 @@ export async function getGroup(classId: string, assignmentNumber: number, groupN return mapToGroupDTO(group); } +export async function putGroup( + classId: string, + assignmentNumber: number, + groupNumber: number, + groupData: Partial> +): Promise { + const group = await fetchGroup(classId, assignmentNumber, groupNumber); + + await putObject(group, groupData, getGroupRepository()); + + return mapToGroupDTO(group); +} + export async function deleteGroup(classId: string, assignmentNumber: number, groupNumber: number): Promise { const group = await fetchGroup(classId, assignmentNumber, groupNumber); const assignment = await fetchAssignment(classId, assignmentNumber); diff --git a/backend/src/services/service-helper.ts b/backend/src/services/service-helper.ts index 28ff0eb0..427ff5dd 100644 --- a/backend/src/services/service-helper.ts +++ b/backend/src/services/service-helper.ts @@ -10,7 +10,7 @@ import { DwengoEntityRepository } from "../data/dwengo-entity-repository"; * * @returns Nothing. */ -export async function putObject( +export async function putObject( object: T, data: Partial>>, repo: DwengoEntityRepository From b963101f629e5f77cba54c86eef99b51a43e2356 Mon Sep 17 00:00:00 2001 From: Lint Action Date: Sun, 6 Apr 2025 20:32:08 +0000 Subject: [PATCH 26/43] style: fix linting issues met ESLint --- backend/src/data/assignments/submission-repository.ts | 4 ++-- backend/src/data/questions/question-repository.ts | 2 +- backend/src/interfaces/assignment.ts | 2 +- backend/src/services/service-helper.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/src/data/assignments/submission-repository.ts b/backend/src/data/assignments/submission-repository.ts index 371dd4ef..2e673210 100644 --- a/backend/src/data/assignments/submission-repository.ts +++ b/backend/src/data/assignments/submission-repository.ts @@ -17,7 +17,7 @@ export class SubmissionRepository extends DwengoEntityRepository { }); } - public findByLearningObject(loId: LearningObjectIdentifier): Promise { + public async findByLearningObject(loId: LearningObjectIdentifier): Promise { return this.find({ learningObjectHruid: loId.hruid, learningObjectLanguage: loId.language, @@ -25,7 +25,7 @@ export class SubmissionRepository extends DwengoEntityRepository { }); } - public findMostRecentSubmissionForStudent(loId: LearningObjectIdentifier, submitter: Student): Promise { + public async findMostRecentSubmissionForStudent(loId: LearningObjectIdentifier, submitter: Student): Promise { return this.findOne( { learningObjectHruid: loId.hruid, diff --git a/backend/src/data/questions/question-repository.ts b/backend/src/data/questions/question-repository.ts index 246b0484..eb145fa9 100644 --- a/backend/src/data/questions/question-repository.ts +++ b/backend/src/data/questions/question-repository.ts @@ -56,7 +56,7 @@ export class QuestionRepository extends DwengoEntityRepository { }); } - public findAllByAssignment(assignment: Assignment): Promise { + public async findAllByAssignment(assignment: Assignment): Promise { return this.find({ author: assignment.groups.flatMap(group => group.members), learningObjectHruid: assignment.learningPathHruid, diff --git a/backend/src/interfaces/assignment.ts b/backend/src/interfaces/assignment.ts index 09e12dd0..2e591797 100644 --- a/backend/src/interfaces/assignment.ts +++ b/backend/src/interfaces/assignment.ts @@ -25,7 +25,7 @@ export function mapToAssignmentDTO(assignment: Assignment): AssignmentDTO { description: assignment.description, learningPath: assignment.learningPathHruid, language: assignment.learningPathLanguage, - // groups: assignment.groups.map(mapToGroupDTO), + // Groups: assignment.groups.map(mapToGroupDTO), }; } diff --git a/backend/src/services/service-helper.ts b/backend/src/services/service-helper.ts index 28ff0eb0..3f8c897e 100644 --- a/backend/src/services/service-helper.ts +++ b/backend/src/services/service-helper.ts @@ -10,7 +10,7 @@ import { DwengoEntityRepository } from "../data/dwengo-entity-repository"; * * @returns Nothing. */ -export async function putObject( +export async function putObject( object: T, data: Partial>>, repo: DwengoEntityRepository From 34facfe7c8c58aed35ef2209e426312dd3d60294 Mon Sep 17 00:00:00 2001 From: Lint Action Date: Sun, 6 Apr 2025 20:32:13 +0000 Subject: [PATCH 27/43] style: fix linting issues met Prettier --- backend/src/controllers/assignments.ts | 23 +++++++++------ backend/src/controllers/classes.ts | 4 +-- backend/src/controllers/submissions.ts | 8 ++---- .../src/data/questions/question-repository.ts | 4 +-- backend/src/routes/classes.ts | 3 +- backend/src/services/assignments.ts | 28 +++++++++---------- backend/src/services/classes.ts | 28 +++++++++---------- backend/src/services/groups.ts | 8 +++--- backend/src/services/service-helper.ts | 12 ++++---- backend/src/services/submissions.ts | 16 +++-------- backend/src/services/teachers.ts | 6 ++-- frontend/src/controllers/classes.ts | 2 +- 12 files changed, 70 insertions(+), 72 deletions(-) diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index 42534b3e..c262057c 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -1,8 +1,15 @@ import { Request, Response } from 'express'; -import { createAssignment, deleteAssignment, getAllAssignments, getAssignment, getAssignmentsSubmissions, putAssignment } from '../services/assignments.js'; +import { + createAssignment, + deleteAssignment, + getAllAssignments, + getAssignment, + getAssignmentsSubmissions, + putAssignment, +} from '../services/assignments.js'; import { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment'; -import {requireFields} from "./error-helper"; -import {BadRequestException} from "../exceptions/bad-request-exception"; +import { requireFields } from './error-helper'; +import { BadRequestException } from '../exceptions/bad-request-exception'; import { getLogger } from '../logging/initalize.js'; import { Assignment } from '../entities/assignments/assignment.entity.js'; import { EntityDTO } from '@mikro-orm/core'; @@ -37,7 +44,7 @@ export async function getAssignmentHandler(req: Request, res: Response): Promise requireFields({ id, classid }); if (isNaN(id)) { - throw new BadRequestException("Assignment id should be a number") + throw new BadRequestException('Assignment id should be a number'); } const assignment = await getAssignment(classid, id); @@ -51,7 +58,7 @@ export async function putAssignmentHandler(req: Request, res: Response): Promise requireFields({ id, classid }); if (isNaN(id)) { - throw new BadRequestException("Assignment id should be a number") + throw new BadRequestException('Assignment id should be a number'); } const assignmentData = req.body as Partial>; @@ -66,10 +73,10 @@ export async function deleteAssignmentHandler(req: Request, res: Response): Prom requireFields({ id, classid }); if (isNaN(id)) { - throw new BadRequestException("Assignment id should be a number"); + throw new BadRequestException('Assignment id should be a number'); } - const assignment = await deleteAssignment(classid, id); + const assignment = await deleteAssignment(classid, id); } export async function getAssignmentsSubmissionsHandler(req: Request, res: Response): Promise { @@ -79,7 +86,7 @@ export async function getAssignmentsSubmissionsHandler(req: Request, res: Respon requireFields({ assignmentNumber, classid }); if (isNaN(assignmentNumber)) { - throw new BadRequestException("Assignment id should be a number") + throw new BadRequestException('Assignment id should be a number'); } const submissions = await getAssignmentsSubmissions(classid, assignmentNumber, full); diff --git a/backend/src/controllers/classes.ts b/backend/src/controllers/classes.ts index 269259aa..6252c714 100644 --- a/backend/src/controllers/classes.ts +++ b/backend/src/controllers/classes.ts @@ -11,10 +11,10 @@ import { getClassStudents, getClassTeacherInvitations, getClassTeachers, - putClass + putClass, } from '../services/classes.js'; import { ClassDTO } from '@dwengo-1/common/interfaces/class'; -import {requireFields} from "./error-helper"; +import { requireFields } from './error-helper'; import { EntityDTO } from '@mikro-orm/core'; import { Class } from '../entities/classes/class.entity.js'; diff --git a/backend/src/controllers/submissions.ts b/backend/src/controllers/submissions.ts index 3e10b0f2..a742685f 100644 --- a/backend/src/controllers/submissions.ts +++ b/backend/src/controllers/submissions.ts @@ -7,19 +7,17 @@ import { Language, languageMap } from '@dwengo-1/common/util/language'; import { SubmissionDTO } from '@dwengo-1/common/interfaces/submission'; import { requireFields } from './error-helper.js'; - - export async function getSubmissionHandler(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 submissionNumber = Number(req.params.id); requireFields({ lohruid, submissionNumber }); - + if (isNaN(submissionNumber)) { throw new BadRequestException('Submission number must be a number'); } - + const loId = new LearningObjectIdentifier(lohruid, lang, version); const submission = await getSubmission(loId, submissionNumber); @@ -54,7 +52,7 @@ export async function deleteSubmissionHandler(req: Request, res: Response): Prom requireFields({ hruid, submissionNumber }); if (isNaN(submissionNumber)) { - throw new BadRequestException('Submission number must be a number'); + throw new BadRequestException('Submission number must be a number'); } const loId = new LearningObjectIdentifier(hruid, lang, version); diff --git a/backend/src/data/questions/question-repository.ts b/backend/src/data/questions/question-repository.ts index eb145fa9..af1e0a17 100644 --- a/backend/src/data/questions/question-repository.ts +++ b/backend/src/data/questions/question-repository.ts @@ -58,12 +58,12 @@ export class QuestionRepository extends DwengoEntityRepository { public async findAllByAssignment(assignment: Assignment): Promise { return this.find({ - author: assignment.groups.flatMap(group => group.members), + author: assignment.groups.flatMap((group) => group.members), learningObjectHruid: assignment.learningPathHruid, learningObjectLanguage: assignment.learningPathLanguage, }); } - + public async findAllByAuthor(author: Student): Promise { return this.findAll({ where: { author }, diff --git a/backend/src/routes/classes.ts b/backend/src/routes/classes.ts index aa2a4814..cef6fd72 100644 --- a/backend/src/routes/classes.ts +++ b/backend/src/routes/classes.ts @@ -8,7 +8,8 @@ import { deleteClassTeacherHandler, getAllClassesHandler, getClassHandler, - getClassStudentsHandler, getClassTeachersHandler, + getClassStudentsHandler, + getClassTeachersHandler, getTeacherInvitationsHandler, putClassHandler, } from '../controllers/classes.js'; diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts index 9f5d9183..3f1de240 100644 --- a/backend/src/services/assignments.ts +++ b/backend/src/services/assignments.ts @@ -1,5 +1,11 @@ import { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment'; -import { getAssignmentRepository, getClassRepository, getGroupRepository, getQuestionRepository, getSubmissionRepository } from '../data/repositories.js'; +import { + getAssignmentRepository, + getClassRepository, + getGroupRepository, + getQuestionRepository, + getSubmissionRepository, +} from '../data/repositories.js'; import { Assignment } from '../entities/assignments/assignment.entity.js'; import { NotFoundException } from '../exceptions/not-found-exception.js'; import { mapToAssignment, mapToAssignmentDTO, mapToAssignmentDTOId } from '../interfaces/assignment.js'; @@ -18,7 +24,7 @@ export async function fetchAssignment(classid: string, assignmentNumber: number) const cls = await classRepository.findById(classid); if (!cls) { - throw new NotFoundException('Could not find assignment\'s class'); + throw new NotFoundException("Could not find assignment's class"); } const assignmentRepository = getAssignmentRepository(); @@ -48,10 +54,10 @@ export async function createAssignment(classid: string, assignmentData: Assignme const cls = await fetchClass(classid); const assignment = mapToAssignment(assignmentData, cls); - + const assignmentRepository = getAssignmentRepository(); const newAssignment = assignmentRepository.create(assignment); - await assignmentRepository.save(newAssignment, {preventOverwrite: true}); + await assignmentRepository.save(newAssignment, { preventOverwrite: true }); return mapToAssignmentDTO(newAssignment); } @@ -63,7 +69,7 @@ export async function getAssignment(classid: string, id: number): Promise>): Promise { const assignment = await fetchAssignment(classid, id); - + await putObject(assignment, assignmentData, getAssignmentRepository()); return mapToAssignmentDTO(assignment); @@ -90,9 +96,7 @@ export async function getAssignmentsSubmissions( const groups = await groupRepository.findAllGroupsForAssignment(assignment); const submissionRepository = getSubmissionRepository(); - const submissions = (await Promise.all( - groups.map(async (group) => submissionRepository.findAllSubmissionsForGroup(group)) - )).flat(); + const submissions = (await Promise.all(groups.map(async (group) => submissionRepository.findAllSubmissionsForGroup(group)))).flat(); if (full) { return submissions.map(mapToSubmissionDTO); @@ -101,11 +105,7 @@ export async function getAssignmentsSubmissions( return submissions.map(mapToSubmissionDTOId); } -export async function getAssignmentsQuestions( - classid: string, - assignmentNumber: number, - full: boolean -): Promise { +export async function getAssignmentsQuestions(classid: string, assignmentNumber: number, full: boolean): Promise { const assignment = await fetchAssignment(classid, assignmentNumber); const questionRepository = getQuestionRepository(); @@ -116,4 +116,4 @@ export async function getAssignmentsQuestions( } return questions.map(mapToQuestionDTO); -} \ No newline at end of file +} diff --git a/backend/src/services/classes.ts b/backend/src/services/classes.ts index 128cdec8..b6b4b04e 100644 --- a/backend/src/services/classes.ts +++ b/backend/src/services/classes.ts @@ -8,10 +8,10 @@ import { Class } from '../entities/classes/class.entity.js'; import { ClassDTO } from '@dwengo-1/common/interfaces/class'; import { TeacherInvitationDTO } from '@dwengo-1/common/interfaces/teacher-invitation'; import { StudentDTO } from '@dwengo-1/common/interfaces/student'; -import {fetchTeacher} from "./teachers"; -import {fetchStudent, getStudent} from "./students"; -import {TeacherDTO} from "@dwengo-1/common/interfaces/teacher"; -import {mapToTeacherDTO} from "../interfaces/teacher"; +import { fetchTeacher } from './teachers'; +import { fetchStudent, getStudent } from './students'; +import { TeacherDTO } from '@dwengo-1/common/interfaces/teacher'; +import { mapToTeacherDTO } from '../interfaces/teacher'; import { EntityDTO } from '@mikro-orm/core'; import { putObject } from './service-helper.js'; @@ -20,7 +20,7 @@ export async function fetchClass(classid: string): Promise { const cls = await classRepository.findById(classid); if (!cls) { - throw new NotFoundException("Class not found"); + throw new NotFoundException('Class not found'); } return cls; @@ -43,10 +43,10 @@ export async function getClass(classId: string): Promise { export async function createClass(classData: ClassDTO): Promise { const teacherUsernames = classData.teachers || []; - const teachers = (await Promise.all(teacherUsernames.map(async (id) => fetchTeacher(id) ))); + const teachers = await Promise.all(teacherUsernames.map(async (id) => fetchTeacher(id))); const studentUsernames = classData.students || []; - const students = (await Promise.all(studentUsernames.map(async (id) => fetchStudent(id) ))); + const students = await Promise.all(studentUsernames.map(async (id) => fetchStudent(id))); const classRepository = getClassRepository(); const newClass = classRepository.create({ @@ -54,7 +54,7 @@ export async function createClass(classData: ClassDTO): Promise { teachers: teachers, students: students, }); - await classRepository.save(newClass, {preventOverwrite: true}); + await classRepository.save(newClass, { preventOverwrite: true }); return mapToClassDTO(newClass); } @@ -93,7 +93,7 @@ export async function getClassStudentsDTO(classId: string): Promise { const cls = await fetchClass(classId); - if (full){ + if (full) { return cls.teachers.map(mapToTeacherDTO); } return cls.teachers.map((student) => student.username); @@ -114,7 +114,7 @@ export async function getClassTeacherInvitations(classId: string, full: boolean) export async function deleteClassStudent(classId: string, username: string): Promise { const cls = await fetchClass(classId); - + const newStudents = { students: cls.students.filter((student) => student.username !== username) }; await putObject(cls, newStudents, getClassRepository()); @@ -123,7 +123,7 @@ export async function deleteClassStudent(classId: string, username: string): Pro export async function deleteClassTeacher(classId: string, username: string): Promise { const cls = await fetchClass(classId); - + const newTeachers = { teachers: cls.teachers.filter((teacher) => teacher.username !== username) }; await putObject(cls, newTeachers, getClassRepository()); @@ -134,9 +134,9 @@ export async function addClassStudent(classId: string, username: string): Promis const cls = await fetchClass(classId); const newStudent = await fetchStudent(username); - const newStudents = { students: [...cls.students, newStudent] } + const newStudents = { students: [...cls.students, newStudent] }; await putObject(cls, newStudents, getClassRepository()); - + return mapToClassDTO(cls); } @@ -145,7 +145,7 @@ export async function addClassTeacher(classId: string, username: string): Promis const newTeacher = await fetchTeacher(username); const newTeachers = { teachers: [...cls.teachers, newTeacher] }; - await putObject(cls, newTeachers, getClassRepository()); + await putObject(cls, newTeachers, getClassRepository()); return mapToClassDTO(cls); } diff --git a/backend/src/services/groups.ts b/backend/src/services/groups.ts index 68784177..711cc11f 100644 --- a/backend/src/services/groups.ts +++ b/backend/src/services/groups.ts @@ -27,7 +27,7 @@ export async function fetchGroup(classId: string, assignmentNumber: number, grou throw new NotFoundException('Could not find group'); } - return group; + return group; } export async function getGroup(classId: string, assignmentNumber: number, groupNumber: number): Promise { @@ -38,7 +38,7 @@ export async function getGroup(classId: string, assignmentNumber: number, groupN export async function deleteGroup(classId: string, assignmentNumber: number, groupNumber: number): Promise { const group = await fetchGroup(classId, assignmentNumber, groupNumber); const assignment = await fetchAssignment(classId, assignmentNumber); - + const groupRepository = getGroupRepository(); await groupRepository.deleteByAssignmentAndGroupNumber(assignment, groupNumber); @@ -46,8 +46,8 @@ export async function deleteGroup(classId: string, assignmentNumber: number, gro } export async function getExistingGroupFromGroupDTO(groupData: GroupDTO) { - const classId = typeof(groupData.class) === 'string' ? groupData.class : groupData.class.id; - const assignmentNumber = typeof(groupData.assignment) === 'number' ? groupData.assignment : groupData.assignment.id; + const classId = typeof groupData.class === 'string' ? groupData.class : groupData.class.id; + const assignmentNumber = typeof groupData.assignment === 'number' ? groupData.assignment : groupData.assignment.id; const groupNumber = groupData.groupNumber; return await fetchGroup(classId, assignmentNumber, groupNumber); diff --git a/backend/src/services/service-helper.ts b/backend/src/services/service-helper.ts index 3f8c897e..215564f3 100644 --- a/backend/src/services/service-helper.ts +++ b/backend/src/services/service-helper.ts @@ -1,5 +1,5 @@ -import { EntityDTO, FromEntityType } from "@mikro-orm/core"; -import { DwengoEntityRepository } from "../data/dwengo-entity-repository"; +import { EntityDTO, FromEntityType } from '@mikro-orm/core'; +import { DwengoEntityRepository } from '../data/dwengo-entity-repository'; /** * Utility function to perform an PUT on an object. @@ -7,14 +7,14 @@ import { DwengoEntityRepository } from "../data/dwengo-entity-repository"; * @param object The object that needs to be changed * @param data The datafields and their values that will be updated * @param repo The repository on which this action needs to be performed - * + * * @returns Nothing. */ export async function putObject( - object: T, - data: Partial>>, + object: T, + data: Partial>>, repo: DwengoEntityRepository ): Promise { repo.assign(object, data); await repo.getEntityManager().flush(); -} \ No newline at end of file +} diff --git a/backend/src/services/submissions.ts b/backend/src/services/submissions.ts index f3ee0f10..25f6e7f0 100644 --- a/backend/src/services/submissions.ts +++ b/backend/src/services/submissions.ts @@ -8,10 +8,7 @@ import { fetchStudent } from './students.js'; import { fetchGroup, getExistingGroupFromGroupDTO } from './groups.js'; import { Submission } from '../entities/assignments/submission.entity.js'; -export async function fetchSubmission( - loId: LearningObjectIdentifier, - submissionNumber: number, -): Promise { +export async function fetchSubmission(loId: LearningObjectIdentifier, submissionNumber: number): Promise { const submissionRepository = getSubmissionRepository(); const submission = await submissionRepository.findSubmissionByLearningObjectAndSubmissionNumber(loId, submissionNumber); @@ -22,17 +19,12 @@ export async function fetchSubmission( return submission; } -export async function getSubmission( - loId: LearningObjectIdentifier, - submissionNumber: number -): Promise { +export async function getSubmission(loId: LearningObjectIdentifier, submissionNumber: number): Promise { const submission = await fetchSubmission(loId, submissionNumber); return mapToSubmissionDTO(submission); } -export async function getAllSubmissions( - loId: LearningObjectIdentifier, -): Promise { +export async function getAllSubmissions(loId: LearningObjectIdentifier): Promise { const submissionRepository = getSubmissionRepository(); const submissions = await submissionRepository.findByLearningObject(loId); @@ -52,7 +44,7 @@ export async function createSubmission(submissionDTO: SubmissionDTO): Promise { const submission = await fetchSubmission(loId, submissionNumber); - + const submissionRepository = getSubmissionRepository(); await submissionRepository.deleteSubmissionByLearningObjectAndSubmissionNumber(loId, submissionNumber); diff --git a/backend/src/services/teachers.ts b/backend/src/services/teachers.ts index 487e42be..3ebc6609 100644 --- a/backend/src/services/teachers.ts +++ b/backend/src/services/teachers.ts @@ -22,7 +22,7 @@ import { Question } from '../entities/questions/question.entity.js'; import { ClassJoinRequestRepository } from '../data/classes/class-join-request-repository.js'; import { Student } from '../entities/users/student.entity.js'; import { NotFoundException } from '../exceptions/not-found-exception.js'; -import {getClassStudents, getClassStudentsDTO} from './classes.js'; +import { getClassStudents, getClassStudentsDTO } from './classes.js'; import { TeacherDTO } from '@dwengo-1/common/interfaces/teacher'; import { ClassDTO } from '@dwengo-1/common/interfaces/class'; import { StudentDTO } from '@dwengo-1/common/interfaces/student'; @@ -102,9 +102,9 @@ export async function getStudentsByTeacher(username: string, full: boolean): Pro const students: StudentDTO[] = (await Promise.all(classIds.map(async (username) => await getClassStudentsDTO(username)))).flat(); if (full) { - return students + return students; } - + return students.map((student) => student.username); } diff --git a/frontend/src/controllers/classes.ts b/frontend/src/controllers/classes.ts index 9c8a667f..d8e93eca 100644 --- a/frontend/src/controllers/classes.ts +++ b/frontend/src/controllers/classes.ts @@ -3,7 +3,7 @@ import type { ClassDTO } from "@dwengo-1/common/interfaces/class"; import type { StudentsResponse } from "./students"; import type { AssignmentsResponse } from "./assignments"; import type { TeacherInvitationDTO } from "@dwengo-1/common/interfaces/teacher-invitation"; -import type {TeachersResponse} from "@/controllers/teachers.ts"; +import type { TeachersResponse } from "@/controllers/teachers.ts"; export interface ClassesResponse { classes: ClassDTO[] | string[]; From 81da765a74bc50922392e726b32e926ca276db87 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sun, 6 Apr 2025 22:40:09 +0200 Subject: [PATCH 28/43] fix: linter run --- backend/src/data/assignments/submission-repository.ts | 4 ++-- backend/src/data/questions/question-repository.ts | 2 +- backend/src/interfaces/assignment.ts | 2 +- backend/src/services/service-helper.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/src/data/assignments/submission-repository.ts b/backend/src/data/assignments/submission-repository.ts index 371dd4ef..2e673210 100644 --- a/backend/src/data/assignments/submission-repository.ts +++ b/backend/src/data/assignments/submission-repository.ts @@ -17,7 +17,7 @@ export class SubmissionRepository extends DwengoEntityRepository { }); } - public findByLearningObject(loId: LearningObjectIdentifier): Promise { + public async findByLearningObject(loId: LearningObjectIdentifier): Promise { return this.find({ learningObjectHruid: loId.hruid, learningObjectLanguage: loId.language, @@ -25,7 +25,7 @@ export class SubmissionRepository extends DwengoEntityRepository { }); } - public findMostRecentSubmissionForStudent(loId: LearningObjectIdentifier, submitter: Student): Promise { + public async findMostRecentSubmissionForStudent(loId: LearningObjectIdentifier, submitter: Student): Promise { return this.findOne( { learningObjectHruid: loId.hruid, diff --git a/backend/src/data/questions/question-repository.ts b/backend/src/data/questions/question-repository.ts index 246b0484..eb145fa9 100644 --- a/backend/src/data/questions/question-repository.ts +++ b/backend/src/data/questions/question-repository.ts @@ -56,7 +56,7 @@ export class QuestionRepository extends DwengoEntityRepository { }); } - public findAllByAssignment(assignment: Assignment): Promise { + public async findAllByAssignment(assignment: Assignment): Promise { return this.find({ author: assignment.groups.flatMap(group => group.members), learningObjectHruid: assignment.learningPathHruid, diff --git a/backend/src/interfaces/assignment.ts b/backend/src/interfaces/assignment.ts index 09e12dd0..2e591797 100644 --- a/backend/src/interfaces/assignment.ts +++ b/backend/src/interfaces/assignment.ts @@ -25,7 +25,7 @@ export function mapToAssignmentDTO(assignment: Assignment): AssignmentDTO { description: assignment.description, learningPath: assignment.learningPathHruid, language: assignment.learningPathLanguage, - // groups: assignment.groups.map(mapToGroupDTO), + // Groups: assignment.groups.map(mapToGroupDTO), }; } diff --git a/backend/src/services/service-helper.ts b/backend/src/services/service-helper.ts index 427ff5dd..1b7c23fb 100644 --- a/backend/src/services/service-helper.ts +++ b/backend/src/services/service-helper.ts @@ -10,7 +10,7 @@ import { DwengoEntityRepository } from "../data/dwengo-entity-repository"; * * @returns Nothing. */ -export async function putObject( +export async function putObject( object: T, data: Partial>>, repo: DwengoEntityRepository From aa2d4242ae8d8d15434167a47242fded08155a08 Mon Sep 17 00:00:00 2001 From: Lint Action Date: Sun, 6 Apr 2025 20:44:06 +0000 Subject: [PATCH 29/43] style: fix linting issues met Prettier --- backend/src/routes/groups.ts | 9 ++++++++- backend/src/services/assignments.ts | 2 +- backend/src/services/classes.ts | 6 +++--- backend/src/services/groups.ts | 6 +++--- backend/src/services/service-helper.ts | 4 ++-- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/backend/src/routes/groups.ts b/backend/src/routes/groups.ts index 54c32462..7f973972 100644 --- a/backend/src/routes/groups.ts +++ b/backend/src/routes/groups.ts @@ -1,5 +1,12 @@ import express from 'express'; -import { createGroupHandler, deleteGroupHandler, getAllGroupsHandler, getGroupHandler, getGroupSubmissionsHandler, putGroupHandler } from '../controllers/groups.js'; +import { + createGroupHandler, + deleteGroupHandler, + getAllGroupsHandler, + getGroupHandler, + getGroupSubmissionsHandler, + putGroupHandler, +} from '../controllers/groups.js'; const router = express.Router({ mergeParams: true }); diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts index d703e7f4..55f4947c 100644 --- a/backend/src/services/assignments.ts +++ b/backend/src/services/assignments.ts @@ -69,7 +69,7 @@ export async function getAssignment(classid: string, id: number): Promise>): Promise { const assignment = await fetchAssignment(classid, id); - + await putObject(assignment, assignmentData, getAssignmentRepository()); return mapToAssignmentDTO(assignment); diff --git a/backend/src/services/classes.ts b/backend/src/services/classes.ts index c9d44730..4abe18af 100644 --- a/backend/src/services/classes.ts +++ b/backend/src/services/classes.ts @@ -134,9 +134,9 @@ export async function addClassStudent(classId: string, username: string): Promis const cls = await fetchClass(classId); const newStudent = await fetchStudent(username); - const newStudents = { students: [...cls.students, newStudent] } + const newStudents = { students: [...cls.students, newStudent] }; await putObject(cls, newStudents, getClassRepository()); - + return mapToClassDTO(cls); } @@ -145,7 +145,7 @@ export async function addClassTeacher(classId: string, username: string): Promis const newTeacher = await fetchTeacher(username); const newTeachers = { teachers: [...cls.teachers, newTeacher] }; - await putObject(cls, newTeachers, getClassRepository()); + await putObject(cls, newTeachers, getClassRepository()); return mapToClassDTO(cls); } diff --git a/backend/src/services/groups.ts b/backend/src/services/groups.ts index 40bad732..9b9903ed 100644 --- a/backend/src/services/groups.ts +++ b/backend/src/services/groups.ts @@ -37,9 +37,9 @@ export async function getGroup(classId: string, assignmentNumber: number, groupN } export async function putGroup( - classId: string, - assignmentNumber: number, - groupNumber: number, + classId: string, + assignmentNumber: number, + groupNumber: number, groupData: Partial> ): Promise { const group = await fetchGroup(classId, assignmentNumber, groupNumber); diff --git a/backend/src/services/service-helper.ts b/backend/src/services/service-helper.ts index 47c4b757..641fada4 100644 --- a/backend/src/services/service-helper.ts +++ b/backend/src/services/service-helper.ts @@ -11,8 +11,8 @@ import { DwengoEntityRepository } from '../data/dwengo-entity-repository'; * @returns Nothing. */ export async function putObject( - object: T, - data: Partial>>, + object: T, + data: Partial>>, repo: DwengoEntityRepository ): Promise { repo.assign(object, data); From d59c61e2e2b3d0118e33295ce7cafa1d7dc8077d Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Mon, 7 Apr 2025 17:33:03 +0200 Subject: [PATCH 30/43] feat: frontend controller endpoints voor add en delete teacher en student van een klas --- frontend/src/controllers/classes.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/frontend/src/controllers/classes.ts b/frontend/src/controllers/classes.ts index 6a5f489c..c6c80af0 100644 --- a/frontend/src/controllers/classes.ts +++ b/frontend/src/controllers/classes.ts @@ -46,10 +46,26 @@ export class ClassController extends BaseController { return this.get(`/${id}/students`, { full }); } + async addStudent(id: string, username: string): Promise { + return this.post(`/${id}/students`, { username }); + } + + async deleteStudent(id: string, username: string): Promise { + return this.delete(`/${id}/students/${ username }`); + } + async getTeachers(id: string, full = true): Promise { return this.get(`/${id}/teachers`, { full }); } + async addTeacher(id: string, username: string): Promise { + return this.post(`/${id}/teachers`, { username }); + } + + async deleteTeacher(id: string, username: string): Promise { + return this.delete(`/${id}/teachers/${ username }`); + } + async getTeacherInvitations(id: string, full = true): Promise { return this.get(`/${id}/teacher-invitations`, { full }); } From 048bba5c7e5c3a0c44d9caf6e1435d3813e34768 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Mon, 7 Apr 2025 17:35:20 +0200 Subject: [PATCH 31/43] feat: PUT in frontend controller voor assignment --- frontend/src/controllers/assignments.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/controllers/assignments.ts b/frontend/src/controllers/assignments.ts index 59ea2428..a66f8e84 100644 --- a/frontend/src/controllers/assignments.ts +++ b/frontend/src/controllers/assignments.ts @@ -33,6 +33,10 @@ export class AssignmentController extends BaseController { return this.delete(`/${num}`); } + async updateAssignment(num: number, data: Partial): Promise { + return this.put(`/${num}`, data); + } + async getSubmissions(assignmentNumber: number, full = true): Promise { return this.get(`/${assignmentNumber}/submissions`, { full }); } From 6a1f5ac4e8cecfdccfa3828ce771a740e9a99d1b Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Mon, 7 Apr 2025 17:36:09 +0200 Subject: [PATCH 32/43] feat: PUT voor frontend controller groups toegevoegd --- frontend/src/controllers/groups.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/controllers/groups.ts b/frontend/src/controllers/groups.ts index 05192636..de6592b5 100644 --- a/frontend/src/controllers/groups.ts +++ b/frontend/src/controllers/groups.ts @@ -32,6 +32,10 @@ export class GroupController extends BaseController { return this.delete(`/${num}`); } + async updateGroup(num: number, data: Partial): Promise { + return this.put(`/${num}`, data); + } + async getSubmissions(groupNumber: number, full = true): Promise { return this.get(`/${groupNumber}/submissions`, { full }); } From aea0c3c7c92f7d90d88ced0dbc1d27491d681b5c Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Mon, 7 Apr 2025 17:37:23 +0200 Subject: [PATCH 33/43] feat: PUT voor frontend controller classes toegevoegd --- frontend/src/controllers/classes.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/controllers/classes.ts b/frontend/src/controllers/classes.ts index c6c80af0..4a349e85 100644 --- a/frontend/src/controllers/classes.ts +++ b/frontend/src/controllers/classes.ts @@ -42,6 +42,10 @@ export class ClassController extends BaseController { return this.delete(`/${id}`); } + async updateClass(id: string, data: Partial): Promise { + return this.put(`/${id}`, data); + } + async getStudents(id: string, full = true): Promise { return this.get(`/${id}/students`, { full }); } From 3f74c3491fe5187c57470ccbb66afaf1c777fdf9 Mon Sep 17 00:00:00 2001 From: Lint Action Date: Mon, 7 Apr 2025 15:40:04 +0000 Subject: [PATCH 34/43] style: fix linting issues met Prettier --- frontend/src/controllers/classes.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/controllers/classes.ts b/frontend/src/controllers/classes.ts index 4a349e85..03e3f560 100644 --- a/frontend/src/controllers/classes.ts +++ b/frontend/src/controllers/classes.ts @@ -55,7 +55,7 @@ export class ClassController extends BaseController { } async deleteStudent(id: string, username: string): Promise { - return this.delete(`/${id}/students/${ username }`); + return this.delete(`/${id}/students/${username}`); } async getTeachers(id: string, full = true): Promise { @@ -67,7 +67,7 @@ export class ClassController extends BaseController { } async deleteTeacher(id: string, username: string): Promise { - return this.delete(`/${id}/teachers/${ username }`); + return this.delete(`/${id}/teachers/${username}`); } async getTeacherInvitations(id: string, full = true): Promise { From 12d21f3c49c59b9207956f361ef0a541215d1b1f Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Mon, 7 Apr 2025 23:48:35 +0200 Subject: [PATCH 35/43] fix: check student in klas, conflict error --- backend/src/services/students.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts index dc40e468..a8300db5 100644 --- a/backend/src/services/students.ts +++ b/backend/src/services/students.ts @@ -23,6 +23,8 @@ import { GroupDTO } from '@dwengo-1/common/interfaces/group'; import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission'; import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question'; import { ClassJoinRequestDTO } from '@dwengo-1/common/interfaces/class-join-request'; +import {BadRequestException} from "../exceptions/bad-request-exception"; +import {ConflictException} from "../exceptions/conflict-exception"; export async function getAllStudents(full: boolean): Promise { const studentRepository = getStudentRepository(); @@ -135,6 +137,10 @@ export async function createClassJoinRequest(username: string, classId: string): const student = await fetchStudent(username); // Throws error if student not found const cls = await fetchClass(classId); + if (cls.students.contains(student)) { + throw new ConflictException("Student already in this class"); + } + const request = mapToStudentRequest(student, cls); await requestRepo.save(request, { preventOverwrite: true }); return mapToStudentRequestDTO(request); From 7887a5aec42bb8cea6c356838148d38a79339bfd Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Mon, 7 Apr 2025 23:49:15 +0200 Subject: [PATCH 36/43] fix: voeg student toe aan klas indien leeraar accept --- backend/src/services/teachers.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/backend/src/services/teachers.ts b/backend/src/services/teachers.ts index 3ebc6609..4e67d27d 100644 --- a/backend/src/services/teachers.ts +++ b/backend/src/services/teachers.ts @@ -22,13 +22,15 @@ import { Question } from '../entities/questions/question.entity.js'; import { ClassJoinRequestRepository } from '../data/classes/class-join-request-repository.js'; import { Student } from '../entities/users/student.entity.js'; import { NotFoundException } from '../exceptions/not-found-exception.js'; -import { getClassStudents, getClassStudentsDTO } from './classes.js'; +import {addClassStudent, fetchClass, getClassStudents, getClassStudentsDTO} from './classes.js'; import { TeacherDTO } from '@dwengo-1/common/interfaces/teacher'; import { ClassDTO } from '@dwengo-1/common/interfaces/class'; import { StudentDTO } from '@dwengo-1/common/interfaces/student'; import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question'; import { ClassJoinRequestDTO } from '@dwengo-1/common/interfaces/class-join-request'; import { ClassJoinRequestStatus } from '@dwengo-1/common/util/class-join-request'; +import {BadRequestException} from "../exceptions/bad-request-exception"; +import {ConflictException} from "../exceptions/conflict-exception"; export async function getAllTeachers(full: boolean): Promise { const teacherRepository: TeacherRepository = getTeacherRepository(); @@ -145,13 +147,12 @@ export async function getJoinRequestsByClass(classId: string): Promise { const requestRepo: ClassJoinRequestRepository = getClassJoinRequestRepository(); - const classRepo: ClassRepository = getClassRepository(); const student: Student = await fetchStudent(studentUsername); - const cls: Class | null = await classRepo.findById(classId); + const cls = await fetchClass(classId); - if (!cls) { - throw new NotFoundException('Class not found'); + if (cls.students.contains(student)) { + throw new ConflictException("Student already in this class"); } const request: ClassJoinRequest | null = await requestRepo.findByStudentAndClass(student, cls); @@ -160,8 +161,14 @@ export async function updateClassJoinRequestStatus(studentUsername: string, clas throw new NotFoundException('Join request not found'); } - request.status = accepted ? ClassJoinRequestStatus.Accepted : ClassJoinRequestStatus.Declined; + request.status = ClassJoinRequestStatus.Declined; + + if (accepted){ + request.status = ClassJoinRequestStatus.Accepted; + await addClassStudent(classId, studentUsername); + } await requestRepo.save(request); + return mapToStudentRequestDTO(request); } From 65bce19fcf48b46c4b1ec5da145e65b4b5af18d1 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Mon, 7 Apr 2025 23:49:35 +0200 Subject: [PATCH 37/43] fix: query naar param --- backend/src/controllers/teachers.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/backend/src/controllers/teachers.ts b/backend/src/controllers/teachers.ts index 9275ca92..c8063f80 100644 --- a/backend/src/controllers/teachers.ts +++ b/backend/src/controllers/teachers.ts @@ -81,16 +81,15 @@ export async function getTeacherQuestionHandler(req: Request, res: Response): Pr } export async function getStudentJoinRequestHandler(req: Request, res: Response): Promise { - const username = req.query.username as string; const classId = req.params.classId; - requireFields({ username, classId }); + requireFields({ classId }); const joinRequests = await getJoinRequestsByClass(classId); res.json({ joinRequests }); } export async function updateStudentJoinRequestHandler(req: Request, res: Response): Promise { - const studentUsername = req.query.studentUsername as string; + const studentUsername = req.params.studentUsername; const classId = req.params.classId; const accepted = req.body.accepted !== 'false'; // Default = true requireFields({ studentUsername, classId }); From c7155fb438d0a88e5bb252f37ced08158c8a82cd Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Mon, 7 Apr 2025 23:50:27 +0200 Subject: [PATCH 38/43] feat: extra tests op nieuwe func student en teacher --- backend/tests/controllers/students.test.ts | 35 +++++++++++++--------- backend/tests/controllers/teachers.test.ts | 13 ++++++-- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/backend/tests/controllers/students.test.ts b/backend/tests/controllers/students.test.ts index 93f35c48..7bb07f8c 100644 --- a/backend/tests/controllers/students.test.ts +++ b/backend/tests/controllers/students.test.ts @@ -198,15 +198,34 @@ describe('Student controllers', () => { ); }); - it('Create join request', async () => { + it('Create and delete join request', async () => { req = { - params: { username: 'Noordkaap' }, + params: { username: 'TheDoors' }, body: { classId: 'id02' }, }; await createStudentRequestHandler(req as Request, res as Response); expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ request: expect.anything() })); + + req = { + params: { username: 'TheDoors', classId: 'id02' }, + }; + + await deleteClassJoinRequestHandler(req as Request, res as Response); + + expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ request: expect.anything() })); + + await expect(async () => deleteClassJoinRequestHandler(req as Request, res as Response)).rejects.toThrow(NotFoundException); + }); + + it('Create join request student already in class error', async () => { + req = { + params: { username: 'Noordkaap' }, + body: { classId: 'id02' }, + }; + + await expect(async () => createStudentRequestHandler(req as Request, res as Response)).rejects.toThrow(ConflictException); }); it('Create join request duplicate', async () => { @@ -217,16 +236,4 @@ describe('Student controllers', () => { await expect(async () => createStudentRequestHandler(req as Request, res as Response)).rejects.toThrow(ConflictException); }); - - it('Delete join request', async () => { - req = { - params: { username: 'Noordkaap', classId: 'id02' }, - }; - - await deleteClassJoinRequestHandler(req as Request, res as Response); - - expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ request: expect.anything() })); - - await expect(async () => deleteClassJoinRequestHandler(req as Request, res as Response)).rejects.toThrow(NotFoundException); - }); }); diff --git a/backend/tests/controllers/teachers.test.ts b/backend/tests/controllers/teachers.test.ts index bee23987..21533876 100644 --- a/backend/tests/controllers/teachers.test.ts +++ b/backend/tests/controllers/teachers.test.ts @@ -16,6 +16,7 @@ import { BadRequestException } from '../../src/exceptions/bad-request-exception. import { EntityAlreadyExistsException } from '../../src/exceptions/entity-already-exists-exception.js'; import { getStudentRequestsHandler } from '../../src/controllers/students.js'; import { TeacherDTO } from '@dwengo-1/common/interfaces/teacher'; +import {getClassHandler} from "../../src/controllers/classes"; describe('Teacher controllers', () => { let req: Partial; @@ -168,7 +169,6 @@ describe('Teacher controllers', () => { it('Get join requests by class', async () => { req = { - query: { username: 'LimpBizkit' }, params: { classId: 'id02' }, }; @@ -183,8 +183,7 @@ describe('Teacher controllers', () => { it('Update join request status', async () => { req = { - query: { username: 'LimpBizkit', studentUsername: 'PinkFloyd' }, - params: { classId: 'id02' }, + params: { classId: 'id02', studentUsername: 'PinkFloyd'}, body: { accepted: 'true' }, }; @@ -200,5 +199,13 @@ describe('Teacher controllers', () => { const status: boolean = jsonMock.mock.lastCall?.[0].requests[0].status; expect(status).toBeTruthy(); + + req = { + params: { id: 'id02' } + } + + await getClassHandler(req as Request, res as Response); + const students: string[] = jsonMock.mock.lastCall?.[0].class.students; + expect(students).contains("PinkFloyd"); }); }); From 1dce9c65c64cc97c9b8db608cd3a4e33ff4b18e9 Mon Sep 17 00:00:00 2001 From: Lint Action Date: Mon, 7 Apr 2025 22:00:34 +0000 Subject: [PATCH 39/43] style: fix linting issues met Prettier --- backend/src/services/students.ts | 6 +++--- backend/src/services/teachers.ts | 10 +++++----- backend/tests/controllers/teachers.test.ts | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts index a8300db5..5fc3427f 100644 --- a/backend/src/services/students.ts +++ b/backend/src/services/students.ts @@ -23,8 +23,8 @@ import { GroupDTO } from '@dwengo-1/common/interfaces/group'; import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission'; import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question'; import { ClassJoinRequestDTO } from '@dwengo-1/common/interfaces/class-join-request'; -import {BadRequestException} from "../exceptions/bad-request-exception"; -import {ConflictException} from "../exceptions/conflict-exception"; +import { BadRequestException } from '../exceptions/bad-request-exception'; +import { ConflictException } from '../exceptions/conflict-exception'; export async function getAllStudents(full: boolean): Promise { const studentRepository = getStudentRepository(); @@ -138,7 +138,7 @@ export async function createClassJoinRequest(username: string, classId: string): const cls = await fetchClass(classId); if (cls.students.contains(student)) { - throw new ConflictException("Student already in this class"); + throw new ConflictException('Student already in this class'); } const request = mapToStudentRequest(student, cls); diff --git a/backend/src/services/teachers.ts b/backend/src/services/teachers.ts index 4e67d27d..4973ce81 100644 --- a/backend/src/services/teachers.ts +++ b/backend/src/services/teachers.ts @@ -22,15 +22,15 @@ import { Question } from '../entities/questions/question.entity.js'; import { ClassJoinRequestRepository } from '../data/classes/class-join-request-repository.js'; import { Student } from '../entities/users/student.entity.js'; import { NotFoundException } from '../exceptions/not-found-exception.js'; -import {addClassStudent, fetchClass, getClassStudents, getClassStudentsDTO} from './classes.js'; +import { addClassStudent, fetchClass, getClassStudents, getClassStudentsDTO } from './classes.js'; import { TeacherDTO } from '@dwengo-1/common/interfaces/teacher'; import { ClassDTO } from '@dwengo-1/common/interfaces/class'; import { StudentDTO } from '@dwengo-1/common/interfaces/student'; import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question'; import { ClassJoinRequestDTO } from '@dwengo-1/common/interfaces/class-join-request'; import { ClassJoinRequestStatus } from '@dwengo-1/common/util/class-join-request'; -import {BadRequestException} from "../exceptions/bad-request-exception"; -import {ConflictException} from "../exceptions/conflict-exception"; +import { BadRequestException } from '../exceptions/bad-request-exception'; +import { ConflictException } from '../exceptions/conflict-exception'; export async function getAllTeachers(full: boolean): Promise { const teacherRepository: TeacherRepository = getTeacherRepository(); @@ -152,7 +152,7 @@ export async function updateClassJoinRequestStatus(studentUsername: string, clas const cls = await fetchClass(classId); if (cls.students.contains(student)) { - throw new ConflictException("Student already in this class"); + throw new ConflictException('Student already in this class'); } const request: ClassJoinRequest | null = await requestRepo.findByStudentAndClass(student, cls); @@ -163,7 +163,7 @@ export async function updateClassJoinRequestStatus(studentUsername: string, clas request.status = ClassJoinRequestStatus.Declined; - if (accepted){ + if (accepted) { request.status = ClassJoinRequestStatus.Accepted; await addClassStudent(classId, studentUsername); } diff --git a/backend/tests/controllers/teachers.test.ts b/backend/tests/controllers/teachers.test.ts index 21533876..c29ecbb3 100644 --- a/backend/tests/controllers/teachers.test.ts +++ b/backend/tests/controllers/teachers.test.ts @@ -16,7 +16,7 @@ import { BadRequestException } from '../../src/exceptions/bad-request-exception. import { EntityAlreadyExistsException } from '../../src/exceptions/entity-already-exists-exception.js'; import { getStudentRequestsHandler } from '../../src/controllers/students.js'; import { TeacherDTO } from '@dwengo-1/common/interfaces/teacher'; -import {getClassHandler} from "../../src/controllers/classes"; +import { getClassHandler } from '../../src/controllers/classes'; describe('Teacher controllers', () => { let req: Partial; @@ -183,7 +183,7 @@ describe('Teacher controllers', () => { it('Update join request status', async () => { req = { - params: { classId: 'id02', studentUsername: 'PinkFloyd'}, + params: { classId: 'id02', studentUsername: 'PinkFloyd' }, body: { accepted: 'true' }, }; @@ -201,11 +201,11 @@ describe('Teacher controllers', () => { expect(status).toBeTruthy(); req = { - params: { id: 'id02' } - } + params: { id: 'id02' }, + }; await getClassHandler(req as Request, res as Response); const students: string[] = jsonMock.mock.lastCall?.[0].class.students; - expect(students).contains("PinkFloyd"); + expect(students).contains('PinkFloyd'); }); }); From 7155d7d8939184f50eb64c60a7085586cce8cd48 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Wed, 9 Apr 2025 10:42:57 +0200 Subject: [PATCH 40/43] fix: .js toevoegen aan imports --- backend/src/controllers/assignments.ts | 5 ++--- backend/src/controllers/classes.ts | 2 +- backend/src/services/classes.ts | 9 ++++----- backend/src/services/students.ts | 3 +-- backend/src/services/teachers.ts | 5 ++--- 5 files changed, 10 insertions(+), 14 deletions(-) diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index c262057c..296de160 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -8,9 +8,8 @@ import { putAssignment, } from '../services/assignments.js'; import { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment'; -import { requireFields } from './error-helper'; -import { BadRequestException } from '../exceptions/bad-request-exception'; -import { getLogger } from '../logging/initalize.js'; +import { requireFields } from './error-helper.js'; +import { BadRequestException } from '../exceptions/bad-request-exception.js'; import { Assignment } from '../entities/assignments/assignment.entity.js'; import { EntityDTO } from '@mikro-orm/core'; diff --git a/backend/src/controllers/classes.ts b/backend/src/controllers/classes.ts index 6252c714..6f253547 100644 --- a/backend/src/controllers/classes.ts +++ b/backend/src/controllers/classes.ts @@ -14,7 +14,7 @@ import { putClass, } from '../services/classes.js'; import { ClassDTO } from '@dwengo-1/common/interfaces/class'; -import { requireFields } from './error-helper'; +import { requireFields } from './error-helper.js'; import { EntityDTO } from '@mikro-orm/core'; import { Class } from '../entities/classes/class.entity.js'; diff --git a/backend/src/services/classes.ts b/backend/src/services/classes.ts index 4abe18af..1f197b2a 100644 --- a/backend/src/services/classes.ts +++ b/backend/src/services/classes.ts @@ -1,17 +1,16 @@ -import { getClassRepository, getStudentRepository, getTeacherInvitationRepository, getTeacherRepository } from '../data/repositories.js'; +import { getClassRepository, getTeacherInvitationRepository } from '../data/repositories.js'; import { mapToClassDTO } from '../interfaces/class.js'; import { mapToStudentDTO } from '../interfaces/student.js'; import { mapToTeacherInvitationDTO, mapToTeacherInvitationDTOIds } from '../interfaces/teacher-invitation.js'; -import { getLogger } from '../logging/initalize.js'; import { NotFoundException } from '../exceptions/not-found-exception.js'; import { Class } from '../entities/classes/class.entity.js'; import { ClassDTO } from '@dwengo-1/common/interfaces/class'; import { TeacherInvitationDTO } from '@dwengo-1/common/interfaces/teacher-invitation'; import { StudentDTO } from '@dwengo-1/common/interfaces/student'; -import { fetchTeacher } from './teachers'; -import { fetchStudent, getStudent } from './students'; +import { fetchTeacher } from './teachers.js'; +import { fetchStudent } from './students.js'; import { TeacherDTO } from '@dwengo-1/common/interfaces/teacher'; -import { mapToTeacherDTO } from '../interfaces/teacher'; +import { mapToTeacherDTO } from '../interfaces/teacher.js'; import { EntityDTO } from '@mikro-orm/core'; import { putObject } from './service-helper.js'; diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts index 5fc3427f..6cfbbd5b 100644 --- a/backend/src/services/students.ts +++ b/backend/src/services/students.ts @@ -23,8 +23,7 @@ import { GroupDTO } from '@dwengo-1/common/interfaces/group'; import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission'; import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question'; import { ClassJoinRequestDTO } from '@dwengo-1/common/interfaces/class-join-request'; -import { BadRequestException } from '../exceptions/bad-request-exception'; -import { ConflictException } from '../exceptions/conflict-exception'; +import { ConflictException } from '../exceptions/conflict-exception.js'; export async function getAllStudents(full: boolean): Promise { const studentRepository = getStudentRepository(); diff --git a/backend/src/services/teachers.ts b/backend/src/services/teachers.ts index 4973ce81..e6596f9e 100644 --- a/backend/src/services/teachers.ts +++ b/backend/src/services/teachers.ts @@ -22,15 +22,14 @@ import { Question } from '../entities/questions/question.entity.js'; import { ClassJoinRequestRepository } from '../data/classes/class-join-request-repository.js'; import { Student } from '../entities/users/student.entity.js'; import { NotFoundException } from '../exceptions/not-found-exception.js'; -import { addClassStudent, fetchClass, getClassStudents, getClassStudentsDTO } from './classes.js'; +import { addClassStudent, fetchClass, getClassStudentsDTO } from './classes.js'; import { TeacherDTO } from '@dwengo-1/common/interfaces/teacher'; import { ClassDTO } from '@dwengo-1/common/interfaces/class'; import { StudentDTO } from '@dwengo-1/common/interfaces/student'; import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question'; import { ClassJoinRequestDTO } from '@dwengo-1/common/interfaces/class-join-request'; import { ClassJoinRequestStatus } from '@dwengo-1/common/util/class-join-request'; -import { BadRequestException } from '../exceptions/bad-request-exception'; -import { ConflictException } from '../exceptions/conflict-exception'; +import { ConflictException } from '../exceptions/conflict-exception.js'; export async function getAllTeachers(full: boolean): Promise { const teacherRepository: TeacherRepository = getTeacherRepository(); From 4928c08f62d081755974fe9dc2261564c37f10c8 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Wed, 9 Apr 2025 10:49:14 +0200 Subject: [PATCH 41/43] refactor(backend): Linting --- backend/src/controllers/assignments.ts | 4 ++-- backend/src/controllers/groups.ts | 3 +-- backend/src/controllers/submissions.ts | 1 - backend/src/interfaces/assignment.ts | 1 - backend/src/services/assignments.ts | 8 +++----- backend/src/services/groups.ts | 21 ++++++--------------- backend/src/services/submissions.ts | 3 +-- 7 files changed, 13 insertions(+), 28 deletions(-) diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index 296de160..2ecb35cb 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -66,7 +66,7 @@ export async function putAssignmentHandler(req: Request, res: Response): Promise res.json({ assignment }); } -export async function deleteAssignmentHandler(req: Request, res: Response): Promise { +export async function deleteAssignmentHandler(req: Request, _res: Response): Promise { const id = Number(req.params.id); const classid = req.params.classid; requireFields({ id, classid }); @@ -75,7 +75,7 @@ export async function deleteAssignmentHandler(req: Request, res: Response): Prom throw new BadRequestException('Assignment id should be a number'); } - const assignment = await deleteAssignment(classid, id); + await deleteAssignment(classid, id); } export async function getAssignmentsSubmissionsHandler(req: Request, res: Response): Promise { diff --git a/backend/src/controllers/groups.ts b/backend/src/controllers/groups.ts index 85b0809c..ec177dcc 100644 --- a/backend/src/controllers/groups.ts +++ b/backend/src/controllers/groups.ts @@ -3,11 +3,10 @@ import { createGroup, deleteGroup, getAllGroups, getGroup, getGroupSubmissions, import { GroupDTO } from '@dwengo-1/common/interfaces/group'; import { requireFields } from './error-helper.js'; import { BadRequestException } from '../exceptions/bad-request-exception.js'; -import { getLogger } from '../logging/initalize.js'; import { EntityDTO } from '@mikro-orm/core'; import { Group } from '../entities/assignments/group.entity.js'; -function checkGroupFields(classId: any, assignmentId: any, groupId: any) { +function checkGroupFields(classId: string, assignmentId: number, groupId: number): void { requireFields({ classId, assignmentId, groupId }); if (isNaN(assignmentId)) { diff --git a/backend/src/controllers/submissions.ts b/backend/src/controllers/submissions.ts index a742685f..fb681656 100644 --- a/backend/src/controllers/submissions.ts +++ b/backend/src/controllers/submissions.ts @@ -1,7 +1,6 @@ import { Request, Response } from 'express'; import { createSubmission, deleteSubmission, getAllSubmissions, getSubmission } from '../services/submissions.js'; import { BadRequestException } from '../exceptions/bad-request-exception.js'; -import { NotFoundException } from '../exceptions/not-found-exception.js'; import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; import { Language, languageMap } from '@dwengo-1/common/util/language'; import { SubmissionDTO } from '@dwengo-1/common/interfaces/submission'; diff --git a/backend/src/interfaces/assignment.ts b/backend/src/interfaces/assignment.ts index 2e591797..7abb3d3c 100644 --- a/backend/src/interfaces/assignment.ts +++ b/backend/src/interfaces/assignment.ts @@ -4,7 +4,6 @@ import { Assignment } from '../entities/assignments/assignment.entity.js'; import { Class } from '../entities/classes/class.entity.js'; import { getLogger } from '../logging/initalize.js'; import { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment'; -import { mapToGroupDTO } from './group.js'; export function mapToAssignmentDTOId(assignment: Assignment): AssignmentDTO { return { diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts index 55f4947c..1b8fc9f1 100644 --- a/backend/src/services/assignments.ts +++ b/backend/src/services/assignments.ts @@ -14,9 +14,7 @@ import { mapToSubmissionDTO, mapToSubmissionDTOId } from '../interfaces/submissi import { fetchClass } from './classes.js'; import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question'; import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission'; -import { getLogger } from '../logging/initalize.js'; -import { EntityData, EntityDTO, FromEntityType } from '@mikro-orm/core'; -import { DwengoEntityRepository } from '../data/dwengo-entity-repository.js'; +import { EntityDTO } from '@mikro-orm/core'; import { putObject } from './service-helper.js'; export async function fetchAssignment(classid: string, assignmentNumber: number): Promise { @@ -24,7 +22,7 @@ export async function fetchAssignment(classid: string, assignmentNumber: number) const cls = await classRepository.findById(classid); if (!cls) { - throw new NotFoundException("Could not find assignment's class"); + throw new NotFoundException('Could not find assignment\'s class'); } const assignmentRepository = getAssignmentRepository(); @@ -88,7 +86,7 @@ export async function deleteAssignment(classid: string, id: number): Promise { const assignment = await fetchAssignment(classid, assignmentNumber); diff --git a/backend/src/services/groups.ts b/backend/src/services/groups.ts index 9b9903ed..f52fff29 100644 --- a/backend/src/services/groups.ts +++ b/backend/src/services/groups.ts @@ -1,21 +1,12 @@ -import { assign, EntityDTO } from '@mikro-orm/core'; -import { - getAssignmentRepository, - getClassRepository, - getGroupRepository, - getQuestionRepository, - getStudentRepository, - getSubmissionRepository, -} from '../data/repositories.js'; +import { EntityDTO } from '@mikro-orm/core'; +import { getGroupRepository, getStudentRepository, getSubmissionRepository } from '../data/repositories.js'; import { Group } from '../entities/assignments/group.entity.js'; import { mapToGroupDTO, mapToGroupDTOId } from '../interfaces/group.js'; import { mapToSubmissionDTO, mapToSubmissionDTOId } from '../interfaces/submission.js'; import { GroupDTO } from '@dwengo-1/common/interfaces/group'; import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission'; -import { getLogger } from '../logging/initalize.js'; import { fetchAssignment } from './assignments.js'; import { NotFoundException } from '../exceptions/not-found-exception.js'; -import { fetchClass } from './classes.js'; import { putObject } from './service-helper.js'; export async function fetchGroup(classId: string, assignmentNumber: number, groupNumber: number): Promise { @@ -40,7 +31,7 @@ export async function putGroup( classId: string, assignmentNumber: number, groupNumber: number, - groupData: Partial> + groupData: Partial>, ): Promise { const group = await fetchGroup(classId, assignmentNumber, groupNumber); @@ -59,7 +50,7 @@ export async function deleteGroup(classId: string, assignmentNumber: number, gro return mapToGroupDTO(group); } -export async function getExistingGroupFromGroupDTO(groupData: GroupDTO) { +export async function getExistingGroupFromGroupDTO(groupData: GroupDTO): Promise { const classId = typeof groupData.class === 'string' ? groupData.class : groupData.class.id; const assignmentNumber = typeof groupData.assignment === 'number' ? groupData.assignment : groupData.assignment.id; const groupNumber = groupData.groupNumber; @@ -72,7 +63,7 @@ export async function createGroup(groupData: GroupDTO, classid: string, assignme const memberUsernames = (groupData.members as string[]) || []; const members = (await Promise.all([...memberUsernames].map(async (id) => studentRepository.findByUsername(id)))).filter( - (student) => student !== null + (student) => student !== null, ); const assignment = await fetchAssignment(classid, assignmentNumber); @@ -104,7 +95,7 @@ export async function getGroupSubmissions( classId: string, assignmentNumber: number, groupNumber: number, - full: boolean + full: boolean, ): Promise { const group = await fetchGroup(classId, assignmentNumber, groupNumber); diff --git a/backend/src/services/submissions.ts b/backend/src/services/submissions.ts index 25f6e7f0..c7daff74 100644 --- a/backend/src/services/submissions.ts +++ b/backend/src/services/submissions.ts @@ -3,9 +3,8 @@ import { LearningObjectIdentifier } from '../entities/content/learning-object-id import { NotFoundException } from '../exceptions/not-found-exception.js'; import { mapToSubmission, mapToSubmissionDTO } from '../interfaces/submission.js'; import { SubmissionDTO } from '@dwengo-1/common/interfaces/submission'; -import { Language } from '@dwengo-1/common/util/language'; import { fetchStudent } from './students.js'; -import { fetchGroup, getExistingGroupFromGroupDTO } from './groups.js'; +import { getExistingGroupFromGroupDTO } from './groups.js'; import { Submission } from '../entities/assignments/submission.entity.js'; export async function fetchSubmission(loId: LearningObjectIdentifier, submissionNumber: number): Promise { From 6a86539cc9728777f9ae49ba421b2925fa67474f Mon Sep 17 00:00:00 2001 From: Lint Action Date: Wed, 9 Apr 2025 08:50:43 +0000 Subject: [PATCH 42/43] style: fix linting issues met Prettier --- backend/src/services/assignments.ts | 4 ++-- backend/src/services/groups.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts index 1b8fc9f1..5fd8f67f 100644 --- a/backend/src/services/assignments.ts +++ b/backend/src/services/assignments.ts @@ -22,7 +22,7 @@ export async function fetchAssignment(classid: string, assignmentNumber: number) const cls = await classRepository.findById(classid); if (!cls) { - throw new NotFoundException('Could not find assignment\'s class'); + throw new NotFoundException("Could not find assignment's class"); } const assignmentRepository = getAssignmentRepository(); @@ -86,7 +86,7 @@ export async function deleteAssignment(classid: string, id: number): Promise { const assignment = await fetchAssignment(classid, assignmentNumber); diff --git a/backend/src/services/groups.ts b/backend/src/services/groups.ts index f52fff29..95b1b7d4 100644 --- a/backend/src/services/groups.ts +++ b/backend/src/services/groups.ts @@ -31,7 +31,7 @@ export async function putGroup( classId: string, assignmentNumber: number, groupNumber: number, - groupData: Partial>, + groupData: Partial> ): Promise { const group = await fetchGroup(classId, assignmentNumber, groupNumber); @@ -63,7 +63,7 @@ export async function createGroup(groupData: GroupDTO, classid: string, assignme const memberUsernames = (groupData.members as string[]) || []; const members = (await Promise.all([...memberUsernames].map(async (id) => studentRepository.findByUsername(id)))).filter( - (student) => student !== null, + (student) => student !== null ); const assignment = await fetchAssignment(classid, assignmentNumber); @@ -95,7 +95,7 @@ export async function getGroupSubmissions( classId: string, assignmentNumber: number, groupNumber: number, - full: boolean, + full: boolean ): Promise { const group = await fetchGroup(classId, assignmentNumber, groupNumber); From fbc77b9ad3722181f10997d20866ad19b993d022 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Wed, 9 Apr 2025 12:19:41 +0200 Subject: [PATCH 43/43] fix: tests --- backend/tests/controllers/students.test.ts | 2 +- backend/tests/controllers/teachers.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/tests/controllers/students.test.ts b/backend/tests/controllers/students.test.ts index b0611ee8..aca29de1 100644 --- a/backend/tests/controllers/students.test.ts +++ b/backend/tests/controllers/students.test.ts @@ -222,7 +222,7 @@ describe('Student controllers', () => { it('Create join request student already in class error', async () => { req = { params: { username: 'Noordkaap' }, - body: { classId: 'id02' }, + body: { classId: '34d484a1-295f-4e9f-bfdc-3e7a23d86a89' }, }; await expect(async () => createStudentRequestHandler(req as Request, res as Response)).rejects.toThrow(ConflictException); diff --git a/backend/tests/controllers/teachers.test.ts b/backend/tests/controllers/teachers.test.ts index f687d797..a73a79a5 100644 --- a/backend/tests/controllers/teachers.test.ts +++ b/backend/tests/controllers/teachers.test.ts @@ -183,7 +183,7 @@ describe('Teacher controllers', () => { it('Update join request status', async () => { req = { - params: { classId: '34d484a1-295f-4e9f-bfdc-3e7a23d86a89' }, + params: { classId: '34d484a1-295f-4e9f-bfdc-3e7a23d86a89', studentUsername: 'PinkFloyd' }, body: { accepted: 'true' }, };