Merge remote-tracking branch 'origin/feat/endpoints-beschermen-met-authenticatie-#105' into feat/endpoints-beschermen-met-authenticatie-#105
# Conflicts: # backend/src/middleware/auth/checks/auth-checks.ts # backend/src/middleware/auth/checks/class-auth-checks.ts # backend/src/routes/teachers.ts # frontend/src/views/assignments/UserAssignments.vue
This commit is contained in:
commit
7da52284e6
40 changed files with 1042 additions and 541 deletions
|
@ -62,6 +62,11 @@ export async function getAllSubmissionsHandler(req: Request, res: Response): Pro
|
|||
|
||||
// TODO: gerald moet nog dingen toevoegen aan de databank voor dat dit gefinaliseerd kan worden
|
||||
export async function createSubmissionHandler(req: Request, res: Response): Promise<void> {
|
||||
const submitter = req.body.submitter;
|
||||
const usernameSubmitter = req.body.submitter.username;
|
||||
const group = req.body.group;
|
||||
requireFields({ group, submitter, usernameSubmitter });
|
||||
|
||||
const submissionDTO = req.body as SubmissionDTO;
|
||||
const submission = await createSubmission(submissionDTO);
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ import {
|
|||
getJoinRequestsByClass,
|
||||
getStudentsByTeacher,
|
||||
getTeacher,
|
||||
getTeacherQuestions,
|
||||
updateClassJoinRequestStatus,
|
||||
} from '../services/teachers.js';
|
||||
import { requireFields } from './error-helper.js';
|
||||
|
@ -70,16 +69,6 @@ export async function getTeacherStudentHandler(req: Request, res: Response): Pro
|
|||
res.json({ students });
|
||||
}
|
||||
|
||||
export async function getTeacherQuestionHandler(req: Request, res: Response): Promise<void> {
|
||||
const username = req.params.username;
|
||||
const full = req.query.full === 'true';
|
||||
requireFields({ username });
|
||||
|
||||
const questions = await getTeacherQuestions(username, full);
|
||||
|
||||
res.json({ questions });
|
||||
}
|
||||
|
||||
export async function getStudentJoinRequestHandler(req: Request, res: Response): Promise<void> {
|
||||
const classId = req.params.classId;
|
||||
requireFields({ classId });
|
||||
|
|
|
@ -2,7 +2,6 @@ import { DwengoEntityRepository } from '../dwengo-entity-repository.js';
|
|||
import { LearningObject } from '../../entities/content/learning-object.entity.js';
|
||||
import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js';
|
||||
import { Language } from '@dwengo-1/common/util/language';
|
||||
import { Teacher } from '../../entities/users/teacher.entity.js';
|
||||
|
||||
export class LearningObjectRepository extends DwengoEntityRepository<LearningObject> {
|
||||
public async findByIdentifier(identifier: LearningObjectIdentifier): Promise<LearningObject | null> {
|
||||
|
@ -32,11 +31,4 @@ export class LearningObjectRepository extends DwengoEntityRepository<LearningObj
|
|||
}
|
||||
);
|
||||
}
|
||||
|
||||
public async findAllByTeacher(teacher: Teacher): Promise<LearningObject[]> {
|
||||
return this.find(
|
||||
{ admins: teacher },
|
||||
{ populate: ['admins'] } // Make sure to load admin relations
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,9 @@ export class Assignment {
|
|||
@Property({ type: 'string' })
|
||||
learningPathHruid!: string;
|
||||
|
||||
@Property({ type: 'datetime', nullable: true })
|
||||
deadline?: Date;
|
||||
|
||||
@Enum({
|
||||
items: () => Language,
|
||||
})
|
||||
|
|
|
@ -20,6 +20,7 @@ export function mapToAssignmentDTO(assignment: Assignment): AssignmentDTO {
|
|||
description: assignment.description,
|
||||
learningPath: assignment.learningPathHruid,
|
||||
language: assignment.learningPathLanguage,
|
||||
deadline: assignment.deadline ?? new Date(),
|
||||
groups: assignment.groups.map((group) => mapToGroupDTO(group, assignment.within)),
|
||||
};
|
||||
}
|
||||
|
@ -31,6 +32,7 @@ export function mapToAssignment(assignmentData: AssignmentDTO, cls: Class): Assi
|
|||
description: assignmentData.description,
|
||||
learningPathHruid: assignmentData.learningPath,
|
||||
learningPathLanguage: languageMap[assignmentData.language],
|
||||
deadline: assignmentData.deadline,
|
||||
groups: [],
|
||||
});
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import * as express from 'express';
|
|||
import { RequestHandler } from 'express';
|
||||
import { UnauthorizedException } from '../../../exceptions/unauthorized-exception.js';
|
||||
import { ForbiddenException } from '../../../exceptions/forbidden-exception.js';
|
||||
import { envVars, getEnvVar } from '../../../util/envVars.js';
|
||||
import {AccountType} from "@dwengo-1/common/util/account-types";
|
||||
|
||||
/**
|
||||
|
@ -15,6 +16,17 @@ import {AccountType} from "@dwengo-1/common/util/account-types";
|
|||
export function authorize<P, ResBody, ReqBody, ReqQuery, Locals extends Record<string, unknown>>(
|
||||
accessCondition: (auth: AuthenticationInfo, req: AuthenticatedRequest<P, ResBody, ReqBody, ReqQuery, Locals>) => boolean | Promise<boolean>
|
||||
): RequestHandler<P, ResBody, ReqBody, ReqQuery, Locals> {
|
||||
// Bypass authentication during testing
|
||||
if (getEnvVar(envVars.RunMode) === 'test') {
|
||||
return async (
|
||||
_req: AuthenticatedRequest<P, ResBody, ReqBody, ReqQuery, Locals>,
|
||||
_res: express.Response,
|
||||
next: express.NextFunction
|
||||
): Promise<void> => {
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
return async (
|
||||
req: AuthenticatedRequest<P, ResBody, ReqBody, ReqQuery, Locals>,
|
||||
_res: express.Response,
|
||||
|
|
|
@ -3,7 +3,7 @@ import { AuthenticationInfo } from '../authentication-info.js';
|
|||
import { AuthenticatedRequest } from '../authenticated-request.js';
|
||||
import { fetchClass } from '../../../services/classes.js';
|
||||
import { mapToUsername } from '../../../interfaces/user.js';
|
||||
import {getAllInvitations} from "../../../services/teacher-invitations";
|
||||
import { getAllInvitations } from '../../../services/teacher-invitations.js';
|
||||
import {AccountType} from "@dwengo-1/common/util/account-types";
|
||||
|
||||
async function teaches(teacherUsername: string, classId: string): Promise<boolean> {
|
||||
|
@ -51,7 +51,7 @@ export const onlyAllowIfInClassOrInvited = authorize(async (auth: Authentication
|
|||
const clazz = await fetchClass(classId);
|
||||
if (auth.accountType === AccountType.Teacher) {
|
||||
const invitations = await getAllInvitations(auth.username, false);
|
||||
return clazz.teachers.map(mapToUsername).includes(auth.username) || invitations.some(invitation => invitation.classId === classId);
|
||||
return clazz.teachers.map(mapToUsername).includes(auth.username) || invitations.some((invitation) => invitation.classId === classId);
|
||||
}
|
||||
return clazz.students.map(mapToUsername).includes(auth.username);
|
||||
});
|
||||
|
|
|
@ -5,25 +5,31 @@ import { authenticatedOnly, studentsOnly, teachersOnly } from '../middleware/aut
|
|||
const router = express.Router();
|
||||
|
||||
// Returns auth configuration for frontend
|
||||
router.get('/config', handleGetFrontendAuthConfig)
|
||||
router.get('/config', handleGetFrontendAuthConfig);
|
||||
|
||||
router.get('/testAuthenticatedOnly', authenticatedOnly, (_req, res) => {
|
||||
/* #swagger.security = [{ "student": [ ] }, { "teacher": [ ] }] */
|
||||
/* #swagger.security = [{ "studentProduction": [ ] }, { "teacherProduction": [ ] }, { "studentStaging": [ ] }, { "teacherStaging": [ ] }, { "studentDev": [ ] }, { "teacherDev": [ ] }] */
|
||||
res.json({ message: 'If you see this, you should be authenticated!' });
|
||||
});
|
||||
|
||||
router.get('/testStudentsOnly', studentsOnly, (_req, res) => {
|
||||
/* #swagger.security = [{ "student": [ ] }] */
|
||||
/* #swagger.security = [{ "studentProduction": [ ] }, { "studentStaging": [ ] }, { "studentDev": [ ] }] */
|
||||
res.json({ message: 'If you see this, you should be a student!' });
|
||||
});
|
||||
|
||||
router.get('/testTeachersOnly', teachersOnly, (_req, res) => {
|
||||
/* #swagger.security = [{ "teacher": [ ] }] */
|
||||
/* #swagger.security = [{ "teacherProduction": [ ] }, { "teacherStaging": [ ] }, { "teacherDev": [ ] }] */
|
||||
res.json({ message: 'If you see this, you should be a teacher!' });
|
||||
});
|
||||
|
||||
// This endpoint is called by the client when the user has just logged in.
|
||||
// It creates or updates the user entity based on the authentication data the endpoint was called with.
|
||||
router.post('/hello', authenticatedOnly, postHelloHandler);
|
||||
router.post(
|
||||
'/hello',
|
||||
authenticatedOnly,
|
||||
/*
|
||||
#swagger.security = [{ "studentProduction": [ ] }, { "teacherProduction": [ ] }, { "studentStaging": [ ] }, { "teacherStaging": [ ] }, { "studentDev": [ ] }, { "teacherDev": [ ] }]
|
||||
*/ postHelloHandler
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
} from '../controllers/classes.js';
|
||||
import assignmentRouter from './assignments.js';
|
||||
import { adminOnly, teachersOnly } from '../middleware/auth/checks/auth-checks.js';
|
||||
import {onlyAllowIfInClass, onlyAllowIfInClassOrInvited} from '../middleware/auth/checks/class-auth-checks.js';
|
||||
import { onlyAllowIfInClass, onlyAllowIfInClassOrInvited } from '../middleware/auth/checks/class-auth-checks.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
|
|
|
@ -18,12 +18,30 @@ router.get('/', (_, res: Response) => {
|
|||
});
|
||||
});
|
||||
|
||||
router.use('/student', studentRouter /* #swagger.tags = ['Student'] */);
|
||||
router.use('/teacher', teacherRouter /* #swagger.tags = ['Teacher'] */);
|
||||
router.use('/class', classRouter /* #swagger.tags = ['Class'] */);
|
||||
router.use('/auth', authRouter /* #swagger.tags = ['Auth'] */);
|
||||
router.use('/theme', themeRoutes /* #swagger.tags = ['Theme'] */);
|
||||
router.use('/learningPath', learningPathRoutes /* #swagger.tags = ['Learning Path'] */);
|
||||
router.use('/learningObject', learningObjectRoutes /* #swagger.tags = ['Learning Object'] */);
|
||||
router.use(
|
||||
'/class',
|
||||
classRouter /* #swagger.tags = ['Class'], #swagger.security = [{ "studentProduction": [ ] }, { "teacherProduction": [ ] }, { "studentStaging": [ ] }, { "teacherStaging": [ ] }, { "studentDev": [ ] }, { "teacherDev": [ ] }] */
|
||||
);
|
||||
router.use(
|
||||
'/learningObject',
|
||||
learningObjectRoutes /* #swagger.tags = ['Learning Object'], #swagger.security = [{ "studentProduction": [ ] }, { "teacherProduction": [ ] }, { "studentStaging": [ ] }, { "teacherStaging": [ ] }, { "studentDev": [ ] }, { "teacherDev": [ ] }] */
|
||||
);
|
||||
router.use(
|
||||
'/learningPath',
|
||||
learningPathRoutes /* #swagger.tags = ['Learning Path'], #swagger.security = [{ "studentProduction": [ ] }, { "teacherProduction": [ ] }, { "studentStaging": [ ] }, { "teacherStaging": [ ] }, { "studentDev": [ ] }, { "teacherDev": [ ] }] */
|
||||
);
|
||||
router.use(
|
||||
'/student',
|
||||
studentRouter /* #swagger.tags = ['Student'], #swagger.security = [{ "studentProduction": [ ] }, { "teacherProduction": [ ] }, { "studentStaging": [ ] }, { "teacherStaging": [ ] }, { "studentDev": [ ] }, { "teacherDev": [ ] }] */
|
||||
);
|
||||
router.use(
|
||||
'/teacher',
|
||||
teacherRouter /* #swagger.tags = ['Teacher'], #swagger.security = [{ "studentProduction": [ ] }, { "teacherProduction": [ ] }, { "studentStaging": [ ] }, { "teacherStaging": [ ] }, { "studentDev": [ ] }, { "teacherDev": [ ] }] */
|
||||
);
|
||||
router.use(
|
||||
'/theme',
|
||||
themeRoutes /* #swagger.tags = ['Theme'], #swagger.security = [{ "studentProduction": [ ] }, { "teacherProduction": [ ] }, { "studentStaging": [ ] }, { "teacherStaging": [ ] }, { "studentDev": [ ] }, { "teacherDev": [ ] }] */
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
|
|
@ -6,7 +6,6 @@ import {
|
|||
getStudentJoinRequestHandler,
|
||||
getTeacherClassHandler,
|
||||
getTeacherHandler,
|
||||
getTeacherQuestionHandler,
|
||||
getTeacherStudentHandler,
|
||||
updateStudentJoinRequestHandler,
|
||||
} from '../controllers/teachers.js';
|
||||
|
@ -29,8 +28,6 @@ router.get('/:username/classes', preventImpersonation, getTeacherClassHandler);
|
|||
|
||||
router.get('/:username/students', preventImpersonation, getTeacherStudentHandler);
|
||||
|
||||
router.get('/:username/questions', preventImpersonation, getTeacherQuestionHandler);
|
||||
|
||||
router.get('/:username/joinRequests/:classId', onlyAllowTeacherOfClass, getStudentJoinRequestHandler);
|
||||
|
||||
router.put('/:username/joinRequests/:classId/:studentUsername', onlyAllowTeacherOfClass, updateStudentJoinRequestHandler);
|
||||
|
|
|
@ -43,7 +43,7 @@ export async function fetchStudent(username: string): Promise<Student> {
|
|||
const user = await studentRepository.findByUsername(username);
|
||||
|
||||
if (!user) {
|
||||
throw new NotFoundException('Student with username not found');
|
||||
throw new NotFoundException(`Student with username ${username} not found`);
|
||||
}
|
||||
|
||||
return user;
|
||||
|
|
|
@ -1,12 +1,5 @@
|
|||
import {
|
||||
getClassJoinRequestRepository,
|
||||
getClassRepository,
|
||||
getLearningObjectRepository,
|
||||
getQuestionRepository,
|
||||
getTeacherRepository,
|
||||
} from '../data/repositories.js';
|
||||
import { getClassJoinRequestRepository, getClassRepository, getTeacherRepository } from '../data/repositories.js';
|
||||
import { mapToClassDTO } from '../interfaces/class.js';
|
||||
import { mapToQuestionDTO, mapToQuestionDTOId } from '../interfaces/question.js';
|
||||
import { mapToTeacher, mapToTeacherDTO } from '../interfaces/teacher.js';
|
||||
import { Teacher } from '../entities/users/teacher.entity.js';
|
||||
import { fetchStudent } from './students.js';
|
||||
|
@ -15,10 +8,6 @@ import { mapToStudentRequestDTO } from '../interfaces/student-request.js';
|
|||
import { TeacherRepository } from '../data/users/teacher-repository.js';
|
||||
import { ClassRepository } from '../data/classes/class-repository.js';
|
||||
import { Class } from '../entities/classes/class.entity.js';
|
||||
import { LearningObjectRepository } from '../data/content/learning-object-repository.js';
|
||||
import { LearningObject } from '../entities/content/learning-object.entity.js';
|
||||
import { QuestionRepository } from '../data/questions/question-repository.js';
|
||||
import { Question } from '../entities/questions/question.entity.js';
|
||||
import { ClassJoinRequestRepository } from '../data/classes/class-join-request-repository.js';
|
||||
import { Student } from '../entities/users/student.entity.js';
|
||||
import { NotFoundException } from '../exceptions/not-found-exception.js';
|
||||
|
@ -26,7 +15,6 @@ import { addClassStudent, fetchClass, getClassStudentsDTO } from './classes.js';
|
|||
import { TeacherDTO } from '@dwengo-1/common/interfaces/teacher';
|
||||
import { ClassDTO } from '@dwengo-1/common/interfaces/class';
|
||||
import { StudentDTO } from '@dwengo-1/common/interfaces/student';
|
||||
import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question';
|
||||
import { ClassJoinRequestDTO } from '@dwengo-1/common/interfaces/class-join-request';
|
||||
import { ClassStatus } from '@dwengo-1/common/util/class-join-request';
|
||||
import { ConflictException } from '../exceptions/conflict-exception.js';
|
||||
|
@ -127,28 +115,6 @@ export async function getStudentsByTeacher(username: string, full: boolean): Pro
|
|||
return students.map((student) => student.username);
|
||||
}
|
||||
|
||||
export async function getTeacherQuestions(username: string, full: boolean): Promise<QuestionDTO[] | QuestionId[]> {
|
||||
const teacher: Teacher = await fetchTeacher(username);
|
||||
|
||||
// Find all learning objects that this teacher manages
|
||||
const learningObjectRepository: LearningObjectRepository = getLearningObjectRepository();
|
||||
const learningObjects: LearningObject[] = await learningObjectRepository.findAllByTeacher(teacher);
|
||||
|
||||
if (!learningObjects || learningObjects.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Fetch all questions related to these learning objects
|
||||
const questionRepository: QuestionRepository = getQuestionRepository();
|
||||
const questions: Question[] = await questionRepository.findAllByLearningObjects(learningObjects);
|
||||
|
||||
if (full) {
|
||||
return questions.map(mapToQuestionDTO);
|
||||
}
|
||||
|
||||
return questions.map(mapToQuestionDTOId);
|
||||
}
|
||||
|
||||
export async function getJoinRequestsByClass(classId: string): Promise<ClassJoinRequestDTO[]> {
|
||||
const classRepository: ClassRepository = getClassRepository();
|
||||
const cls: Class | null = await classRepository.findById(classId);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue