From 912369f87e9bdc7afaa01a7841ad2366ccf78401 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Tue, 1 Apr 2025 14:24:06 +0200 Subject: [PATCH] fix: student join req by class route + teacher return post put delete + status --- backend/src/controllers/students.ts | 13 ++++++- backend/src/controllers/teachers.ts | 12 +++--- backend/src/routes/student-join-requests.ts | 11 +++++- backend/src/services/students.ts | 26 ++++++++++--- backend/src/services/teachers.ts | 14 +++---- backend/tests/controllers/students.test.ts | 20 ++++++++-- backend/tests/controllers/teachers.test.ts | 42 ++++++++++----------- frontend/src/controllers/students.ts | 4 ++ frontend/src/queries/students.ts | 11 ++++++ 9 files changed, 106 insertions(+), 47 deletions(-) diff --git a/backend/src/controllers/students.ts b/backend/src/controllers/students.ts index a6807827..0161ab58 100644 --- a/backend/src/controllers/students.ts +++ b/backend/src/controllers/students.ts @@ -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 { +export async function getStudentRequestsHandler(req: Request, res: Response): Promise { 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 { + 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; diff --git a/backend/src/controllers/teachers.ts b/backend/src/controllers/teachers.ts index ce328dac..89fc1a27 100644 --- a/backend/src/controllers/teachers.ts +++ b/backend/src/controllers/teachers.ts @@ -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 { @@ -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 }); } diff --git a/backend/src/routes/student-join-requests.ts b/backend/src/routes/student-join-requests.ts index 7dda6a07..6204e74d 100644 --- a/backend/src/routes/student-join-requests.ts +++ b/backend/src/routes/student-join-requests.ts @@ -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; diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts index 8f3cd30d..3eb93278 100644 --- a/backend/src/services/students.ts +++ b/backend/src/services/students.ts @@ -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); diff --git a/backend/src/services/teachers.ts b/backend/src/services/teachers.ts index 11b27b70..41b62b8b 100644 --- a/backend/src/services/teachers.ts +++ b/backend/src/services/teachers.ts @@ -51,19 +51,21 @@ export async function getTeacher(username: string): Promise { return mapToTeacherDTO(user); } -export async function createTeacher(userData: TeacherDTO): Promise { +export async function createTeacher(userData: TeacherDTO): Promise { const teacherRepository: TeacherRepository = getTeacherRepository(); const newTeacher = mapToTeacher(userData); await teacherRepository.save(newTeacher, { preventOverwrite: true }); + return mapToTeacherDTO(newTeacher); } -export async function deleteTeacher(username: string): Promise { +export async function deleteTeacher(username: string): Promise { 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 { @@ -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 { +export async function updateClassJoinRequestStatus(studentUsername: string, classId: string, accepted: boolean = true): Promise { 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); } diff --git a/backend/tests/controllers/students.test.ts b/backend/tests/controllers/students.test.ts index 8c1d020a..de33b117 100644 --- a/backend/tests/controllers/students.test.ts +++ b/backend/tests/controllers/students.test.ts @@ -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' }, diff --git a/backend/tests/controllers/teachers.test.ts b/backend/tests/controllers/teachers.test.ts index 9fa3e501..dc6f05b2 100644 --- a/backend/tests/controllers/teachers.test.ts +++ b/backend/tests/controllers/teachers.test.ts @@ -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; 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 () => { + const teacher = { + id: 'coolteacher', + username: 'coolteacher', + firstName: 'New', + lastName: 'Teacher', + } req = { - body: { - username: 'coolteacher', - firstName: 'New', - lastName: 'Teacher', - }, + 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; diff --git a/frontend/src/controllers/students.ts b/frontend/src/controllers/students.ts index 2e765e0a..425bc018 100644 --- a/frontend/src/controllers/students.ts +++ b/frontend/src/controllers/students.ts @@ -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); } diff --git a/frontend/src/queries/students.ts b/frontend/src/queries/students.ts index f94d85ed..d1f06fa6 100644 --- a/frontend/src/queries/students.ts +++ b/frontend/src/queries/students.ts @@ -121,6 +121,17 @@ export function useStudentJoinRequestsQuery(username: MaybeRefOrGetter, + classId: MaybeRefOrGetter, +) { + 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 */