fix: student join req by class route + teacher return post put delete + status
This commit is contained in:
		
							parent
							
								
									c0995d3933
								
							
						
					
					
						commit
						912369f87e
					
				
					 9 changed files with 106 additions and 47 deletions
				
			
		|  | @ -4,7 +4,7 @@ import { | |||
|     createStudent, | ||||
|     deleteClassJoinRequest, | ||||
|     deleteStudent, | ||||
|     getAllStudents, | ||||
|     getAllStudents, getJoinRequestByStudentClass, | ||||
|     getJoinRequestsByStudent, | ||||
|     getStudent, | ||||
|     getStudentAssignments, | ||||
|  | @ -116,7 +116,7 @@ export async function createStudentRequestHandler(req: Request, res: Response): | |||
|     res.json({ request }); | ||||
| } | ||||
| 
 | ||||
| export async function getStudentRequestHandler(req: Request, res: Response): Promise<void> { | ||||
| export async function getStudentRequestsHandler(req: Request, res: Response): Promise<void> { | ||||
|     const username = req.params.username; | ||||
|     requireFields({ username }); | ||||
| 
 | ||||
|  | @ -124,6 +124,15 @@ export async function getStudentRequestHandler(req: Request, res: Response): Pro | |||
|     res.json({ requests }); | ||||
| } | ||||
| 
 | ||||
| export async function getStudentRequestHandler(req: Request, res: Response): Promise<void> { | ||||
|     const username = req.params.username as string; | ||||
|     const classId = req.params.classId; | ||||
|     requireFields({ username, classId }); | ||||
| 
 | ||||
|     const request = await getJoinRequestByStudentClass(username, classId); | ||||
|     res.json({ request }); | ||||
| } | ||||
| 
 | ||||
| export async function deleteClassJoinRequestHandler(req: Request, res: Response) { | ||||
|     const username = req.params.username as string; | ||||
|     const classId = req.params.classId; | ||||
|  |  | |||
|  | @ -41,16 +41,16 @@ export async function createTeacherHandler(req: Request, res: Response) { | |||
| 
 | ||||
|     const userData = req.body as TeacherDTO; | ||||
| 
 | ||||
|     await createTeacher(userData); | ||||
|     res.sendStatus(201); | ||||
|     const teacher = await createTeacher(userData); | ||||
|     res.json({ teacher }); | ||||
| } | ||||
| 
 | ||||
| export async function deleteTeacherHandler(req: Request, res: Response) { | ||||
|     const username = req.params.username; | ||||
|     requireFields({ username }); | ||||
| 
 | ||||
|     await deleteTeacher(username); | ||||
|     res.sendStatus(200); | ||||
|     const teacher = await deleteTeacher(username); | ||||
|     res.json({ teacher }); | ||||
| } | ||||
| 
 | ||||
| export async function getTeacherClassHandler(req: Request, res: Response): Promise<void> { | ||||
|  | @ -98,6 +98,6 @@ export async function updateStudentJoinRequestHandler(req: Request, res: Respons | |||
|     const accepted = req.body.accepted !== 'false'; // Default = true
 | ||||
|     requireFields({ studentUsername, classId }); | ||||
| 
 | ||||
|     await updateClassJoinRequestStatus(studentUsername, classId, accepted); | ||||
|     res.sendStatus(200); | ||||
|     const request = await updateClassJoinRequestStatus(studentUsername, classId, accepted); | ||||
|     res.json({ request }); | ||||
| } | ||||
|  |  | |||
|  | @ -1,12 +1,19 @@ | |||
| import express from 'express'; | ||||
| import { createStudentRequestHandler, deleteClassJoinRequestHandler, getStudentRequestHandler } from '../controllers/students.js'; | ||||
| import { | ||||
|     createStudentRequestHandler, | ||||
|     deleteClassJoinRequestHandler, | ||||
|     getStudentRequestHandler, | ||||
|     getStudentRequestsHandler | ||||
| } from '../controllers/students.js'; | ||||
| 
 | ||||
| const router = express.Router({ mergeParams: true }); | ||||
| 
 | ||||
| router.get('/', getStudentRequestHandler); | ||||
| router.get('/', getStudentRequestsHandler); | ||||
| 
 | ||||
| router.post('/', createStudentRequestHandler); | ||||
| 
 | ||||
| router.get('/:classId', getStudentRequestHandler); | ||||
| 
 | ||||
| router.delete('/:classId', deleteClassJoinRequestHandler); | ||||
| 
 | ||||
| export default router; | ||||
|  |  | |||
|  | @ -125,10 +125,10 @@ export async function getStudentQuestions(username: string, full: boolean): Prom | |||
|     return questionsDTO.map(mapToQuestionId); | ||||
| } | ||||
| 
 | ||||
| export async function createClassJoinRequest(studentUsername: string, classId: string) { | ||||
| export async function createClassJoinRequest(username: string, classId: string) { | ||||
|     const requestRepo = getClassJoinRequestRepository(); | ||||
| 
 | ||||
|     const student = await fetchStudent(studentUsername); // Throws error if student not found
 | ||||
|     const student = await fetchStudent(username); // Throws error if student not found
 | ||||
|     const cls = await fetchClass(classId); | ||||
| 
 | ||||
|     const request = mapToStudentRequest(student, cls); | ||||
|  | @ -136,19 +136,33 @@ export async function createClassJoinRequest(studentUsername: string, classId: s | |||
|     return mapToStudentRequestDTO(request); | ||||
| } | ||||
| 
 | ||||
| export async function getJoinRequestsByStudent(studentUsername: string) { | ||||
| export async function getJoinRequestsByStudent(username: string) { | ||||
|     const requestRepo = getClassJoinRequestRepository(); | ||||
| 
 | ||||
|     const student = await fetchStudent(studentUsername); | ||||
|     const student = await fetchStudent(username); | ||||
| 
 | ||||
|     const requests = await requestRepo.findAllRequestsBy(student); | ||||
|     return requests.map(mapToStudentRequestDTO); | ||||
| } | ||||
| 
 | ||||
| export async function deleteClassJoinRequest(studentUsername: string, classId: string) { | ||||
| export async function getJoinRequestByStudentClass(username: string, classId: string){ | ||||
|     const requestRepo = getClassJoinRequestRepository(); | ||||
| 
 | ||||
|     const student = await fetchStudent(studentUsername); | ||||
|     const student = await fetchStudent(username); | ||||
|     const cls = await fetchClass(classId); | ||||
| 
 | ||||
|     const request = await requestRepo.findByStudentAndClass(student, cls); | ||||
|     if (!request){ | ||||
|         throw new NotFoundException('Join request not found'); | ||||
|     } | ||||
| 
 | ||||
|     return mapToStudentRequestDTO(request); | ||||
| } | ||||
| 
 | ||||
| export async function deleteClassJoinRequest(username: string, classId: string) { | ||||
|     const requestRepo = getClassJoinRequestRepository(); | ||||
| 
 | ||||
|     const student = await fetchStudent(username); | ||||
|     const cls = await fetchClass(classId); | ||||
| 
 | ||||
|     const request = await requestRepo.findByStudentAndClass(student, cls); | ||||
|  |  | |||
|  | @ -51,19 +51,21 @@ export async function getTeacher(username: string): Promise<TeacherDTO> { | |||
|     return mapToTeacherDTO(user); | ||||
| } | ||||
| 
 | ||||
| export async function createTeacher(userData: TeacherDTO): Promise<void> { | ||||
| export async function createTeacher(userData: TeacherDTO): Promise<TeacherDTO> { | ||||
|     const teacherRepository: TeacherRepository = getTeacherRepository(); | ||||
| 
 | ||||
|     const newTeacher = mapToTeacher(userData); | ||||
|     await teacherRepository.save(newTeacher, { preventOverwrite: true }); | ||||
|     return mapToTeacherDTO(newTeacher); | ||||
| } | ||||
| 
 | ||||
| export async function deleteTeacher(username: string): Promise<void> { | ||||
| export async function deleteTeacher(username: string): Promise<TeacherDTO> { | ||||
|     const teacherRepository: TeacherRepository = getTeacherRepository(); | ||||
| 
 | ||||
|     await fetchTeacher(username); // Throws error if it does not exist
 | ||||
|     const teacher = await fetchTeacher(username); // Throws error if it does not exist
 | ||||
| 
 | ||||
|     await teacherRepository.deleteByUsername(username); | ||||
|     return mapToTeacherDTO(teacher); | ||||
| } | ||||
| 
 | ||||
| async function fetchClassesByTeacher(username: string): Promise<ClassDTO[]> { | ||||
|  | @ -106,9 +108,6 @@ export async function getTeacherQuestions(username: string, full: boolean): Prom | |||
|     const learningObjectRepository: LearningObjectRepository = getLearningObjectRepository(); | ||||
|     const learningObjects: LearningObject[] = await learningObjectRepository.findAllByTeacher(teacher); | ||||
| 
 | ||||
|     // Console.log(learningObjects)
 | ||||
|     // TODO returns empty
 | ||||
| 
 | ||||
|     if (!learningObjects || learningObjects.length === 0) { | ||||
|         return []; | ||||
|     } | ||||
|  | @ -138,7 +137,7 @@ export async function getJoinRequestsByClass(classId: string): Promise<StudentRe | |||
|     return requests.map(mapToStudentRequestDTO); | ||||
| } | ||||
| 
 | ||||
| export async function updateClassJoinRequestStatus(studentUsername: string, classId: string, accepted: boolean = true): Promise<void> { | ||||
| export async function updateClassJoinRequestStatus(studentUsername: string, classId: string, accepted: boolean = true): Promise<StudentRequestDTO> { | ||||
|     const requestRepo: ClassJoinRequestRepository = getClassJoinRequestRepository(); | ||||
|     const classRepo: ClassRepository = getClassRepository(); | ||||
| 
 | ||||
|  | @ -158,4 +157,5 @@ export async function updateClassJoinRequestStatus(studentUsername: string, clas | |||
|     request.status = accepted ? ClassJoinRequestStatus.Accepted : ClassJoinRequestStatus.Declined; | ||||
| 
 | ||||
|     await requestRepo.save(request); | ||||
|     return mapToStudentRequestDTO(request); | ||||
| } | ||||
|  |  | |||
|  | @ -11,8 +11,8 @@ import { | |||
|     getStudentSubmissionsHandler, | ||||
|     getStudentQuestionsHandler, | ||||
|     createStudentRequestHandler, | ||||
|     getStudentRequestHandler, | ||||
|     deleteClassJoinRequestHandler, | ||||
|     getStudentRequestsHandler, | ||||
|     deleteClassJoinRequestHandler, getStudentRequestHandler, | ||||
| } from '../../src/controllers/students.js'; | ||||
| import { TEST_STUDENTS } from '../test_assets/users/students.testdata.js'; | ||||
| import { NotFoundException } from '../../src/exceptions/not-found-exception.js'; | ||||
|  | @ -176,7 +176,7 @@ describe('Student controllers', () => { | |||
|             params: { username: 'PinkFloyd' }, | ||||
|         }; | ||||
| 
 | ||||
|         await getStudentRequestHandler(req as Request, res as Response); | ||||
|         await getStudentRequestsHandler(req as Request, res as Response); | ||||
| 
 | ||||
|         expect(jsonMock).toHaveBeenCalledWith( | ||||
|             expect.objectContaining({ | ||||
|  | @ -189,6 +189,20 @@ describe('Student controllers', () => { | |||
|         expect(result.requests.length).toBeGreaterThan(0); | ||||
|     }); | ||||
| 
 | ||||
|     it('Get join request by student', async () => { | ||||
|         req = { | ||||
|             params: { username: 'PinkFloyd', classId: 'id02' }, | ||||
|         }; | ||||
| 
 | ||||
|         await getStudentRequestHandler(req as Request, res as Response); | ||||
| 
 | ||||
|         expect(jsonMock).toHaveBeenCalledWith( | ||||
|             expect.objectContaining({ | ||||
|                 request: expect.anything(), | ||||
|             }) | ||||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it('Create join request', async () => { | ||||
|         req = { | ||||
|             params: { username: 'Noordkaap' }, | ||||
|  |  | |||
|  | @ -15,7 +15,7 @@ import { | |||
| } from '../../src/controllers/teachers.js'; | ||||
| import { BadRequestException } from '../../src/exceptions/bad-request-exception.js'; | ||||
| import { EntityAlreadyExistsException } from '../../src/exceptions/entity-already-exists-exception.js'; | ||||
| import { getStudentRequestHandler } from '../../src/controllers/students.js'; | ||||
| import { getStudentRequestsHandler } from '../../src/controllers/students.js'; | ||||
| import { TeacherDTO } from '../../src/interfaces/teacher.js'; | ||||
| 
 | ||||
| describe('Teacher controllers', () => { | ||||
|  | @ -23,8 +23,6 @@ describe('Teacher controllers', () => { | |||
|     let res: Partial<Response>; | ||||
| 
 | ||||
|     let jsonMock: Mock; | ||||
|     let statusMock: Mock; | ||||
|     let sendStatusMock: Mock; | ||||
| 
 | ||||
|     beforeAll(async () => { | ||||
|         await setupTestApp(); | ||||
|  | @ -32,12 +30,8 @@ describe('Teacher controllers', () => { | |||
| 
 | ||||
|     beforeEach(() => { | ||||
|         jsonMock = vi.fn(); | ||||
|         statusMock = vi.fn().mockReturnThis(); | ||||
|         sendStatusMock = vi.fn().mockReturnThis(); | ||||
|         res = { | ||||
|             json: jsonMock, | ||||
|             status: statusMock, | ||||
|             sendStatus: sendStatusMock, | ||||
|         }; | ||||
|     }); | ||||
| 
 | ||||
|  | @ -52,33 +46,37 @@ describe('Teacher controllers', () => { | |||
|     it('Teacher not found', async () => { | ||||
|         req = { params: { username: 'doesnotexist' } }; | ||||
| 
 | ||||
|         await expect(() => getTeacherHandler(req as Request, res as Response)).rejects.toThrow(NotFoundException); | ||||
|         await expect(() => getTeacherHandler(req as Request, res as Response)) | ||||
|             .rejects.toThrow(NotFoundException); | ||||
|     }); | ||||
| 
 | ||||
|     it('No username', async () => { | ||||
|         req = { params: {} }; | ||||
| 
 | ||||
|         await expect(() => getTeacherHandler(req as Request, res as Response)).rejects.toThrowError(BadRequestException); | ||||
|         await expect(() => getTeacherHandler(req as Request, res as Response)) | ||||
|             .rejects.toThrowError(BadRequestException); | ||||
|     }); | ||||
| 
 | ||||
|     it('Create and delete teacher', async () => { | ||||
|         req = { | ||||
|             body: { | ||||
|         const teacher = { | ||||
|             id: 'coolteacher', | ||||
|             username: 'coolteacher', | ||||
|             firstName: 'New', | ||||
|             lastName: 'Teacher', | ||||
|             }, | ||||
|         } | ||||
|         req = { | ||||
|             body: teacher, | ||||
|         }; | ||||
| 
 | ||||
|         await createTeacherHandler(req as Request, res as Response); | ||||
| 
 | ||||
|         expect(sendStatusMock).toHaveBeenCalledWith(201); | ||||
|         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ teacher: expect.objectContaining(teacher) })); | ||||
| 
 | ||||
|         req = { params: { username: 'coolteacher' } }; | ||||
| 
 | ||||
|         await deleteTeacherHandler(req as Request, res as Response); | ||||
| 
 | ||||
|         expect(sendStatusMock).toHaveBeenCalledWith(200); | ||||
|         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ teacher: expect.objectContaining(teacher) })); | ||||
|     }); | ||||
| 
 | ||||
|     it('Create duplicate student', async () => { | ||||
|  | @ -90,13 +88,15 @@ describe('Teacher controllers', () => { | |||
|             }, | ||||
|         }; | ||||
| 
 | ||||
|         await expect(() => createTeacherHandler(req as Request, res as Response)).rejects.toThrowError(EntityAlreadyExistsException); | ||||
|         await expect(() => createTeacherHandler(req as Request, res as Response)) | ||||
|             .rejects.toThrowError(EntityAlreadyExistsException); | ||||
|     }); | ||||
| 
 | ||||
|     it('Create teacher no body', async () => { | ||||
|         req = { body: {} }; | ||||
| 
 | ||||
|         await expect(() => createTeacherHandler(req as Request, res as Response)).rejects.toThrowError(BadRequestException); | ||||
|         await expect(() => createTeacherHandler(req as Request, res as Response)) | ||||
|             .rejects.toThrowError(BadRequestException); | ||||
|     }); | ||||
| 
 | ||||
|     it('Teacher list', async () => { | ||||
|  | @ -193,15 +193,15 @@ describe('Teacher controllers', () => { | |||
|             body: { accepted: 'true' }, | ||||
|         }; | ||||
| 
 | ||||
|         await updateStudentJoinRequestHandler(req as Request, res as Response); | ||||
|         const teacher = await updateStudentJoinRequestHandler(req as Request, res as Response); | ||||
| 
 | ||||
|         expect(sendStatusMock).toHaveBeenCalledWith(200); | ||||
|         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ teacher: expect.objectContaining(teacher) })); | ||||
| 
 | ||||
|         req = { | ||||
|             params: { username: 'PinkFloyd' }, | ||||
|         }; | ||||
| 
 | ||||
|         await getStudentRequestHandler(req as Request, res as Response); | ||||
|         await getStudentRequestsHandler(req as Request, res as Response); | ||||
| 
 | ||||
|         const status: boolean = jsonMock.mock.lastCall?.[0].requests[0].status; | ||||
|         expect(status).toBeTruthy; | ||||
|  |  | |||
|  | @ -45,6 +45,10 @@ export class StudentController extends BaseController { | |||
|         return this.get<{ requests: any[] }>(`/${username}/joinRequests`); | ||||
|     } | ||||
| 
 | ||||
|     getJoinRequest(username: string, classId: string) { | ||||
|         return this.get<{ request: any[] }>(`/${username}/joinRequests/${classId}`); | ||||
|     } | ||||
| 
 | ||||
|     createJoinRequest(username: string, classId: string) { | ||||
|         return this.post(`/${username}/joinRequests}`, classId); | ||||
|     } | ||||
|  |  | |||
|  | @ -121,6 +121,17 @@ export function useStudentJoinRequestsQuery(username: MaybeRefOrGetter<string | | |||
|     }); | ||||
| } | ||||
| 
 | ||||
| export function useStudentJoinRequestQuery( | ||||
|     username: MaybeRefOrGetter<string | undefined>, | ||||
|     classId: MaybeRefOrGetter<string | undefined>, | ||||
| ) { | ||||
|     return useQuery({ | ||||
|         queryKey: computed(() => STUDENT_JOIN_REQUESTS_QUERY_KEY(toValue(username)!)), | ||||
|         queryFn: () => studentController.getJoinRequest(toValue(username)!, toValue(classId)!), | ||||
|         enabled: () => Boolean(toValue(username)), | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Mutation to create a join request for a class | ||||
|  */ | ||||
|  |  | |||
		Reference in a new issue
	
	 Gabriellvl
						Gabriellvl