Merge branch 'dev' into feat/endpoints-beschermen-met-authenticatie-#105
This commit is contained in:
		
						commit
						6edb5f144d
					
				
					 46 changed files with 10216 additions and 1054 deletions
				
			
		|  | @ -4,6 +4,7 @@ import { | |||
|     deleteAssignment, | ||||
|     getAllAssignments, | ||||
|     getAssignment, | ||||
|     getAssignmentsQuestions, | ||||
|     getAssignmentsSubmissions, | ||||
|     putAssignment, | ||||
| } from '../services/assignments.js'; | ||||
|  | @ -13,6 +14,19 @@ import { BadRequestException } from '../exceptions/bad-request-exception.js'; | |||
| import { Assignment } from '../entities/assignments/assignment.entity.js'; | ||||
| import { EntityDTO } from '@mikro-orm/core'; | ||||
| 
 | ||||
| function getAssignmentParams(req: Request): { classid: string; assignmentNumber: number; full: boolean } { | ||||
|     const classid = req.params.classid; | ||||
|     const assignmentNumber = Number(req.params.id); | ||||
|     const full = req.query.full === 'true'; | ||||
|     requireFields({ assignmentNumber, classid }); | ||||
| 
 | ||||
|     if (isNaN(assignmentNumber)) { | ||||
|         throw new BadRequestException('Assignment id should be a number'); | ||||
|     } | ||||
| 
 | ||||
|     return { classid, assignmentNumber, full }; | ||||
| } | ||||
| 
 | ||||
| export async function getAllAssignmentsHandler(req: Request, res: Response): Promise<void> { | ||||
|     const classId = req.params.classid; | ||||
|     const full = req.query.full === 'true'; | ||||
|  | @ -38,57 +52,42 @@ export async function createAssignmentHandler(req: Request, res: Response): Prom | |||
| } | ||||
| 
 | ||||
| export async function getAssignmentHandler(req: Request, res: Response): Promise<void> { | ||||
|     const id = Number(req.params.id); | ||||
|     const classid = req.params.classid; | ||||
|     requireFields({ id, classid }); | ||||
|     const { classid, assignmentNumber } = getAssignmentParams(req); | ||||
| 
 | ||||
|     if (isNaN(id)) { | ||||
|         throw new BadRequestException('Assignment id should be a number'); | ||||
|     } | ||||
| 
 | ||||
|     const assignment = await getAssignment(classid, id); | ||||
|     const assignment = await getAssignment(classid, assignmentNumber); | ||||
| 
 | ||||
|     res.json({ assignment }); | ||||
| } | ||||
| 
 | ||||
| export async function putAssignmentHandler(req: Request, res: Response): Promise<void> { | ||||
|     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 { classid, assignmentNumber } = getAssignmentParams(req); | ||||
| 
 | ||||
|     const assignmentData = req.body as Partial<EntityDTO<Assignment>>; | ||||
|     const assignment = await putAssignment(classid, id, assignmentData); | ||||
|     const assignment = await putAssignment(classid, assignmentNumber, assignmentData); | ||||
| 
 | ||||
|     res.json({ assignment }); | ||||
| } | ||||
| 
 | ||||
| export async function deleteAssignmentHandler(req: Request, _res: Response): Promise<void> { | ||||
|     const id = Number(req.params.id); | ||||
|     const classid = req.params.classid; | ||||
|     requireFields({ id, classid }); | ||||
| export async function deleteAssignmentHandler(req: Request, res: Response): Promise<void> { | ||||
|     const { classid, assignmentNumber } = getAssignmentParams(req); | ||||
| 
 | ||||
|     if (isNaN(id)) { | ||||
|         throw new BadRequestException('Assignment id should be a number'); | ||||
|     } | ||||
|     const assignment = await deleteAssignment(classid, assignmentNumber); | ||||
| 
 | ||||
|     await deleteAssignment(classid, id); | ||||
|     res.json({ assignment }); | ||||
| } | ||||
| 
 | ||||
| export async function getAssignmentsSubmissionsHandler(req: Request, res: Response): Promise<void> { | ||||
|     const classid = req.params.classid; | ||||
|     const assignmentNumber = Number(req.params.id); | ||||
|     const full = req.query.full === 'true'; | ||||
|     requireFields({ assignmentNumber, classid }); | ||||
| 
 | ||||
|     if (isNaN(assignmentNumber)) { | ||||
|         throw new BadRequestException('Assignment id should be a number'); | ||||
|     } | ||||
|     const { classid, assignmentNumber, full } = getAssignmentParams(req); | ||||
| 
 | ||||
|     const submissions = await getAssignmentsSubmissions(classid, assignmentNumber, full); | ||||
| 
 | ||||
|     res.json({ submissions }); | ||||
| } | ||||
| 
 | ||||
| export async function getAssignmentQuestionsHandler(req: Request, res: Response): Promise<void> { | ||||
|     const { classid, assignmentNumber, full } = getAssignmentParams(req); | ||||
| 
 | ||||
|     const questions = await getAssignmentsQuestions(classid, assignmentNumber, full); | ||||
| 
 | ||||
|     res.json({ questions }); | ||||
| } | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| import { Request, Response } from 'express'; | ||||
| import { createGroup, deleteGroup, getAllGroups, getGroup, getGroupSubmissions, putGroup } from '../services/groups.js'; | ||||
| import { createGroup, deleteGroup, getAllGroups, getGroup, getGroupQuestions, 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'; | ||||
|  | @ -84,7 +84,7 @@ export async function createGroupHandler(req: Request, res: Response): Promise<v | |||
|     res.status(201).json({ group }); | ||||
| } | ||||
| 
 | ||||
| export async function getGroupSubmissionsHandler(req: Request, res: Response): Promise<void> { | ||||
| function getGroupParams(req: Request): { classId: string; assignmentId: number; groupId: number; full: boolean } { | ||||
|     const classId = req.params.classid; | ||||
|     const assignmentId = Number(req.params.assignmentid); | ||||
|     const groupId = Number(req.params.groupid); | ||||
|  | @ -100,7 +100,21 @@ export async function getGroupSubmissionsHandler(req: Request, res: Response): P | |||
|         throw new BadRequestException('Group id must be a number'); | ||||
|     } | ||||
| 
 | ||||
|     return { classId, assignmentId, groupId, full }; | ||||
| } | ||||
| 
 | ||||
| export async function getGroupSubmissionsHandler(req: Request, res: Response): Promise<void> { | ||||
|     const { classId, assignmentId, groupId, full } = getGroupParams(req); | ||||
| 
 | ||||
|     const submissions = await getGroupSubmissions(classId, assignmentId, groupId, full); | ||||
| 
 | ||||
|     res.json({ submissions }); | ||||
| } | ||||
| 
 | ||||
| export async function getGroupQuestionsHandler(req: Request, res: Response): Promise<void> { | ||||
|     const { classId, assignmentId, groupId, full } = getGroupParams(req); | ||||
| 
 | ||||
|     const questions = await getGroupQuestions(classId, assignmentId, groupId, full); | ||||
| 
 | ||||
|     res.json({ questions }); | ||||
| } | ||||
|  |  | |||
|  | @ -62,9 +62,7 @@ export class QuestionRepository extends DwengoEntityRepository<Question> { | |||
| 
 | ||||
|     public async findAllByAssignment(assignment: Assignment): Promise<Question[]> { | ||||
|         return this.find({ | ||||
|             inGroup: { | ||||
|                 $contained: assignment.groups, | ||||
|             }, | ||||
|             inGroup: assignment.groups.getItems(), | ||||
|             learningObjectHruid: assignment.learningPathHruid, | ||||
|             learningObjectLanguage: assignment.learningPathLanguage, | ||||
|         }); | ||||
|  | @ -77,6 +75,13 @@ export class QuestionRepository extends DwengoEntityRepository<Question> { | |||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     public async findAllByGroup(inGroup: Group): Promise<Question[]> { | ||||
|         return this.findAll({ | ||||
|             where: { inGroup }, | ||||
|             orderBy: { timestamp: 'DESC' }, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Looks up all questions for the given learning object which were asked as part of the given assignment. | ||||
|      * When forStudentUsername is set, only the questions within the given user's group are shown. | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { Collection, Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; | ||||
| import { Cascade, Collection, Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; | ||||
| import { Class } from '../classes/class.entity.js'; | ||||
| import { Group } from './group.entity.js'; | ||||
| import { Language } from '@dwengo-1/common/util/language'; | ||||
|  | @ -34,6 +34,7 @@ export class Assignment { | |||
|     @OneToMany({ | ||||
|         entity: () => Group, | ||||
|         mappedBy: 'assignment', | ||||
|         cascade: [Cascade.ALL], | ||||
|     }) | ||||
|     groups: Collection<Group> = new Collection<Group>(this); | ||||
| } | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| import { Student } from '../users/student.entity.js'; | ||||
| import { Group } from './group.entity.js'; | ||||
| import { Entity, Enum, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'; | ||||
| import { Entity, Enum, ManyToOne, PrimaryKey, Property, Cascade } from '@mikro-orm/core'; | ||||
| import { SubmissionRepository } from '../../data/assignments/submission-repository.js'; | ||||
| import { Language } from '@dwengo-1/common/util/language'; | ||||
| 
 | ||||
|  | @ -21,8 +21,8 @@ export class Submission { | |||
|     @PrimaryKey({ type: 'numeric', autoincrement: false }) | ||||
|     learningObjectVersion = 1; | ||||
| 
 | ||||
|     @ManyToOne({ | ||||
|         entity: () => Group, | ||||
|     @ManyToOne(() => Group, { | ||||
|         cascade: [Cascade.REMOVE], | ||||
|     }) | ||||
|     onBehalfOf!: Group; | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ import { | |||
|     deleteAssignmentHandler, | ||||
|     getAllAssignmentsHandler, | ||||
|     getAssignmentHandler, | ||||
|     getAssignmentQuestionsHandler, | ||||
|     getAssignmentsSubmissionsHandler, | ||||
|     putAssignmentHandler, | ||||
| } from '../controllers/assignments.js'; | ||||
|  | @ -26,11 +27,7 @@ router.delete('/:id', teachersOnly, onlyAllowIfHasAccessToAssignment, deleteAssi | |||
| 
 | ||||
| router.get('/:id/submissions', teachersOnly, onlyAllowIfHasAccessToAssignment, getAssignmentsSubmissionsHandler); | ||||
| 
 | ||||
| router.get('/:id/questions', teachersOnly, onlyAllowIfHasAccessToAssignment, (_req, res) => { | ||||
|     res.json({ | ||||
|         questions: ['0'], | ||||
|     }); | ||||
| }); | ||||
| router.get('/:id/questions', teachersOnly, onlyAllowIfHasAccessToAssignment, getAssignmentQuestionsHandler); | ||||
| 
 | ||||
| router.use('/:assignmentid/groups', groupRouter); | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ import { | |||
|     deleteGroupHandler, | ||||
|     getAllGroupsHandler, | ||||
|     getGroupHandler, | ||||
|     getGroupQuestionsHandler, | ||||
|     getGroupSubmissionsHandler, | ||||
|     putGroupHandler, | ||||
| } from '../controllers/groups.js'; | ||||
|  | @ -32,4 +33,6 @@ router.get('/:groupid/questions', onlyAllowIfHasAccessToGroup, (_req, res) => { | |||
|     }); | ||||
| }); | ||||
| 
 | ||||
| router.get('/:groupid/questions', getGroupQuestionsHandler); | ||||
| 
 | ||||
| export default router; | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| import { EntityDTO } from '@mikro-orm/core'; | ||||
| import { getGroupRepository, getSubmissionRepository } from '../data/repositories.js'; | ||||
| import { getGroupRepository, getQuestionRepository, 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'; | ||||
|  | @ -12,6 +12,8 @@ import { fetchClass } from './classes.js'; | |||
| import { BadRequestException } from '../exceptions/bad-request-exception.js'; | ||||
| import { Student } from '../entities/users/student.entity.js'; | ||||
| import { Class } from '../entities/classes/class.entity.js'; | ||||
| import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question'; | ||||
| import { mapToQuestionDTO, mapToQuestionDTOId } from '../interfaces/question.js'; | ||||
| 
 | ||||
| async function assertMembersInClass(members: Student[], cls: Class): Promise<void> { | ||||
|     if (!members.every((student) => cls.students.contains(student))) { | ||||
|  | @ -130,3 +132,21 @@ export async function getGroupSubmissions( | |||
| 
 | ||||
|     return submissions.map(mapToSubmissionDTOId); | ||||
| } | ||||
| 
 | ||||
| export async function getGroupQuestions( | ||||
|     classId: string, | ||||
|     assignmentNumber: number, | ||||
|     groupNumber: number, | ||||
|     full: boolean | ||||
| ): Promise<QuestionDTO[] | QuestionId[]> { | ||||
|     const group = await fetchGroup(classId, assignmentNumber, groupNumber); | ||||
| 
 | ||||
|     const questionRepository = getQuestionRepository(); | ||||
|     const questions = await questionRepository.findAllByGroup(group); | ||||
| 
 | ||||
|     if (full) { | ||||
|         return questions.map(mapToQuestionDTO); | ||||
|     } | ||||
| 
 | ||||
|     return questions.map(mapToQuestionDTOId); | ||||
| } | ||||
|  |  | |||
		Reference in a new issue
	
	 Adriaan J.
						Adriaan J.