feat(backend): Eigen error handler toegevoegd.
Hiervoor was ook refactoring aan de exception-klassen nodig.
This commit is contained in:
		
							parent
							
								
									bc94b25a6a
								
							
						
					
					
						commit
						aaa71aa648
					
				
					 14 changed files with 103 additions and 55 deletions
				
			
		|  | @ -9,6 +9,7 @@ import { EnvVars, getNumericEnvVar } from './util/envvars.js'; | ||||||
| import apiRouter from './routes/router.js'; | import apiRouter from './routes/router.js'; | ||||||
| import swaggerMiddleware from './swagger.js'; | import swaggerMiddleware from './swagger.js'; | ||||||
| import swaggerUi from 'swagger-ui-express'; | import swaggerUi from 'swagger-ui-express'; | ||||||
|  | import {errorHandler} from "./middleware/error-handling/error-handler"; | ||||||
| 
 | 
 | ||||||
| const logger: Logger = getLogger(); | const logger: Logger = getLogger(); | ||||||
| 
 | 
 | ||||||
|  | @ -26,6 +27,8 @@ app.use('/api', apiRouter); | ||||||
| // Swagger
 | // Swagger
 | ||||||
| app.use('/api-docs', swaggerUi.serve, swaggerMiddleware); | app.use('/api-docs', swaggerUi.serve, swaggerMiddleware); | ||||||
| 
 | 
 | ||||||
|  | app.use(errorHandler); | ||||||
|  | 
 | ||||||
| async function startServer() { | async function startServer() { | ||||||
|     await initORM(); |     await initORM(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -4,9 +4,9 @@ import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifie | ||||||
| import learningObjectService from '../services/learning-objects/learning-object-service.js'; | import learningObjectService from '../services/learning-objects/learning-object-service.js'; | ||||||
| import { EnvVars, getEnvVar } from '../util/envvars.js'; | import { EnvVars, getEnvVar } from '../util/envvars.js'; | ||||||
| import { Language } from '../entities/content/language.js'; | import { Language } from '../entities/content/language.js'; | ||||||
| import { BadRequestException } from '../exceptions.js'; |  | ||||||
| import attachmentService from '../services/learning-objects/attachment-service.js'; | import attachmentService from '../services/learning-objects/attachment-service.js'; | ||||||
| import { NotFoundError } from '@mikro-orm/core'; | import { NotFoundError } from '@mikro-orm/core'; | ||||||
|  | import {BadRequestException} from "../exceptions/badRequestException"; | ||||||
| 
 | 
 | ||||||
| function getLearningObjectIdentifierFromRequest(req: Request): LearningObjectIdentifier { | function getLearningObjectIdentifierFromRequest(req: Request): LearningObjectIdentifier { | ||||||
|     if (!req.params.hruid) { |     if (!req.params.hruid) { | ||||||
|  |  | ||||||
|  | @ -2,13 +2,14 @@ import { Request, Response } from 'express'; | ||||||
| import { themes } from '../data/themes.js'; | import { themes } from '../data/themes.js'; | ||||||
| import { FALLBACK_LANG } from '../config.js'; | import { FALLBACK_LANG } from '../config.js'; | ||||||
| import learningPathService from '../services/learning-paths/learning-path-service.js'; | import learningPathService from '../services/learning-paths/learning-path-service.js'; | ||||||
| import { BadRequestException, NotFoundException } from '../exceptions.js'; |  | ||||||
| import { Language } from '../entities/content/language.js'; | import { Language } from '../entities/content/language.js'; | ||||||
| import { | import { | ||||||
|     PersonalizationTarget, |     PersonalizationTarget, | ||||||
|     personalizedForGroup, |     personalizedForGroup, | ||||||
|     personalizedForStudent, |     personalizedForStudent, | ||||||
| } from '../services/learning-paths/learning-path-personalization-util.js'; | } from '../services/learning-paths/learning-path-personalization-util.js'; | ||||||
|  | import {BadRequestException} from "../exceptions/badRequestException"; | ||||||
|  | import {NotFoundException} from "../exceptions/not-found-exception"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Fetch learning paths based on query parameters. |  * Fetch learning paths based on query parameters. | ||||||
|  |  | ||||||
|  | @ -1,42 +0,0 @@ | ||||||
| /** |  | ||||||
|  * Exception for HTTP 400 Bad Request |  | ||||||
|  */ |  | ||||||
| export class BadRequestException extends Error { |  | ||||||
|     public status = 400; |  | ||||||
| 
 |  | ||||||
|     constructor(error: string) { |  | ||||||
|         super(error); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Exception for HTTP 401 Unauthorized |  | ||||||
|  */ |  | ||||||
| export class UnauthorizedException extends Error { |  | ||||||
|     status = 401; |  | ||||||
|     constructor(message: string = 'Unauthorized') { |  | ||||||
|         super(message); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Exception for HTTP 403 Forbidden |  | ||||||
|  */ |  | ||||||
| export class ForbiddenException extends Error { |  | ||||||
|     status = 403; |  | ||||||
| 
 |  | ||||||
|     constructor(message: string = 'Forbidden') { |  | ||||||
|         super(message); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Exception for HTTP 404 Not Found |  | ||||||
|  */ |  | ||||||
| export class NotFoundException extends Error { |  | ||||||
|     public status = 404; |  | ||||||
| 
 |  | ||||||
|     constructor(error: string) { |  | ||||||
|         super(error); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										10
									
								
								backend/src/exceptions/bad-request-exception.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								backend/src/exceptions/bad-request-exception.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | ||||||
|  | import {ExceptionWithHttpState} from "./exception-with-http-state.js"; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Exception for HTTP 400 Bad Request | ||||||
|  |  */ | ||||||
|  | export abstract class BadRequestException extends ExceptionWithHttpState { | ||||||
|  |     constructor(error: string) { | ||||||
|  |         super(400, error); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								backend/src/exceptions/conflict-exception.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								backend/src/exceptions/conflict-exception.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | ||||||
|  | import {ExceptionWithHttpState} from "./exception-with-http-state.js"; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Exception for HTTP 409 Conflict | ||||||
|  |  */ | ||||||
|  | export class ConflictException extends ExceptionWithHttpState { | ||||||
|  |     public status = 409; | ||||||
|  | 
 | ||||||
|  |     constructor(error: string) { | ||||||
|  |         super(409, error); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								backend/src/exceptions/exception-with-http-state.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								backend/src/exceptions/exception-with-http-state.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | ||||||
|  | /** | ||||||
|  |  * Exceptions which are associated with a HTTP error code. | ||||||
|  |  */ | ||||||
|  | export abstract class ExceptionWithHttpState extends Error { | ||||||
|  |     constructor(public status: number, public error: string) { | ||||||
|  |         super(error); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
							
								
								
									
										12
									
								
								backend/src/exceptions/forbidden-exception.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								backend/src/exceptions/forbidden-exception.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | ||||||
|  | import {ExceptionWithHttpState} from "./exception-with-http-state.js"; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Exception for HTTP 403 Forbidden | ||||||
|  |  */ | ||||||
|  | export class ForbiddenException extends ExceptionWithHttpState { | ||||||
|  |     status = 403; | ||||||
|  | 
 | ||||||
|  |     constructor(message: string = 'Forbidden') { | ||||||
|  |         super(403, message); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								backend/src/exceptions/not-found-exception.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								backend/src/exceptions/not-found-exception.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | ||||||
|  | import {ExceptionWithHttpState} from "./exception-with-http-state.js"; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Exception for HTTP 404 Not Found | ||||||
|  |  */ | ||||||
|  | export class NotFoundException extends ExceptionWithHttpState { | ||||||
|  |     public status = 404; | ||||||
|  | 
 | ||||||
|  |     constructor(error: string) { | ||||||
|  |         super(404, error); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										10
									
								
								backend/src/exceptions/unauthorized-exception.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								backend/src/exceptions/unauthorized-exception.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | ||||||
|  | import {ExceptionWithHttpState} from "./exception-with-http-state.js"; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Exception for HTTP 401 Unauthorized | ||||||
|  |  */ | ||||||
|  | export class UnauthorizedException extends ExceptionWithHttpState { | ||||||
|  |     constructor(message: string = 'Unauthorized') { | ||||||
|  |         super(401, message); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -6,7 +6,8 @@ import * as express from 'express'; | ||||||
| import * as jwt from 'jsonwebtoken'; | import * as jwt from 'jsonwebtoken'; | ||||||
| import { AuthenticatedRequest } from './authenticated-request.js'; | import { AuthenticatedRequest } from './authenticated-request.js'; | ||||||
| import { AuthenticationInfo } from './authentication-info.js'; | import { AuthenticationInfo } from './authentication-info.js'; | ||||||
| import { ForbiddenException, UnauthorizedException } from '../../exceptions.js'; | import {UnauthorizedException} from "../../exceptions/unauthorized-exception"; | ||||||
|  | import {ForbiddenException} from "../../exceptions/forbidden-exception"; | ||||||
| 
 | 
 | ||||||
| const JWKS_CACHE = true; | const JWKS_CACHE = true; | ||||||
| const JWKS_RATE_LIMIT = true; | const JWKS_RATE_LIMIT = true; | ||||||
|  |  | ||||||
							
								
								
									
										15
									
								
								backend/src/middleware/error-handling/error-handler.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								backend/src/middleware/error-handling/error-handler.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | ||||||
|  | import {NextFunction, Request, Response} from "express"; | ||||||
|  | import {getLogger, Logger} from "../../logging/initalize"; | ||||||
|  | import {ExceptionWithHttpState} from "../../exceptions/exception-with-http-state"; | ||||||
|  | 
 | ||||||
|  | const logger: Logger = getLogger(); | ||||||
|  | 
 | ||||||
|  | export function errorHandler(err: unknown, req: Request, res: Response, _: NextFunction): void { | ||||||
|  |     if (err instanceof ExceptionWithHttpState) { | ||||||
|  |         logger.warn(`An error occurred while handling request ${JSON.stringify(req)}: ${err.error} (-> HTTP ${err.status})`); | ||||||
|  |         res.status(err.status).json(err); | ||||||
|  |     } else { | ||||||
|  |         logger.error(`Unexpected error occurred while handling request ${JSON.stringify(req)}: ${JSON.stringify(err)}`); | ||||||
|  |         res.status(500).json(err); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -5,6 +5,9 @@ import { GroupDTO, mapToGroupDTO, mapToGroupDTOId } from '../interfaces/group.js | ||||||
| import { mapToStudent, mapToStudentDTO, StudentDTO } from '../interfaces/student.js'; | import { mapToStudent, mapToStudentDTO, StudentDTO } from '../interfaces/student.js'; | ||||||
| import { mapToSubmissionDTO, mapToSubmissionDTOId, SubmissionDTO, SubmissionDTOId } from '../interfaces/submission.js'; | import { mapToSubmissionDTO, mapToSubmissionDTOId, SubmissionDTO, SubmissionDTOId } from '../interfaces/submission.js'; | ||||||
| import { getAllAssignments } from './assignments.js'; | import { getAllAssignments } from './assignments.js'; | ||||||
|  | import {UniqueConstraintViolationException} from "@mikro-orm/core"; | ||||||
|  | 
 | ||||||
|  | import {ConflictException} from "../exceptions/conflict-exception"; | ||||||
| 
 | 
 | ||||||
| export async function getAllStudents(full: boolean): Promise<StudentDTO[] | string[]> { | export async function getAllStudents(full: boolean): Promise<StudentDTO[] | string[]> { | ||||||
|     const studentRepository = getStudentRepository(); |     const studentRepository = getStudentRepository(); | ||||||
|  | @ -29,11 +32,12 @@ export async function createStudent(userData: StudentDTO): Promise<StudentDTO | | ||||||
|     try { |     try { | ||||||
|         const newStudent = mapToStudent(userData); |         const newStudent = mapToStudent(userData); | ||||||
|         await studentRepository.save(newStudent); |         await studentRepository.save(newStudent); | ||||||
| 
 |  | ||||||
|         return mapToStudentDTO(newStudent); |         return mapToStudentDTO(newStudent); | ||||||
|     } catch (e) { |     } catch (e: unknown) { | ||||||
|         console.log(e); |         if (e instanceof UniqueConstraintViolationException) { | ||||||
|         return null; |             throw new ConflictException(`There is already a user with username '${userData.username}'.`); | ||||||
|  |         } | ||||||
|  |         throw e; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,17 +2,16 @@ import { | ||||||
|     getClassRepository, |     getClassRepository, | ||||||
|     getLearningObjectRepository, |     getLearningObjectRepository, | ||||||
|     getQuestionRepository, |     getQuestionRepository, | ||||||
|     getStudentRepository, |  | ||||||
|     getTeacherRepository, |     getTeacherRepository, | ||||||
| } from '../data/repositories.js'; | } from '../data/repositories.js'; | ||||||
| import { Teacher } from '../entities/users/teacher.entity.js'; |  | ||||||
| import { ClassDTO, mapToClassDTO } from '../interfaces/class.js'; | import { ClassDTO, mapToClassDTO } from '../interfaces/class.js'; | ||||||
| import { getClassStudents } from './classes.js'; | import { getClassStudents } from './classes.js'; | ||||||
| import { StudentDTO } from '../interfaces/student.js'; | import { StudentDTO } from '../interfaces/student.js'; | ||||||
| import { mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId } from '../interfaces/question.js'; | import { mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId } from '../interfaces/question.js'; | ||||||
| import { mapToUser } from '../interfaces/user.js'; |  | ||||||
| import { mapToTeacher, mapToTeacherDTO, TeacherDTO } from '../interfaces/teacher.js'; | import { mapToTeacher, mapToTeacherDTO, TeacherDTO } from '../interfaces/teacher.js'; | ||||||
| import { teachersOnly } from '../middleware/auth/auth.js'; | import {UniqueConstraintViolationException} from "@mikro-orm/core"; | ||||||
|  | 
 | ||||||
|  | import {ConflictException} from "../exceptions/conflict-exception"; | ||||||
| 
 | 
 | ||||||
| export async function getAllTeachers(full: boolean): Promise<TeacherDTO[] | string[]> { | export async function getAllTeachers(full: boolean): Promise<TeacherDTO[] | string[]> { | ||||||
|     const teacherRepository = getTeacherRepository(); |     const teacherRepository = getTeacherRepository(); | ||||||
|  | @ -40,8 +39,10 @@ export async function createTeacher(userData: TeacherDTO): Promise<TeacherDTO | | ||||||
| 
 | 
 | ||||||
|         return mapToTeacherDTO(newTeacher); |         return mapToTeacherDTO(newTeacher); | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
|         console.log(e); |         if (e instanceof UniqueConstraintViolationException) { | ||||||
|         return null; |             throw new ConflictException(`There is already a user with username '${userData.username}'.`); | ||||||
|  |         } | ||||||
|  |         throw e; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Gerald Schmittinger
						Gerald Schmittinger