diff --git a/backend/src/controllers/students.ts b/backend/src/controllers/students.ts index 1fbfeec3..cabdd5c9 100644 --- a/backend/src/controllers/students.ts +++ b/backend/src/controllers/students.ts @@ -9,13 +9,11 @@ import { getStudentClasses, getStudentGroups, getStudentQuestions, - getStudentSubmissions, updateClassJoinRequestStatus, + getStudentSubmissions, } from '../services/students.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 { const full = req.query.full === 'true'; @@ -41,16 +39,16 @@ export async function createStudentHandler(req: Request, res: Response) { const userData = req.body as StudentDTO; - const student = await createStudent(userData); - res.status(201).json({ student }); + await createStudent(userData); + res.status(201); } export async function deleteStudentHandler(req: Request, res: Response) { const username = req.params.username; requireFields({ username }); - const student = await deleteStudent(username); - res.status(200).json({ student }); + await deleteStudent(username); + res.status(200); } export async function getStudentClassesHandler(req: Request, res: Response): Promise { @@ -133,16 +131,6 @@ export async function getStudentRequestHandler(req: Request, res: Response): Pro 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.params.username as string; const classId = req.params.classId; diff --git a/backend/src/controllers/teachers.ts b/backend/src/controllers/teachers.ts index dd45ef02..e3571d63 100644 --- a/backend/src/controllers/teachers.ts +++ b/backend/src/controllers/teachers.ts @@ -3,10 +3,10 @@ import { createTeacher, deleteTeacher, getAllTeachers, - getClassesByTeacher, + getClassesByTeacher, getJoinRequestsByClass, getStudentsByTeacher, getTeacher, - getTeacherQuestions + getTeacherQuestions, updateClassJoinRequestStatus } from '../services/teachers.js'; import { ClassDTO } from '../interfaces/class.js'; import { StudentDTO } from '../interfaces/student.js'; @@ -19,11 +19,6 @@ export async function getAllTeachersHandler(req: Request, res: Response): Promis const teachers: TeacherDTO[] | string[] = await getAllTeachers(full); - if (!teachers) { - res.status(404).json({ error: `Teachers not found.` }); - return; - } - res.json({ teachers }); } @@ -31,59 +26,45 @@ export async function getTeacherHandler(req: Request, res: Response): Promise { const username = req.params.username as string; const full = req.query.full === 'true'; - - if (!username) { - return; - } + requireFields({ username }); const classes: ClassDTO[] | string[] = await getClassesByTeacher(username, full); - res.status(201).json(classes); + res.json(classes); } export async function getTeacherStudentHandler(req: Request, res: Response): Promise { const username = req.params.username as string; const full = req.query.full === 'true'; - - if (!username) { - return; - } + requireFields({ username }); const students: StudentDTO[] | string[] = await getStudentsByTeacher(username, full); @@ -93,12 +74,28 @@ export async function getTeacherStudentHandler(req: Request, res: Response): Pro export async function getTeacherQuestionHandler(req: Request, res: Response): Promise { const username = req.params.username as string; const full = req.query.full === 'true'; - - if (!username) { - return; - } + requireFields({ username }); const questions: QuestionDTO[] | QuestionId[] = await getTeacherQuestions(username, full); res.json({ questions }); } + +export async function getStudentJoinRequestHandler(req: Request, res: Response) { + const username = req.query.username as string; + const classId = req.params.classId; + requireFields({ username, classId }); + + const joinRequests = await getJoinRequestsByClass(classId); + res.json({ joinRequests }); +} + +export async function updateStudentJoinRequestHandler(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 }); + + await updateClassJoinRequestStatus(username, classId, accepted); + res.status(200); +} diff --git a/backend/src/data/classes/class-join-request-repository.ts b/backend/src/data/classes/class-join-request-repository.ts index 04152a26..b2d699ee 100644 --- a/backend/src/data/classes/class-join-request-repository.ts +++ b/backend/src/data/classes/class-join-request-repository.ts @@ -1,6 +1,6 @@ import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; import { Class } from '../../entities/classes/class.entity.js'; -import { ClassJoinRequest } from '../../entities/classes/class-join-request.entity.js'; +import {ClassJoinRequest, ClassJoinRequestStatus} from '../../entities/classes/class-join-request.entity.js'; import { Student } from '../../entities/users/student.entity.js'; export class ClassJoinRequestRepository extends DwengoEntityRepository { @@ -8,7 +8,7 @@ export class ClassJoinRequestRepository extends DwengoEntityRepository { - return this.findAll({ where: { class: clazz } }); + return this.findAll({ where: { class: clazz, status: ClassJoinRequestStatus.Open, } }); // TODO check if works like this } public findByStudentAndClass(requester: Student, clazz: Class): Promise { return this.findOne({ requester, class: clazz }); diff --git a/backend/src/routes/teachers.ts b/backend/src/routes/teachers.ts index 8e7f709d..058bc0a3 100644 --- a/backend/src/routes/teachers.ts +++ b/backend/src/routes/teachers.ts @@ -2,11 +2,11 @@ import express from 'express'; import { createTeacherHandler, deleteTeacherHandler, - getAllTeachersHandler, + getAllTeachersHandler, getStudentJoinRequestHandler, getTeacherClassHandler, getTeacherHandler, getTeacherQuestionHandler, - getTeacherStudentHandler, + getTeacherStudentHandler, updateStudentJoinRequestHandler, } from '../controllers/teachers.js'; const router = express.Router(); @@ -25,6 +25,10 @@ router.get('/:username/students', getTeacherStudentHandler); router.get('/:username/questions', getTeacherQuestionHandler); +router.get('/:username/joinRequests/:classId', getStudentJoinRequestHandler); + +router.put('/:username/joinRequests/:classId/:studentUsername', updateStudentJoinRequestHandler); + // Invitations to other classes a teacher received router.get('/:id/invitations', (req, res) => { res.json({ diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts index 9360ed54..d9cbceb5 100644 --- a/backend/src/services/students.ts +++ b/backend/src/services/students.ts @@ -44,31 +44,25 @@ export async function getStudent(username: string): Promise { return mapToStudentDTO(user); } -export async function createStudent(userData: StudentDTO): Promise { +export async function createStudent(userData: StudentDTO): Promise { const studentRepository = getStudentRepository(); const user = await studentRepository.findByUsername(userData.username); if (user) { - throw new ConflictException("Username already exists"); + throw new ConflictException("Student with that sername already exists"); } const newStudent = studentRepository.create(mapToStudent(userData)); await studentRepository.save(newStudent); - return mapToStudentDTO(newStudent); } -export async function deleteStudent(username: string): Promise { +export async function deleteStudent(username: string): Promise { const studentRepository = getStudentRepository(); - const user = await studentRepository.findByUsername(username); - - if (!user) { - throw new NotFoundException("Student with username not found"); - } + await fetchStudent(username); // throws error if it does not exist await studentRepository.deleteByUsername(username); - return mapToStudentDTO(user); } export async function getStudentClasses(username: string, full: boolean): Promise { @@ -153,7 +147,6 @@ export async function createClassJoinRequest(studentUsername: string, classId: s }); await requestRepo.save(request); - return request; } export async function getJoinRequestsByStudent(studentUsername: string) { @@ -165,30 +158,6 @@ export async function getJoinRequestsByStudent(studentUsername: string) { return requests.map(mapToStudentRequestDTO); } -// TODO naar teacher -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(); @@ -207,7 +176,6 @@ export async function deleteClassJoinRequest(studentUsername: string, classId: s } await requestRepo.deleteBy(student, cls); - return true; } diff --git a/backend/src/services/teachers.ts b/backend/src/services/teachers.ts index 612cf6fb..bc5a2d0a 100644 --- a/backend/src/services/teachers.ts +++ b/backend/src/services/teachers.ts @@ -1,13 +1,19 @@ import { + getClassJoinRequestRepository, getClassRepository, getLearningObjectRepository, - getQuestionRepository, + getQuestionRepository, getStudentRepository, getTeacherRepository, } from '../data/repositories.js'; import { ClassDTO, mapToClassDTO } from '../interfaces/class.js'; import { getClassStudents } from './class.js'; import {mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId} from '../interfaces/question.js'; import { mapToTeacher, mapToTeacherDTO, TeacherDTO } from '../interfaces/teacher.js'; +import {ConflictException, NotFoundException} from "../exceptions"; +import {Teacher} from "../entities/users/teacher.entity"; +import {fetchStudent} from "./students"; +import {ClassJoinRequestStatus} from "../entities/classes/class-join-request.entity"; +import {mapToStudentRequestDTO} from "../interfaces/student-request"; export async function getAllTeachers(full: boolean): Promise { const teacherRepository = getTeacherRepository(); @@ -19,51 +25,45 @@ export async function getAllTeachers(full: boolean): Promise user.username); } -export async function getTeacher(username: string): Promise { - const teacherRepository = getTeacherRepository(); - const user = await teacherRepository.findByUsername(username); - return user ? mapToTeacherDTO(user) : null; -} - -export async function createTeacher(userData: TeacherDTO): Promise { - const teacherRepository = getTeacherRepository(); - - try { - const newTeacher = teacherRepository.create(mapToTeacher(userData)); - await teacherRepository.save(newTeacher); - - return mapToTeacherDTO(newTeacher); - } catch (e) { - console.log(e); - return null; - } -} - -export async function deleteTeacher(username: string): Promise { - const teacherRepository = getTeacherRepository(); - - const user = await teacherRepository.findByUsername(username); +export async function fetchTeacher(username: string): Promise { + const studentRepository = getStudentRepository(); + const user = await studentRepository.findByUsername(username); if (!user) { - return null; + throw new NotFoundException("Teacher with username not found"); } - try { - await teacherRepository.deleteByUsername(username); + return user; +} - return mapToTeacherDTO(user); - } catch (e) { - console.log(e); - return null; +export async function getTeacher(username: string): Promise { + const user = await fetchTeacher(username); + return mapToTeacherDTO(user); +} + +export async function createTeacher(userData: TeacherDTO): Promise { + const teacherRepository = getTeacherRepository(); + + const user = await teacherRepository.findByUsername(userData.username); + + if (user){ + throw new ConflictException("Teacher with that username already exists"); } + + const newTeacher = teacherRepository.create(mapToTeacher(userData)); + await teacherRepository.save(newTeacher); +} + +export async function deleteTeacher(username: string): Promise { + const teacherRepository = getTeacherRepository(); + + await fetchTeacher(username); // throws error if it does not exist + + await teacherRepository.deleteByUsername(username); } async function fetchClassesByTeacher(username: string): Promise { - const teacherRepository = getTeacherRepository(); - const teacher = await teacherRepository.findByUsername(username); - if (!teacher) { - return []; - } + const teacher = await fetchTeacher(username); const classRepository = getClassRepository(); const classes = await classRepository.findByTeacher(teacher); @@ -81,6 +81,11 @@ export async function getClassesByTeacher(username: string, full: boolean): Prom export async function getStudentsByTeacher(username: string, full: boolean) { const classes = await fetchClassesByTeacher(username); + + if (!classes || classes.length === 0){ + return []; + } + const classIds = classes.map((cls) => cls.id); const students = (await Promise.all(classIds.map(async (id) => getClassStudents(id)))).flat(); @@ -91,16 +96,16 @@ export async function getStudentsByTeacher(username: string, full: boolean) { } export async function getTeacherQuestions(username: string, full: boolean): Promise { - const teacherRepository = getTeacherRepository(); - const teacher = await teacherRepository.findByUsername(username); - if (!teacher) { - throw new Error(`Teacher with username '${username}' not found.`); - } + const teacher = await fetchTeacher(username); // Find all learning objects that this teacher manages const learningObjectRepository = getLearningObjectRepository(); const learningObjects = await learningObjectRepository.findAllByTeacher(teacher); + if (!learningObjects || learningObjects.length === 0){ + return []; + } + // Fetch all questions related to these learning objects const questionRepository = getQuestionRepository(); const questions = await questionRepository.findAllByLearningObjects(learningObjects); @@ -112,3 +117,38 @@ export async function getTeacherQuestions(username: string, full: boolean): Prom return questionsDTO.map(mapToQuestionId); } + +export async function getJoinRequestsByClass( classId: string ){ + const classRepository = getClassRepository(); + const cls = await classRepository.findById(classId); + + if (!cls) { + throw new NotFoundException("Class with id not found"); + } + + const requestRepo = getClassJoinRequestRepository(); + const requests = await requestRepo.findAllOpenRequestsTo(cls); + 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.findByStudentAndClass(student, cls); + + if (!request) { + throw new NotFoundException('Join request not found'); + } + + request.status = accepted ? ClassJoinRequestStatus.Accepted : ClassJoinRequestStatus.Declined; + + await requestRepo.save(request); +}