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,
|
||||||
|
};
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
throw new ConflictException("Username already exists");
|
||||||
|
}
|
||||||
|
|
||||||
const newStudent = studentRepository.create(mapToStudent(userData));
|
const newStudent = studentRepository.create(mapToStudent(userData));
|
||||||
await studentRepository.save(newStudent);
|
await studentRepository.save(newStudent);
|
||||||
|
|
||||||
return mapToStudentDTO(newStudent);
|
return mapToStudentDTO(newStudent);
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
});
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue