fix: student join req by class route + teacher return post put delete + status

This commit is contained in:
Gabriellvl 2025-04-01 14:24:06 +02:00
parent c0995d3933
commit 912369f87e
9 changed files with 106 additions and 47 deletions

View file

@ -4,7 +4,7 @@ import {
createStudent, createStudent,
deleteClassJoinRequest, deleteClassJoinRequest,
deleteStudent, deleteStudent,
getAllStudents, getAllStudents, getJoinRequestByStudentClass,
getJoinRequestsByStudent, getJoinRequestsByStudent,
getStudent, getStudent,
getStudentAssignments, getStudentAssignments,
@ -116,7 +116,7 @@ export async function createStudentRequestHandler(req: Request, res: Response):
res.json({ request }); 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; const username = req.params.username;
requireFields({ username }); requireFields({ username });
@ -124,6 +124,15 @@ export async function getStudentRequestHandler(req: Request, res: Response): Pro
res.json({ requests }); 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) { export async function deleteClassJoinRequestHandler(req: Request, res: Response) {
const username = req.params.username as string; const username = req.params.username as string;
const classId = req.params.classId; const classId = req.params.classId;

View file

@ -41,16 +41,16 @@ export async function createTeacherHandler(req: Request, res: Response) {
const userData = req.body as TeacherDTO; const userData = req.body as TeacherDTO;
await createTeacher(userData); const teacher = await createTeacher(userData);
res.sendStatus(201); res.json({ teacher });
} }
export async function deleteTeacherHandler(req: Request, res: Response) { export async function deleteTeacherHandler(req: Request, res: Response) {
const username = req.params.username; const username = req.params.username;
requireFields({ username }); requireFields({ username });
await deleteTeacher(username); const teacher = await deleteTeacher(username);
res.sendStatus(200); res.json({ teacher });
} }
export async function getTeacherClassHandler(req: Request, res: Response): Promise<void> { 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 const accepted = req.body.accepted !== 'false'; // Default = true
requireFields({ studentUsername, classId }); requireFields({ studentUsername, classId });
await updateClassJoinRequestStatus(studentUsername, classId, accepted); const request = await updateClassJoinRequestStatus(studentUsername, classId, accepted);
res.sendStatus(200); res.json({ request });
} }

View file

@ -1,12 +1,19 @@
import express from 'express'; 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 }); const router = express.Router({ mergeParams: true });
router.get('/', getStudentRequestHandler); router.get('/', getStudentRequestsHandler);
router.post('/', createStudentRequestHandler); router.post('/', createStudentRequestHandler);
router.get('/:classId', getStudentRequestHandler);
router.delete('/:classId', deleteClassJoinRequestHandler); router.delete('/:classId', deleteClassJoinRequestHandler);
export default router; export default router;

View file

@ -125,10 +125,10 @@ 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) { export async function createClassJoinRequest(username: string, classId: string) {
const requestRepo = getClassJoinRequestRepository(); 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 cls = await fetchClass(classId);
const request = mapToStudentRequest(student, cls); const request = mapToStudentRequest(student, cls);
@ -136,19 +136,33 @@ export async function createClassJoinRequest(studentUsername: string, classId: s
return mapToStudentRequestDTO(request); return mapToStudentRequestDTO(request);
} }
export async function getJoinRequestsByStudent(studentUsername: string) { export async function getJoinRequestsByStudent(username: string) {
const requestRepo = getClassJoinRequestRepository(); const requestRepo = getClassJoinRequestRepository();
const student = await fetchStudent(studentUsername); const student = await fetchStudent(username);
const requests = await requestRepo.findAllRequestsBy(student); const requests = await requestRepo.findAllRequestsBy(student);
return requests.map(mapToStudentRequestDTO); return requests.map(mapToStudentRequestDTO);
} }
export async function deleteClassJoinRequest(studentUsername: string, classId: string) { export async function getJoinRequestByStudentClass(username: string, classId: string){
const requestRepo = getClassJoinRequestRepository(); 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 cls = await fetchClass(classId);
const request = await requestRepo.findByStudentAndClass(student, cls); const request = await requestRepo.findByStudentAndClass(student, cls);

View file

@ -51,19 +51,21 @@ export async function getTeacher(username: string): Promise<TeacherDTO> {
return mapToTeacherDTO(user); return mapToTeacherDTO(user);
} }
export async function createTeacher(userData: TeacherDTO): Promise<void> { export async function createTeacher(userData: TeacherDTO): Promise<TeacherDTO> {
const teacherRepository: TeacherRepository = getTeacherRepository(); const teacherRepository: TeacherRepository = getTeacherRepository();
const newTeacher = mapToTeacher(userData); const newTeacher = mapToTeacher(userData);
await teacherRepository.save(newTeacher, { preventOverwrite: true }); 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(); 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); await teacherRepository.deleteByUsername(username);
return mapToTeacherDTO(teacher);
} }
async function fetchClassesByTeacher(username: string): Promise<ClassDTO[]> { 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 learningObjectRepository: LearningObjectRepository = getLearningObjectRepository();
const learningObjects: LearningObject[] = await learningObjectRepository.findAllByTeacher(teacher); const learningObjects: LearningObject[] = await learningObjectRepository.findAllByTeacher(teacher);
// Console.log(learningObjects)
// TODO returns empty
if (!learningObjects || learningObjects.length === 0) { if (!learningObjects || learningObjects.length === 0) {
return []; return [];
} }
@ -138,7 +137,7 @@ export async function getJoinRequestsByClass(classId: string): Promise<StudentRe
return requests.map(mapToStudentRequestDTO); 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 requestRepo: ClassJoinRequestRepository = getClassJoinRequestRepository();
const classRepo: ClassRepository = getClassRepository(); const classRepo: ClassRepository = getClassRepository();
@ -158,4 +157,5 @@ export async function updateClassJoinRequestStatus(studentUsername: string, clas
request.status = accepted ? ClassJoinRequestStatus.Accepted : ClassJoinRequestStatus.Declined; request.status = accepted ? ClassJoinRequestStatus.Accepted : ClassJoinRequestStatus.Declined;
await requestRepo.save(request); await requestRepo.save(request);
return mapToStudentRequestDTO(request);
} }

View file

@ -11,8 +11,8 @@ import {
getStudentSubmissionsHandler, getStudentSubmissionsHandler,
getStudentQuestionsHandler, getStudentQuestionsHandler,
createStudentRequestHandler, createStudentRequestHandler,
getStudentRequestHandler, getStudentRequestsHandler,
deleteClassJoinRequestHandler, deleteClassJoinRequestHandler, getStudentRequestHandler,
} from '../../src/controllers/students.js'; } from '../../src/controllers/students.js';
import { TEST_STUDENTS } from '../test_assets/users/students.testdata.js'; import { TEST_STUDENTS } from '../test_assets/users/students.testdata.js';
import { NotFoundException } from '../../src/exceptions/not-found-exception.js'; import { NotFoundException } from '../../src/exceptions/not-found-exception.js';
@ -176,7 +176,7 @@ describe('Student controllers', () => {
params: { username: 'PinkFloyd' }, params: { username: 'PinkFloyd' },
}; };
await getStudentRequestHandler(req as Request, res as Response); await getStudentRequestsHandler(req as Request, res as Response);
expect(jsonMock).toHaveBeenCalledWith( expect(jsonMock).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
@ -189,6 +189,20 @@ describe('Student controllers', () => {
expect(result.requests.length).toBeGreaterThan(0); 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 () => { it('Create join request', async () => {
req = { req = {
params: { username: 'Noordkaap' }, params: { username: 'Noordkaap' },

View file

@ -15,7 +15,7 @@ import {
} from '../../src/controllers/teachers.js'; } from '../../src/controllers/teachers.js';
import { BadRequestException } from '../../src/exceptions/bad-request-exception.js'; import { BadRequestException } from '../../src/exceptions/bad-request-exception.js';
import { EntityAlreadyExistsException } from '../../src/exceptions/entity-already-exists-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'; import { TeacherDTO } from '../../src/interfaces/teacher.js';
describe('Teacher controllers', () => { describe('Teacher controllers', () => {
@ -23,8 +23,6 @@ describe('Teacher controllers', () => {
let res: Partial<Response>; let res: Partial<Response>;
let jsonMock: Mock; let jsonMock: Mock;
let statusMock: Mock;
let sendStatusMock: Mock;
beforeAll(async () => { beforeAll(async () => {
await setupTestApp(); await setupTestApp();
@ -32,12 +30,8 @@ describe('Teacher controllers', () => {
beforeEach(() => { beforeEach(() => {
jsonMock = vi.fn(); jsonMock = vi.fn();
statusMock = vi.fn().mockReturnThis();
sendStatusMock = vi.fn().mockReturnThis();
res = { res = {
json: jsonMock, json: jsonMock,
status: statusMock,
sendStatus: sendStatusMock,
}; };
}); });
@ -52,33 +46,37 @@ describe('Teacher controllers', () => {
it('Teacher not found', async () => { it('Teacher not found', async () => {
req = { params: { username: 'doesnotexist' } }; 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 () => { it('No username', async () => {
req = { params: {} }; 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 () => { it('Create and delete teacher', async () => {
const teacher = {
id: 'coolteacher',
username: 'coolteacher',
firstName: 'New',
lastName: 'Teacher',
}
req = { req = {
body: { body: teacher,
username: 'coolteacher',
firstName: 'New',
lastName: 'Teacher',
},
}; };
await createTeacherHandler(req as Request, res as Response); 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' } }; req = { params: { username: 'coolteacher' } };
await deleteTeacherHandler(req as Request, res as Response); 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 () => { 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 () => { it('Create teacher no body', async () => {
req = { body: {} }; 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 () => { it('Teacher list', async () => {
@ -193,15 +193,15 @@ describe('Teacher controllers', () => {
body: { accepted: 'true' }, 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 = { req = {
params: { username: 'PinkFloyd' }, 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; const status: boolean = jsonMock.mock.lastCall?.[0].requests[0].status;
expect(status).toBeTruthy; expect(status).toBeTruthy;

View file

@ -45,6 +45,10 @@ export class StudentController extends BaseController {
return this.get<{ requests: any[] }>(`/${username}/joinRequests`); 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) { createJoinRequest(username: string, classId: string) {
return this.post(`/${username}/joinRequests}`, classId); return this.post(`/${username}/joinRequests}`, classId);
} }

View file

@ -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 * Mutation to create a join request for a class
*/ */