feat: student send join req
This commit is contained in:
		
							parent
							
								
									70d4c80093
								
							
						
					
					
						commit
						3093a6c131
					
				
					 12 changed files with 347 additions and 169 deletions
				
			
		
							
								
								
									
										18
									
								
								backend/src/controllers/error-helper.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								backend/src/controllers/error-helper.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | ||||||
|  | import { BadRequestException } from '../exceptions.js'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Checks for the presence of required fields and throws a BadRequestException | ||||||
|  |  * if any are missing. | ||||||
|  |  * | ||||||
|  |  * @param fields - An object with key-value pairs to validate. | ||||||
|  |  */ | ||||||
|  | export function requireFields(fields: Record<string, unknown>): void { | ||||||
|  |     const missing = Object.entries(fields) | ||||||
|  |         .filter(([_, value]) => value === undefined || value === null || value === '') | ||||||
|  |         .map(([key]) => key); | ||||||
|  | 
 | ||||||
|  |     if (missing.length > 0) { | ||||||
|  |         const message = `Missing required field${missing.length > 1 ? 's' : ''}: ${missing.join(', ')}`; | ||||||
|  |         throw new BadRequestException(message); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,17 +1,19 @@ | ||||||
| import { Request, Response } from 'express'; | import { Request, Response } from 'express'; | ||||||
| import { | import { | ||||||
|     createStudent, |     createClassJoinRequest, | ||||||
|  |     createStudent, deleteClassJoinRequest, | ||||||
|     deleteStudent, |     deleteStudent, | ||||||
|     getAllStudents, |     getAllStudents, getJoinRequestsByStudent, | ||||||
|     getStudent, |     getStudent, | ||||||
|     getStudentAssignments, |     getStudentAssignments, | ||||||
|     getStudentClasses, |     getStudentClasses, | ||||||
|     getStudentGroups, |     getStudentGroups, | ||||||
|     getStudentQuestions, |     getStudentQuestions, | ||||||
|     getStudentSubmissions, |     getStudentSubmissions, updateClassJoinRequestStatus, | ||||||
| } from '../services/students.js'; | } from '../services/students.js'; | ||||||
| import { MISSING_FIELDS_ERROR, MISSING_USERNAME_ERROR, NAME_NOT_FOUND_ERROR } from './users.js'; |  | ||||||
| import { StudentDTO } from '../interfaces/student.js'; | import { StudentDTO } from '../interfaces/student.js'; | ||||||
|  | import {BadRequestException} from "../exceptions"; | ||||||
|  | import {requireFields} from "./error-helper"; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| export async function getAllStudentsHandler(req: Request, res: Response): Promise<void> { | export async function getAllStudentsHandler(req: Request, res: Response): Promise<void> { | ||||||
|  | @ -19,69 +21,42 @@ export async function getAllStudentsHandler(req: Request, res: Response): Promis | ||||||
| 
 | 
 | ||||||
|     const students: StudentDTO[] | string[] = await getAllStudents(full); |     const students: StudentDTO[] | string[] = await getAllStudents(full); | ||||||
| 
 | 
 | ||||||
|     if (!students) { |  | ||||||
|         res.status(404).json({ error: `Students not found.` }); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     res.json({ students }); |     res.json({ students }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getStudentHandler(req: Request, res: Response): Promise<void> { | export async function getStudentHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const username = req.params.username; |     const username = req.params.username; | ||||||
|  |     requireFields({ username }); | ||||||
| 
 | 
 | ||||||
|     if (!username) { |     const student = await getStudent(username); | ||||||
|         res.status(400).json(MISSING_USERNAME_ERROR); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const user = await getStudent(username); |     res.status(201).json({ student }); | ||||||
| 
 |  | ||||||
|     if (!user) { |  | ||||||
|         res.status(404).json(NAME_NOT_FOUND_ERROR(username)); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     res.status(201).json(user); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function createStudentHandler(req: Request, res: Response) { | export async function createStudentHandler(req: Request, res: Response) { | ||||||
|  |     const username = req.body.username; | ||||||
|  |     const firstName = req.body.firstName; | ||||||
|  |     const lastName = req.body.lastName; | ||||||
|  |     requireFields({ username, firstName, lastName }); | ||||||
|  | 
 | ||||||
|     const userData = req.body as StudentDTO; |     const userData = req.body as StudentDTO; | ||||||
| 
 | 
 | ||||||
|     if (!userData.username || !userData.firstName || !userData.lastName) { |     const student = await createStudent(userData); | ||||||
|         res.status(400).json(MISSING_FIELDS_ERROR); |     res.status(201).json({ student }); | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const newUser = await createStudent(userData); |  | ||||||
|     res.status(201).json(newUser); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function deleteStudentHandler(req: Request, res: Response) { | export async function deleteStudentHandler(req: Request, res: Response) { | ||||||
|     const username = req.params.username; |     const username = req.params.username; | ||||||
|  |     requireFields({ username }); | ||||||
| 
 | 
 | ||||||
|     if (!username) { |     const student = await deleteStudent(username); | ||||||
|         res.status(400).json(MISSING_USERNAME_ERROR); |     res.status(200).json({ student }); | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const deletedUser = await deleteStudent(username); |  | ||||||
|     if (!deletedUser) { |  | ||||||
|         res.status(404).json(NAME_NOT_FOUND_ERROR(username)); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     res.status(200).json(deletedUser); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getStudentClassesHandler(req: Request, res: Response): Promise<void> { | export async function getStudentClassesHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const full = req.query.full === 'true'; |     const full = req.query.full === 'true'; | ||||||
|     const username = req.params.username; |     const username = req.params.username; | ||||||
| 
 |     requireFields({ username }); | ||||||
|     if (!username) { |  | ||||||
|         res.status(400).json(MISSING_USERNAME_ERROR); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const classes = await getStudentClasses(username, full); |     const classes = await getStudentClasses(username, full); | ||||||
| 
 | 
 | ||||||
|  | @ -97,11 +72,7 @@ export async function getStudentClassesHandler(req: Request, res: Response): Pro | ||||||
| export async function getStudentAssignmentsHandler(req: Request, res: Response): Promise<void> { | export async function getStudentAssignmentsHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const full = req.query.full === 'true'; |     const full = req.query.full === 'true'; | ||||||
|     const username = req.params.username; |     const username = req.params.username; | ||||||
| 
 |     requireFields({ username }); | ||||||
|     if (!username) { |  | ||||||
|         res.status(400).json(MISSING_USERNAME_ERROR); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const assignments = getStudentAssignments(username, full); |     const assignments = getStudentAssignments(username, full); | ||||||
| 
 | 
 | ||||||
|  | @ -113,11 +84,7 @@ export async function getStudentAssignmentsHandler(req: Request, res: Response): | ||||||
| export async function getStudentGroupsHandler(req: Request, res: Response): Promise<void> { | export async function getStudentGroupsHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const full = req.query.full === 'true'; |     const full = req.query.full === 'true'; | ||||||
|     const username = req.params.username; |     const username = req.params.username; | ||||||
| 
 |     requireFields({ username }); | ||||||
|     if (!username) { |  | ||||||
|         res.status(400).json(MISSING_USERNAME_ERROR); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const groups = await getStudentGroups(username, full); |     const groups = await getStudentGroups(username, full); | ||||||
| 
 | 
 | ||||||
|  | @ -128,11 +95,7 @@ export async function getStudentGroupsHandler(req: Request, res: Response): Prom | ||||||
| 
 | 
 | ||||||
| export async function getStudentSubmissionsHandler(req: Request, res: Response): Promise<void> { | export async function getStudentSubmissionsHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const username = req.params.username; |     const username = req.params.username; | ||||||
| 
 |     requireFields({ username }); | ||||||
|     if (!username) { |  | ||||||
|         res.status(400).json(MISSING_USERNAME_ERROR); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const submissions = await getStudentSubmissions(username); |     const submissions = await getStudentSubmissions(username); | ||||||
| 
 | 
 | ||||||
|  | @ -144,11 +107,7 @@ export async function getStudentSubmissionsHandler(req: Request, res: Response): | ||||||
| export async function getStudentQuestionsHandler(req: Request, res: Response): Promise<void> { | export async function getStudentQuestionsHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const full = req.query.full === 'true'; |     const full = req.query.full === 'true'; | ||||||
|     const username = req.params.username; |     const username = req.params.username; | ||||||
| 
 |     requireFields({ username }); | ||||||
|     if (!username) { |  | ||||||
|         res.status(400).json(MISSING_USERNAME_ERROR); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const questions = await getStudentQuestions(username, full); |     const questions = await getStudentQuestions(username, full); | ||||||
| 
 | 
 | ||||||
|  | @ -156,3 +115,41 @@ export async function getStudentQuestionsHandler(req: Request, res: Response): P | ||||||
|         questions, |         questions, | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export async function createStudentRequestHandler(req: Request, res: Response): Promise<void> { | ||||||
|  |     const username = req.params.username; | ||||||
|  |     const classId = req.params.classId; | ||||||
|  |     requireFields({ username, classId }); | ||||||
|  | 
 | ||||||
|  |     await createClassJoinRequest(username, classId); | ||||||
|  |     res.status(201).send(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function getStudentRequestHandler(req: Request, res: Response): Promise<void> { | ||||||
|  |     const username = req.params.username; | ||||||
|  |     requireFields({ username }); | ||||||
|  | 
 | ||||||
|  |     const requests = await getJoinRequestsByStudent(username); | ||||||
|  |     res.status(201).json({ requests }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function updateClassJoinRequestHandler(req: Request, res: Response) { | ||||||
|  |     const username = req.query.username as string; | ||||||
|  |     const classId = req.params.classId; | ||||||
|  |     const accepted = req.query.accepted !== 'false'; // default = true
 | ||||||
|  |     requireFields({ username, classId }); | ||||||
|  | 
 | ||||||
|  |     const result = await updateClassJoinRequestStatus(username, classId, accepted); | ||||||
|  |     res.status(200).json(result); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function deleteClassJoinRequestHandler(req: Request, res: Response) { | ||||||
|  |     const username = req.query.username as string; | ||||||
|  |     const classId = req.params.classId; | ||||||
|  |     requireFields({ username, classId }); | ||||||
|  | 
 | ||||||
|  |     await deleteClassJoinRequest(username, classId); | ||||||
|  |     res.status(204).send(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | @ -1,10 +1,18 @@ | ||||||
| import { Request, Response } from 'express'; | import { Request, Response } from 'express'; | ||||||
| import { createTeacher, deleteTeacher, getAllTeachers, getClassesByTeacher, getStudentsByTeacher, getTeacher } from '../services/teachers.js'; | import { | ||||||
|  |     createTeacher, | ||||||
|  |     deleteTeacher, | ||||||
|  |     getAllTeachers, | ||||||
|  |     getClassesByTeacher, | ||||||
|  |     getStudentsByTeacher, | ||||||
|  |     getTeacher, | ||||||
|  |     getTeacherQuestions | ||||||
|  | } from '../services/teachers.js'; | ||||||
| import { ClassDTO } from '../interfaces/class.js'; | import { ClassDTO } from '../interfaces/class.js'; | ||||||
| import { StudentDTO } from '../interfaces/student.js'; | import { StudentDTO } from '../interfaces/student.js'; | ||||||
| import { QuestionDTO, QuestionId } from '../interfaces/question.js'; | import { QuestionDTO, QuestionId } from '../interfaces/question.js'; | ||||||
| import { TeacherDTO } from '../interfaces/teacher.js'; | import { TeacherDTO } from '../interfaces/teacher.js'; | ||||||
| import { MISSING_FIELDS_ERROR, MISSING_USERNAME_ERROR, NAME_NOT_FOUND_ERROR } from './users'; | import {requireFields} from "./error-helper"; | ||||||
| 
 | 
 | ||||||
| export async function getAllTeachersHandler(req: Request, res: Response): Promise<void> { | export async function getAllTeachersHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const full = req.query.full === 'true'; |     const full = req.query.full === 'true'; | ||||||
|  | @ -21,18 +29,11 @@ export async function getAllTeachersHandler(req: Request, res: Response): Promis | ||||||
| 
 | 
 | ||||||
| export async function getTeacherHandler(req: Request, res: Response): Promise<void> { | export async function getTeacherHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const username = req.params.username; |     const username = req.params.username; | ||||||
| 
 |     requireFields({ username }); | ||||||
|     if (!username) { |  | ||||||
|         res.status(400).json(MISSING_USERNAME_ERROR); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const user = await getTeacher(username); |     const user = await getTeacher(username); | ||||||
| 
 | 
 | ||||||
|     if (!user) { | 
 | ||||||
|         res.status(404).json(NAME_NOT_FOUND_ERROR(username)); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     res.status(201).json(user); |     res.status(201).json(user); | ||||||
| } | } | ||||||
|  | @ -41,7 +42,6 @@ export async function createTeacherHandler(req: Request, res: Response) { | ||||||
|     const userData = req.body as TeacherDTO; |     const userData = req.body as TeacherDTO; | ||||||
| 
 | 
 | ||||||
|     if (!userData.username || !userData.firstName || !userData.lastName) { |     if (!userData.username || !userData.firstName || !userData.lastName) { | ||||||
|         res.status(400).json(MISSING_FIELDS_ERROR); |  | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -53,13 +53,11 @@ export async function deleteTeacherHandler(req: Request, res: Response) { | ||||||
|     const username = req.params.username; |     const username = req.params.username; | ||||||
| 
 | 
 | ||||||
|     if (!username) { |     if (!username) { | ||||||
|         res.status(400).json(MISSING_USERNAME_ERROR); |  | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const deletedUser = await deleteTeacher(username); |     const deletedUser = await deleteTeacher(username); | ||||||
|     if (!deletedUser) { |     if (!deletedUser) { | ||||||
|         res.status(404).json(NAME_NOT_FOUND_ERROR(username)); |  | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -71,7 +69,6 @@ export async function getTeacherClassHandler(req: Request, res: Response): Promi | ||||||
|     const full = req.query.full === 'true'; |     const full = req.query.full === 'true'; | ||||||
| 
 | 
 | ||||||
|     if (!username) { |     if (!username) { | ||||||
|         res.status(400).json(MISSING_USERNAME_ERROR); |  | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -85,7 +82,6 @@ export async function getTeacherStudentHandler(req: Request, res: Response): Pro | ||||||
|     const full = req.query.full === 'true'; |     const full = req.query.full === 'true'; | ||||||
| 
 | 
 | ||||||
|     if (!username) { |     if (!username) { | ||||||
|         res.status(400).json(MISSING_USERNAME_ERROR); |  | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -99,11 +95,10 @@ export async function getTeacherQuestionHandler(req: Request, res: Response): Pr | ||||||
|     const full = req.query.full === 'true'; |     const full = req.query.full === 'true'; | ||||||
| 
 | 
 | ||||||
|     if (!username) { |     if (!username) { | ||||||
|         res.status(400).json(MISSING_USERNAME_ERROR); |  | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const questions: QuestionDTO[] | QuestionId[] = await getQuestionsByTeacher(username, full); |     const questions: QuestionDTO[] | QuestionId[] = await getTeacherQuestions(username, full); | ||||||
| 
 | 
 | ||||||
|     res.json({ questions }); |     res.json({ questions }); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,7 +0,0 @@ | ||||||
| export const MISSING_USERNAME_ERROR = { error: 'Missing required field: username' }; |  | ||||||
| 
 |  | ||||||
| export function NAME_NOT_FOUND_ERROR(username: string) { |  | ||||||
|     return { error: `User with username '${username}' not found.` }; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export const MISSING_FIELDS_ERROR = { error: 'Missing required fields: username, firstName, lastName' }; |  | ||||||
|  | @ -40,3 +40,17 @@ export class NotFoundException extends Error { | ||||||
|         super(error); |         super(error); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export class ConflictException extends Error { | ||||||
|  |     public status = 409; | ||||||
|  |     constructor(message: string = 'Conflict') { | ||||||
|  |         super(message); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class InternalServerError extends Error { | ||||||
|  |     public status = 500; | ||||||
|  |     constructor(message: string = 'Internal Server Error') { | ||||||
|  |         super(message); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										16
									
								
								backend/src/interfaces/student-request.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								backend/src/interfaces/student-request.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | ||||||
|  | import {mapToStudentDTO, StudentDTO} from "./student"; | ||||||
|  | import {ClassJoinRequest, ClassJoinRequestStatus} from "../entities/classes/class-join-request.entity"; | ||||||
|  | 
 | ||||||
|  | export interface StudentRequestDTO { | ||||||
|  |     requester: StudentDTO; | ||||||
|  |     class: string; | ||||||
|  |     status: ClassJoinRequestStatus | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function mapToStudentRequestDTO(request: ClassJoinRequest): StudentRequestDTO { | ||||||
|  |     return { | ||||||
|  |         requester: mapToStudentDTO(request.requester), | ||||||
|  |         class: request.class.classId!, | ||||||
|  |         status: request.status, | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import { Student } from '../entities/users/student.entity.js'; | import {Student} from '../entities/users/student.entity.js'; | ||||||
| 
 | 
 | ||||||
| export interface StudentDTO { | export interface StudentDTO { | ||||||
|     id?: string; |     id?: string; | ||||||
|  | @ -17,7 +17,5 @@ export function mapToStudentDTO(student: Student): StudentDTO { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function mapToStudent(studentData: StudentDTO): Student { | export function mapToStudent(studentData: StudentDTO): Student { | ||||||
|     const student = new Student(studentData.username, studentData.firstName, studentData.lastName); |     return new Student(studentData.username, studentData.firstName, studentData.lastName); | ||||||
| 
 |  | ||||||
|     return student; |  | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										18
									
								
								backend/src/routes/student-join-requests.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								backend/src/routes/student-join-requests.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | ||||||
|  | import express from "express"; | ||||||
|  | import { | ||||||
|  |     createStudentRequestHandler, deleteClassJoinRequestHandler, | ||||||
|  |     getStudentRequestHandler, | ||||||
|  |     updateClassJoinRequestHandler | ||||||
|  | } from "../controllers/students"; | ||||||
|  | 
 | ||||||
|  | const router = express.Router({ mergeParams: true }); | ||||||
|  | 
 | ||||||
|  | router.get('/', getStudentRequestHandler); | ||||||
|  | 
 | ||||||
|  | router.post('/:classId', createStudentRequestHandler); | ||||||
|  | 
 | ||||||
|  | router.put('/:classId', updateClassJoinRequestHandler); | ||||||
|  | 
 | ||||||
|  | router.delete('/:classId', deleteClassJoinRequestHandler); | ||||||
|  | 
 | ||||||
|  | export default router; | ||||||
|  | @ -10,7 +10,8 @@ import { | ||||||
|     getStudentQuestionsHandler, |     getStudentQuestionsHandler, | ||||||
|     getStudentSubmissionsHandler, |     getStudentSubmissionsHandler, | ||||||
| } from '../controllers/students.js'; | } from '../controllers/students.js'; | ||||||
| import { getStudentGroups } from '../services/students.js'; | import joinRequestRouter from './student-join-requests.js' | ||||||
|  | 
 | ||||||
| const router = express.Router(); | const router = express.Router(); | ||||||
| 
 | 
 | ||||||
| // Root endpoint used to search objects
 | // Root endpoint used to search objects
 | ||||||
|  | @ -38,4 +39,6 @@ router.get('/:username/groups', getStudentGroupsHandler); | ||||||
| // A list of questions a user has created
 | // A list of questions a user has created
 | ||||||
| router.get('/:username/questions', getStudentQuestionsHandler); | router.get('/:username/questions', getStudentQuestionsHandler); | ||||||
| 
 | 
 | ||||||
|  | router.use('/:username/join-requests', joinRequestRouter) | ||||||
|  | 
 | ||||||
| export default router; | export default router; | ||||||
|  |  | ||||||
|  | @ -1,4 +1,5 @@ | ||||||
| import { | import { | ||||||
|  |     getClassJoinRequestRepository, | ||||||
|     getClassRepository, |     getClassRepository, | ||||||
|     getGroupRepository, |     getGroupRepository, | ||||||
|     getQuestionRepository, |     getQuestionRepository, | ||||||
|  | @ -12,6 +13,10 @@ import { mapToStudent, mapToStudentDTO, StudentDTO } from '../interfaces/student | ||||||
| import { mapToSubmissionDTO, SubmissionDTO } from '../interfaces/submission.js'; | import { mapToSubmissionDTO, SubmissionDTO } from '../interfaces/submission.js'; | ||||||
| import { getAllAssignments } from './assignments.js'; | import { getAllAssignments } from './assignments.js'; | ||||||
| import { mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId } from '../interfaces/question'; | import { mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId } from '../interfaces/question'; | ||||||
|  | import {ClassJoinRequestStatus} from "../entities/classes/class-join-request.entity"; | ||||||
|  | import {ConflictException, NotFoundException} from "../exceptions"; | ||||||
|  | import {Student} from "../entities/users/student.entity"; | ||||||
|  | import {mapToStudentRequestDTO} from "../interfaces/student-request"; | ||||||
| 
 | 
 | ||||||
| export async function getAllStudents(full: boolean): Promise<StudentDTO[] | string[]> { | export async function getAllStudents(full: boolean): Promise<StudentDTO[] | string[]> { | ||||||
|     const studentRepository = getStudentRepository(); |     const studentRepository = getStudentRepository(); | ||||||
|  | @ -23,24 +28,34 @@ export async function getAllStudents(full: boolean): Promise<StudentDTO[] | stri | ||||||
|     return users.map((user) => user.username); |     return users.map((user) => user.username); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getStudent(username: string): Promise<StudentDTO | null> { | export async function fetchStudent(username: string): Promise<Student> { | ||||||
|     const studentRepository = getStudentRepository(); |     const studentRepository = getStudentRepository(); | ||||||
|     const user = await studentRepository.findByUsername(username); |     const user = await studentRepository.findByUsername(username); | ||||||
|     return user ? mapToStudentDTO(user) : null; | 
 | ||||||
|  |     if (!user) { | ||||||
|  |         throw new NotFoundException("Student with username not found"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return user; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function getStudent(username: string): Promise<StudentDTO> { | ||||||
|  |     const user = await fetchStudent(username); | ||||||
|  |     return mapToStudentDTO(user); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function createStudent(userData: StudentDTO): Promise<StudentDTO | null> { | export async function createStudent(userData: StudentDTO): Promise<StudentDTO | null> { | ||||||
|     const studentRepository = getStudentRepository(); |     const studentRepository = getStudentRepository(); | ||||||
| 
 | 
 | ||||||
|     try { |     const user = await studentRepository.findByUsername(userData.username); | ||||||
|         const newStudent = studentRepository.create(mapToStudent(userData)); |  | ||||||
|         await studentRepository.save(newStudent); |  | ||||||
| 
 | 
 | ||||||
|         return mapToStudentDTO(newStudent); |     if (user) { | ||||||
|     } catch (e) { |         throw new ConflictException("Username already exists"); | ||||||
|         console.log(e); |  | ||||||
|         return null; |  | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     const newStudent = studentRepository.create(mapToStudent(userData)); | ||||||
|  |     await studentRepository.save(newStudent); | ||||||
|  |     return mapToStudentDTO(newStudent); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function deleteStudent(username: string): Promise<StudentDTO | null> { | export async function deleteStudent(username: string): Promise<StudentDTO | null> { | ||||||
|  | @ -49,26 +64,15 @@ export async function deleteStudent(username: string): Promise<StudentDTO | null | ||||||
|     const user = await studentRepository.findByUsername(username); |     const user = await studentRepository.findByUsername(username); | ||||||
| 
 | 
 | ||||||
|     if (!user) { |     if (!user) { | ||||||
|         return null; |         throw new NotFoundException("Student with username not found"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     try { |     await studentRepository.deleteByUsername(username); | ||||||
|         await studentRepository.deleteByUsername(username); |     return mapToStudentDTO(user); | ||||||
| 
 |  | ||||||
|         return mapToStudentDTO(user); |  | ||||||
|     } catch (e) { |  | ||||||
|         console.log(e); |  | ||||||
|         return null; |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getStudentClasses(username: string, full: boolean): Promise<ClassDTO[] | string[]> { | export async function getStudentClasses(username: string, full: boolean): Promise<ClassDTO[] | string[]> { | ||||||
|     const studentRepository = getStudentRepository(); |     const student = await fetchStudent(username); | ||||||
|     const student = await studentRepository.findByUsername(username); |  | ||||||
| 
 |  | ||||||
|     if (!student) { |  | ||||||
|         return []; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const classRepository = getClassRepository(); |     const classRepository = getClassRepository(); | ||||||
|     const classes = await classRepository.findByStudent(student); |     const classes = await classRepository.findByStudent(student); | ||||||
|  | @ -81,12 +85,7 @@ export async function getStudentClasses(username: string, full: boolean): Promis | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getStudentAssignments(username: string, full: boolean): Promise<AssignmentDTO[]> { | export async function getStudentAssignments(username: string, full: boolean): Promise<AssignmentDTO[]> { | ||||||
|     const studentRepository = getStudentRepository(); |     const student = await fetchStudent(username); | ||||||
|     const student = await studentRepository.findByUsername(username); |  | ||||||
| 
 |  | ||||||
|     if (!student) { |  | ||||||
|         return []; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const classRepository = getClassRepository(); |     const classRepository = getClassRepository(); | ||||||
|     const classes = await classRepository.findByStudent(student); |     const classes = await classRepository.findByStudent(student); | ||||||
|  | @ -95,12 +94,7 @@ export async function getStudentAssignments(username: string, full: boolean): Pr | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getStudentGroups(username: string, full: boolean): Promise<GroupDTO[]> { | export async function getStudentGroups(username: string, full: boolean): Promise<GroupDTO[]> { | ||||||
|     const studentRepository = getStudentRepository(); |     const student = await fetchStudent(username); | ||||||
|     const student = await studentRepository.findByUsername(username); |  | ||||||
| 
 |  | ||||||
|     if (!student) { |  | ||||||
|         return []; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const groupRepository = getGroupRepository(); |     const groupRepository = getGroupRepository(); | ||||||
|     const groups = await groupRepository.findAllGroupsWithStudent(student); |     const groups = await groupRepository.findAllGroupsWithStudent(student); | ||||||
|  | @ -113,12 +107,7 @@ export async function getStudentGroups(username: string, full: boolean): Promise | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getStudentSubmissions(username: string): Promise<SubmissionDTO[]> { | export async function getStudentSubmissions(username: string): Promise<SubmissionDTO[]> { | ||||||
|     const studentRepository = getStudentRepository(); |     const student = await fetchStudent(username); | ||||||
|     const student = await studentRepository.findByUsername(username); |  | ||||||
| 
 |  | ||||||
|     if (!student) { |  | ||||||
|         return []; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const submissionRepository = getSubmissionRepository(); |     const submissionRepository = getSubmissionRepository(); | ||||||
|     const submissions = await submissionRepository.findAllSubmissionsForStudent(student); |     const submissions = await submissionRepository.findAllSubmissionsForStudent(student); | ||||||
|  | @ -127,12 +116,7 @@ export async function getStudentSubmissions(username: string): Promise<Submissio | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getStudentQuestions(username: string, full: boolean): Promise<QuestionDTO[] | QuestionId[]> { | export async function getStudentQuestions(username: string, full: boolean): Promise<QuestionDTO[] | QuestionId[]> { | ||||||
|     const studentRepository = getStudentRepository(); |     const student = await fetchStudent(username); | ||||||
|     const student = await studentRepository.findByUsername(username); |  | ||||||
| 
 |  | ||||||
|     if (!student) { |  | ||||||
|         return []; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const questionRepository = getQuestionRepository(); |     const questionRepository = getQuestionRepository(); | ||||||
|     const questions = await questionRepository.findAllByAuthor(student); |     const questions = await questionRepository.findAllByAuthor(student); | ||||||
|  | @ -144,3 +128,79 @@ export async function getStudentQuestions(username: string, full: boolean): Prom | ||||||
| 
 | 
 | ||||||
|     return questionsDTO.map(mapToQuestionId); |     return questionsDTO.map(mapToQuestionId); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export async function createClassJoinRequest(studentUsername: string, classId: string) { | ||||||
|  |     const classRepo = getClassRepository(); | ||||||
|  |     const requestRepo = getClassJoinRequestRepository(); | ||||||
|  | 
 | ||||||
|  |     const student = await fetchStudent(studentUsername); | ||||||
|  |     const cls = await classRepo.findById(classId); | ||||||
|  | 
 | ||||||
|  |     if (!cls){ | ||||||
|  |         throw new NotFoundException("Class with id not found"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const request = requestRepo.create({ | ||||||
|  |         requester: student, | ||||||
|  |         class: cls, | ||||||
|  |         status: ClassJoinRequestStatus.Open, | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     await requestRepo.save(request); | ||||||
|  |     return request; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function getJoinRequestsByStudent(studentUsername: string) { | ||||||
|  |     const requestRepo = getClassJoinRequestRepository(); | ||||||
|  | 
 | ||||||
|  |     const student = await fetchStudent(studentUsername); | ||||||
|  | 
 | ||||||
|  |     const requests = await requestRepo.findAllRequestsBy(student); | ||||||
|  |     return requests.map(mapToStudentRequestDTO); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function updateClassJoinRequestStatus( studentUsername: string, classId: string, accepted: boolean = true) { | ||||||
|  |     const requestRepo = getClassJoinRequestRepository(); | ||||||
|  |     const classRepo = getClassRepository(); | ||||||
|  | 
 | ||||||
|  |     const student = await fetchStudent(studentUsername); | ||||||
|  |     const cls = await classRepo.findById(classId); | ||||||
|  | 
 | ||||||
|  |     if (!cls) { | ||||||
|  |         throw new NotFoundException('Class not found'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const request = await requestRepo.findOne({ requester: student, class: cls }); | ||||||
|  | 
 | ||||||
|  |     if (!request) { | ||||||
|  |         throw new NotFoundException('Join request not found'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     request.status = accepted ? ClassJoinRequestStatus.Accepted : ClassJoinRequestStatus.Declined; | ||||||
|  | 
 | ||||||
|  |     await requestRepo.save(request); | ||||||
|  |     return request; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function deleteClassJoinRequest(studentUsername: string, classId: string) { | ||||||
|  |     const requestRepo = getClassJoinRequestRepository(); | ||||||
|  |     const classRepo = getClassRepository(); | ||||||
|  | 
 | ||||||
|  |     const student = await fetchStudent(studentUsername); | ||||||
|  |     const cls = await classRepo.findById(classId); | ||||||
|  | 
 | ||||||
|  |     if (!cls) { | ||||||
|  |         throw new NotFoundException('Class not found'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const request = await requestRepo.findOne({ requester: student, class: cls }); | ||||||
|  | 
 | ||||||
|  |     if (!request) { | ||||||
|  |         throw new NotFoundException('Join request not found'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     await requestRepo.deleteBy(student, cls); | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | @ -2,15 +2,14 @@ import { | ||||||
|     getClassRepository, |     getClassRepository, | ||||||
|     getLearningObjectRepository, |     getLearningObjectRepository, | ||||||
|     getQuestionRepository, |     getQuestionRepository, | ||||||
|     getStudentRepository, |  | ||||||
|     getTeacherRepository, |     getTeacherRepository, | ||||||
| } from '../data/repositories.js'; | } from '../data/repositories.js'; | ||||||
| import { ClassDTO, mapToClassDTO } from '../interfaces/class.js'; | import { ClassDTO, mapToClassDTO } from '../interfaces/class.js'; | ||||||
| import { getClassStudents } from './class.js'; | import { getClassStudents } from './class.js'; | ||||||
| import { mapToQuestionDTO, mapToQuestionId, QuestionDTO } from '../interfaces/question.js'; | import {mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId} from '../interfaces/question.js'; | ||||||
| import { mapToTeacher, mapToTeacherDTO, TeacherDTO } from '../interfaces/teacher.js'; | import { mapToTeacher, mapToTeacherDTO, TeacherDTO } from '../interfaces/teacher.js'; | ||||||
| 
 | 
 | ||||||
| export async function getAllTeachers(full: boolean): Promise<TeacherDTO[]> { | export async function getAllTeachers(full: boolean): Promise<TeacherDTO[] | string[]> { | ||||||
|     const teacherRepository = getTeacherRepository(); |     const teacherRepository = getTeacherRepository(); | ||||||
|     const users = await teacherRepository.findAll(); |     const users = await teacherRepository.findAll(); | ||||||
| 
 | 
 | ||||||
|  | @ -81,16 +80,17 @@ export async function getClassesByTeacher(username: string, full: boolean): Prom | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getStudentsByTeacher(username: string, full: boolean) { | export async function getStudentsByTeacher(username: string, full: boolean) { | ||||||
|     const classes = await getClassesByTeacher(username, false); |     const classes = await fetchClassesByTeacher(username); | ||||||
|  |     const classIds = classes.map((cls) => cls.id); | ||||||
| 
 | 
 | ||||||
|     const students = (await Promise.all(classes.map(async (id) => getClassStudents(id)))).flat(); |     const students = (await Promise.all(classIds.map(async (id) => getClassStudents(id)))).flat(); | ||||||
|     if (full) { |     if (full) { | ||||||
|         return students; |         return students; | ||||||
|     } |     } | ||||||
|     return students.map((student) => student.username); |     return students.map((student) => student.username); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getTeacherQuestions(username: string, full: boolean): Promise<QuestionDTO[]> { | export async function getTeacherQuestions(username: string, full: boolean): Promise<QuestionDTO[] | QuestionId[]> { | ||||||
|     const teacherRepository = getTeacherRepository(); |     const teacherRepository = getTeacherRepository(); | ||||||
|     const teacher = await teacherRepository.findByUsername(username); |     const teacher = await teacherRepository.findByUsername(username); | ||||||
|     if (!teacher) { |     if (!teacher) { | ||||||
|  | @ -104,10 +104,11 @@ export async function getTeacherQuestions(username: string, full: boolean): Prom | ||||||
|     // Fetch all questions related to these learning objects
 |     // Fetch all questions related to these learning objects
 | ||||||
|     const questionRepository = getQuestionRepository(); |     const questionRepository = getQuestionRepository(); | ||||||
|     const questions = await questionRepository.findAllByLearningObjects(learningObjects); |     const questions = await questionRepository.findAllByLearningObjects(learningObjects); | ||||||
|  |     const questionsDTO = questions.map(mapToQuestionDTO); | ||||||
| 
 | 
 | ||||||
|     if (full) { |     if (full) { | ||||||
|         return questions.map(mapToQuestionDTO); |         return questionsDTO; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return questions.map(mapToQuestionId); |     return questionsDTO.map(mapToQuestionId); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -9,9 +9,14 @@ import { | ||||||
|     getStudentClassesHandler, |     getStudentClassesHandler, | ||||||
|     getStudentGroupsHandler, |     getStudentGroupsHandler, | ||||||
|     getStudentSubmissionsHandler, |     getStudentSubmissionsHandler, | ||||||
|     getStudentQuestionsHandler |     getStudentQuestionsHandler, | ||||||
|  |     createStudentRequestHandler, | ||||||
|  |     getStudentRequestHandler, | ||||||
|  |     updateClassJoinRequestHandler, | ||||||
|  |     deleteClassJoinRequestHandler | ||||||
| } from '../../src/controllers/students.js'; | } from '../../src/controllers/students.js'; | ||||||
| import {TEST_STUDENTS} from "../test_assets/users/students.testdata"; | import {TEST_STUDENTS} from "../test_assets/users/students.testdata"; | ||||||
|  | import {BadRequestException, NotFoundException} from "../../src/exceptions"; | ||||||
| 
 | 
 | ||||||
| describe('Student controllers', () => { | describe('Student controllers', () => { | ||||||
|     let req: Partial<Request>; |     let req: Partial<Request>; | ||||||
|  | @ -33,22 +38,20 @@ describe('Student controllers', () => { | ||||||
|         }; |         }; | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('Student not found 404', async () => { |     it('Student not found', async () => { | ||||||
|         req = { params: { username: 'doesnotexist' } }; |         req = { params: { username: 'doesnotexist' } }; | ||||||
| 
 | 
 | ||||||
|         await getStudentHandler(req as Request, res as Response); |         await expect(() => deleteStudentHandler(req as Request, res as Response)) | ||||||
| 
 |             .rejects | ||||||
|         expect(statusMock).toHaveBeenCalledWith(404); |             .toThrow(NotFoundException); | ||||||
|         expect(jsonMock).toHaveBeenCalled(); |  | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('No username 400', async () => { |     it('No username', async () => { | ||||||
|         req = { params: {} }; |         req = { params: {} }; | ||||||
| 
 | 
 | ||||||
|         await getStudentHandler(req as Request, res as Response); |         await expect(() => getStudentHandler(req as Request, res as Response)) | ||||||
| 
 |             .rejects | ||||||
|         expect(statusMock).toHaveBeenCalledWith(400); |             .toThrowError(BadRequestException); | ||||||
|         expect(jsonMock).toHaveBeenCalled(); |  | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('Create student', async () => { |     it('Create student', async () => { | ||||||
|  | @ -66,13 +69,14 @@ describe('Student controllers', () => { | ||||||
|         expect(jsonMock).toHaveBeenCalled(); |         expect(jsonMock).toHaveBeenCalled(); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  |     // TODO create duplicate student id
 | ||||||
|  | 
 | ||||||
|     it('Create student no body 400', async () => { |     it('Create student no body 400', async () => { | ||||||
|         req = { body: {} }; |         req = { body: {} }; | ||||||
| 
 | 
 | ||||||
|         await createStudentHandler(req as Request, res as Response); |         await expect(() => createStudentHandler(req as Request, res as Response)) | ||||||
| 
 |             .rejects | ||||||
|         expect(statusMock).toHaveBeenCalledWith(400); |             .toThrowError(BadRequestException); | ||||||
|         expect(jsonMock).toHaveBeenCalled(); |  | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('Student list', async () => { |     it('Student list', async () => { | ||||||
|  | @ -146,12 +150,73 @@ describe('Student controllers', () => { | ||||||
|         expect(jsonMock).toHaveBeenCalled(); |         expect(jsonMock).toHaveBeenCalled(); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('Deleting non-existent student 404', async () => { |     it('Deleting non-existent student', async () => { | ||||||
|         req = { params: { username: 'doesnotexist' } }; |         req = { params: { username: 'doesnotexist' } }; | ||||||
| 
 | 
 | ||||||
|         await deleteStudentHandler(req as Request, res as Response); |         await expect(() => deleteStudentHandler(req as Request, res as Response)) | ||||||
|  |             .rejects | ||||||
|  |             .toThrow(NotFoundException); | ||||||
|  |     }); | ||||||
| 
 | 
 | ||||||
|         expect(statusMock).toHaveBeenCalledWith(404); |     it('Get join requests by student', async () => { | ||||||
|  |         req = { | ||||||
|  |             params: { username: 'PinkFloyd' }, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         await getStudentRequestHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(statusMock).toHaveBeenCalledWith(201); | ||||||
|  |         expect(jsonMock).toHaveBeenCalledWith( | ||||||
|  |             expect.objectContaining({ | ||||||
|  |                 requests: expect.anything(), | ||||||
|  |             }) | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         const result = jsonMock.mock.lastCall?.[0]; | ||||||
|  |         // console.log('[JOIN REQUESTS]', result.requests);
 | ||||||
|  |         expect(result.requests.length).toBeGreaterThan(0); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Create join request', async () => { | ||||||
|  |         req = { | ||||||
|  |             params: { username: 'DireStraits', classId: '' }, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         await createStudentRequestHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(statusMock).toHaveBeenCalledWith(201); | ||||||
|         expect(jsonMock).toHaveBeenCalled(); |         expect(jsonMock).toHaveBeenCalled(); | ||||||
|     }); |     }); | ||||||
|  | 
 | ||||||
|  |     /* | ||||||
|  | 
 | ||||||
|  |     it('Update join request status (accept)', async () => { | ||||||
|  |         req = { | ||||||
|  |             params: { classId }, | ||||||
|  |             query: { username }, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         await updateClassJoinRequestHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(statusMock).toHaveBeenCalledWith(200); | ||||||
|  |         expect(jsonMock).toHaveBeenCalled(); | ||||||
|  |         const result = jsonMock.mock.lastCall?.[0]; | ||||||
|  |         console.log('[UPDATED REQUEST]', result); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Delete join request', async () => { | ||||||
|  |         req = { | ||||||
|  |             params: { classId }, | ||||||
|  |             query: { username }, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         await deleteClassJoinRequestHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(statusMock).toHaveBeenCalledWith(204); | ||||||
|  |         expect(sendMock).toHaveBeenCalled(); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |      */ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| }); | }); | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Gabriellvl
						Gabriellvl