refactor(backend): Functions
This commit is contained in:
		
							parent
							
								
									5ec62554e3
								
							
						
					
					
						commit
						65c1a5e6b6
					
				
					 57 changed files with 172 additions and 117 deletions
				
			
		|  | @ -26,7 +26,7 @@ app.use('/api', apiRouter); | ||||||
| // Swagger
 | // Swagger
 | ||||||
| app.use('/api-docs', swaggerUi.serve, swaggerMiddleware); | app.use('/api-docs', swaggerUi.serve, swaggerMiddleware); | ||||||
| 
 | 
 | ||||||
| async function startServer() { | async function startServer(): Promise<void> { | ||||||
|     await initORM(); |     await initORM(); | ||||||
| 
 | 
 | ||||||
|     app.listen(port, () => { |     app.listen(port, () => { | ||||||
|  |  | ||||||
|  | @ -47,7 +47,7 @@ export async function getStudentHandler(req: Request, res: Response): Promise<vo | ||||||
|     res.status(201).json(user); |     res.status(201).json(user); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function createStudentHandler(req: Request, res: Response) { | export async function createStudentHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const userData = req.body as StudentDTO; |     const userData = req.body as StudentDTO; | ||||||
| 
 | 
 | ||||||
|     if (!userData.username || !userData.firstName || !userData.lastName) { |     if (!userData.username || !userData.firstName || !userData.lastName) { | ||||||
|  | @ -61,7 +61,7 @@ export async function createStudentHandler(req: Request, res: Response) { | ||||||
|     res.status(201).json(newUser); |     res.status(201).json(newUser); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function deleteStudentHandler(req: Request, res: Response) { | export async function deleteStudentHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const username = req.params.username; |     const username = req.params.username; | ||||||
| 
 | 
 | ||||||
|     if (!username) { |     if (!username) { | ||||||
|  |  | ||||||
|  | @ -30,7 +30,7 @@ export async function getSubmissionHandler(req: Request<SubmissionParams>, res: | ||||||
|     res.json(submission); |     res.json(submission); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function createSubmissionHandler(req: Request, res: Response) { | export async function createSubmissionHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const submissionDTO = req.body as SubmissionDTO; |     const submissionDTO = req.body as SubmissionDTO; | ||||||
| 
 | 
 | ||||||
|     const submission = await createSubmission(submissionDTO); |     const submission = await createSubmission(submissionDTO); | ||||||
|  | @ -42,7 +42,7 @@ export async function createSubmissionHandler(req: Request, res: Response) { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function deleteSubmissionHandler(req: Request, res: Response) { | export async function deleteSubmissionHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const hruid = req.params.hruid; |     const hruid = req.params.hruid; | ||||||
|     const submissionNumber = +req.params.id; |     const submissionNumber = +req.params.id; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -50,7 +50,7 @@ export async function getTeacherHandler(req: Request, res: Response): Promise<vo | ||||||
|     res.status(201).json(user); |     res.status(201).json(user); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function createTeacherHandler(req: Request, res: Response) { | export async function createTeacherHandler(req: Request, res: Response): Promise<void> { | ||||||
|     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) { | ||||||
|  | @ -64,7 +64,7 @@ export async function createTeacherHandler(req: Request, res: Response) { | ||||||
|     res.status(201).json(newUser); |     res.status(201).json(newUser); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function deleteTeacherHandler(req: Request, res: Response) { | export async function deleteTeacherHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const username = req.params.username; |     const username = req.params.username; | ||||||
| 
 | 
 | ||||||
|     if (!username) { |     if (!username) { | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ interface Translations { | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function getThemes(req: Request, res: Response) { | export function getThemes(req: Request, res: Response): void { | ||||||
|     const language = (req.query.language as string)?.toLowerCase() || 'nl'; |     const language = (req.query.language as string)?.toLowerCase() || 'nl'; | ||||||
|     const translations = loadTranslations<Translations>(language); |     const translations = loadTranslations<Translations>(language); | ||||||
|     const themeList = themes.map((theme) => ({ |     const themeList = themes.map((theme) => ({ | ||||||
|  | @ -21,7 +21,7 @@ export function getThemes(req: Request, res: Response) { | ||||||
|     res.json(themeList); |     res.json(themeList); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function getThemeByTitle(req: Request, res: Response) { | export function getThemeByTitle(req: Request, res: Response): void { | ||||||
|     const themeKey = req.params.theme; |     const themeKey = req.params.theme; | ||||||
|     const theme = themes.find((t) => t.title === themeKey); |     const theme = themes.find((t) => t.title === themeKey); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -47,7 +47,7 @@ export async function getUserHandler<T extends User>(req: Request, res: Response | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function createUserHandler<T extends User>(req: Request, res: Response, service: UserService<T>, userClass: new () => T) { | export async function createUserHandler<T extends User>(req: Request, res: Response, service: UserService<T>, userClass: new () => T): Promise<void> { | ||||||
|     try { |     try { | ||||||
|         getLogger().debug({ req: req }); |         getLogger().debug({ req: req }); | ||||||
|         const userData = req.body as UserDTO; |         const userData = req.body as UserDTO; | ||||||
|  | @ -67,7 +67,7 @@ export async function createUserHandler<T extends User>(req: Request, res: Respo | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function deleteUserHandler<T extends User>(req: Request, res: Response, service: UserService<T>) { | export async function deleteUserHandler<T extends User>(req: Request, res: Response, service: UserService<T>): Promise<void> { | ||||||
|     try { |     try { | ||||||
|         const username = req.params.username; |         const username = req.params.username; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,13 +3,13 @@ import { Assignment } from '../../entities/assignments/assignment.entity.js'; | ||||||
| import { Class } from '../../entities/classes/class.entity.js'; | import { Class } from '../../entities/classes/class.entity.js'; | ||||||
| 
 | 
 | ||||||
| export class AssignmentRepository extends DwengoEntityRepository<Assignment> { | export class AssignmentRepository extends DwengoEntityRepository<Assignment> { | ||||||
|     public findByClassAndId(within: Class, id: number): Promise<Assignment | null> { |     public async findByClassAndId(within: Class, id: number): Promise<Assignment | null> { | ||||||
|         return this.findOne({ within: within, id: id }); |         return this.findOne({ within: within, id: id }); | ||||||
|     } |     } | ||||||
|     public findAllAssignmentsInClass(within: Class): Promise<Assignment[]> { |     public async findAllAssignmentsInClass(within: Class): Promise<Assignment[]> { | ||||||
|         return this.findAll({ where: { within: within } }); |         return this.findAll({ where: { within: within } }); | ||||||
|     } |     } | ||||||
|     public deleteByClassAndId(within: Class, id: number): Promise<void> { |     public async deleteByClassAndId(within: Class, id: number): Promise<void> { | ||||||
|         return this.deleteWhere({ within: within, id: id }); |         return this.deleteWhere({ within: within, id: id }); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ import { Assignment } from '../../entities/assignments/assignment.entity.js'; | ||||||
| import { Student } from '../../entities/users/student.entity.js'; | import { Student } from '../../entities/users/student.entity.js'; | ||||||
| 
 | 
 | ||||||
| export class GroupRepository extends DwengoEntityRepository<Group> { | export class GroupRepository extends DwengoEntityRepository<Group> { | ||||||
|     public findByAssignmentAndGroupNumber(assignment: Assignment, groupNumber: number): Promise<Group | null> { |     public async findByAssignmentAndGroupNumber(assignment: Assignment, groupNumber: number): Promise<Group | null> { | ||||||
|         return this.findOne( |         return this.findOne( | ||||||
|             { |             { | ||||||
|                 assignment: assignment, |                 assignment: assignment, | ||||||
|  | @ -13,16 +13,16 @@ export class GroupRepository extends DwengoEntityRepository<Group> { | ||||||
|             { populate: ['members'] } |             { populate: ['members'] } | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|     public findAllGroupsForAssignment(assignment: Assignment): Promise<Group[]> { |     public async findAllGroupsForAssignment(assignment: Assignment): Promise<Group[]> { | ||||||
|         return this.findAll({ |         return this.findAll({ | ||||||
|             where: { assignment: assignment }, |             where: { assignment: assignment }, | ||||||
|             populate: ['members'], |             populate: ['members'], | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|     public findAllGroupsWithStudent(student: Student): Promise<Group[]> { |     public async findAllGroupsWithStudent(student: Student): Promise<Group[]> { | ||||||
|         return this.find({ members: student }, { populate: ['members'] }); |         return this.find({ members: student }, { populate: ['members'] }); | ||||||
|     } |     } | ||||||
|     public deleteByAssignmentAndGroupNumber(assignment: Assignment, groupNumber: number) { |     public async deleteByAssignmentAndGroupNumber(assignment: Assignment, groupNumber: number): Promise<void> { | ||||||
|         return this.deleteWhere({ |         return this.deleteWhere({ | ||||||
|             assignment: assignment, |             assignment: assignment, | ||||||
|             groupNumber: groupNumber, |             groupNumber: groupNumber, | ||||||
|  |  | ||||||
|  | @ -5,7 +5,10 @@ import { LearningObjectIdentifier } from '../../entities/content/learning-object | ||||||
| import { Student } from '../../entities/users/student.entity.js'; | import { Student } from '../../entities/users/student.entity.js'; | ||||||
| 
 | 
 | ||||||
| export class SubmissionRepository extends DwengoEntityRepository<Submission> { | export class SubmissionRepository extends DwengoEntityRepository<Submission> { | ||||||
|     public findSubmissionByLearningObjectAndSubmissionNumber(loId: LearningObjectIdentifier, submissionNumber: number): Promise<Submission | null> { |     public async findSubmissionByLearningObjectAndSubmissionNumber( | ||||||
|  |         loId: LearningObjectIdentifier, | ||||||
|  |         submissionNumber: number | ||||||
|  |     ): Promise<Submission | null> { | ||||||
|         return this.findOne({ |         return this.findOne({ | ||||||
|             learningObjectHruid: loId.hruid, |             learningObjectHruid: loId.hruid, | ||||||
|             learningObjectLanguage: loId.language, |             learningObjectLanguage: loId.language, | ||||||
|  | @ -14,7 +17,7 @@ export class SubmissionRepository extends DwengoEntityRepository<Submission> { | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public findMostRecentSubmissionForStudent(loId: LearningObjectIdentifier, submitter: Student): Promise<Submission | null> { |     public async findMostRecentSubmissionForStudent(loId: LearningObjectIdentifier, submitter: Student): Promise<Submission | null> { | ||||||
|         return this.findOne( |         return this.findOne( | ||||||
|             { |             { | ||||||
|                 learningObjectHruid: loId.hruid, |                 learningObjectHruid: loId.hruid, | ||||||
|  | @ -26,7 +29,7 @@ export class SubmissionRepository extends DwengoEntityRepository<Submission> { | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public findMostRecentSubmissionForGroup(loId: LearningObjectIdentifier, group: Group): Promise<Submission | null> { |     public async findMostRecentSubmissionForGroup(loId: LearningObjectIdentifier, group: Group): Promise<Submission | null> { | ||||||
|         return this.findOne( |         return this.findOne( | ||||||
|             { |             { | ||||||
|                 learningObjectHruid: loId.hruid, |                 learningObjectHruid: loId.hruid, | ||||||
|  | @ -38,15 +41,15 @@ export class SubmissionRepository extends DwengoEntityRepository<Submission> { | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public findAllSubmissionsForGroup(group: Group): Promise<Submission[]> { |     public async findAllSubmissionsForGroup(group: Group): Promise<Submission[]> { | ||||||
|         return this.find({ onBehalfOf: group }); |         return this.find({ onBehalfOf: group }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public findAllSubmissionsForStudent(student: Student): Promise<Submission[]> { |     public async findAllSubmissionsForStudent(student: Student): Promise<Submission[]> { | ||||||
|         return this.find({ submitter: student }); |         return this.find({ submitter: student }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public deleteSubmissionByLearningObjectAndSubmissionNumber(loId: LearningObjectIdentifier, submissionNumber: number): Promise<void> { |     public async deleteSubmissionByLearningObjectAndSubmissionNumber(loId: LearningObjectIdentifier, submissionNumber: number): Promise<void> { | ||||||
|         return this.deleteWhere({ |         return this.deleteWhere({ | ||||||
|             learningObjectHruid: loId.hruid, |             learningObjectHruid: loId.hruid, | ||||||
|             learningObjectLanguage: loId.language, |             learningObjectLanguage: loId.language, | ||||||
|  |  | ||||||
|  | @ -4,13 +4,13 @@ import { ClassJoinRequest } from '../../entities/classes/class-join-request.enti | ||||||
| import { Student } from '../../entities/users/student.entity.js'; | import { Student } from '../../entities/users/student.entity.js'; | ||||||
| 
 | 
 | ||||||
| export class ClassJoinRequestRepository extends DwengoEntityRepository<ClassJoinRequest> { | export class ClassJoinRequestRepository extends DwengoEntityRepository<ClassJoinRequest> { | ||||||
|     public findAllRequestsBy(requester: Student): Promise<ClassJoinRequest[]> { |     public async findAllRequestsBy(requester: Student): Promise<ClassJoinRequest[]> { | ||||||
|         return this.findAll({ where: { requester: requester } }); |         return this.findAll({ where: { requester: requester } }); | ||||||
|     } |     } | ||||||
|     public findAllOpenRequestsTo(clazz: Class): Promise<ClassJoinRequest[]> { |     public async findAllOpenRequestsTo(clazz: Class): Promise<ClassJoinRequest[]> { | ||||||
|         return this.findAll({ where: { class: clazz } }); |         return this.findAll({ where: { class: clazz } }); | ||||||
|     } |     } | ||||||
|     public deleteBy(requester: Student, clazz: Class): Promise<void> { |     public async deleteBy(requester: Student, clazz: Class): Promise<void> { | ||||||
|         return this.deleteWhere({ requester: requester, class: clazz }); |         return this.deleteWhere({ requester: requester, class: clazz }); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -4,20 +4,20 @@ import { Student } from '../../entities/users/student.entity.js'; | ||||||
| import { Teacher } from '../../entities/users/teacher.entity'; | import { Teacher } from '../../entities/users/teacher.entity'; | ||||||
| 
 | 
 | ||||||
| export class ClassRepository extends DwengoEntityRepository<Class> { | export class ClassRepository extends DwengoEntityRepository<Class> { | ||||||
|     public findById(id: string): Promise<Class | null> { |     public async findById(id: string): Promise<Class | null> { | ||||||
|         return this.findOne({ classId: id }, { populate: ['students', 'teachers'] }); |         return this.findOne({ classId: id }, { populate: ['students', 'teachers'] }); | ||||||
|     } |     } | ||||||
|     public deleteById(id: string): Promise<void> { |     public async deleteById(id: string): Promise<void> { | ||||||
|         return this.deleteWhere({ classId: id }); |         return this.deleteWhere({ classId: id }); | ||||||
|     } |     } | ||||||
|     public findByStudent(student: Student): Promise<Class[]> { |     public async findByStudent(student: Student): Promise<Class[]> { | ||||||
|         return this.find( |         return this.find( | ||||||
|             { students: student }, |             { students: student }, | ||||||
|             { populate: ['students', 'teachers'] } // Voegt student en teacher objecten toe
 |             { populate: ['students', 'teachers'] } // Voegt student en teacher objecten toe
 | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public findByTeacher(teacher: Teacher): Promise<Class[]> { |     public async findByTeacher(teacher: Teacher): Promise<Class[]> { | ||||||
|         return this.find({ teachers: teacher }, { populate: ['students', 'teachers'] }); |         return this.find({ teachers: teacher }, { populate: ['students', 'teachers'] }); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -4,16 +4,16 @@ import { TeacherInvitation } from '../../entities/classes/teacher-invitation.ent | ||||||
| import { Teacher } from '../../entities/users/teacher.entity.js'; | import { Teacher } from '../../entities/users/teacher.entity.js'; | ||||||
| 
 | 
 | ||||||
| export class TeacherInvitationRepository extends DwengoEntityRepository<TeacherInvitation> { | export class TeacherInvitationRepository extends DwengoEntityRepository<TeacherInvitation> { | ||||||
|     public findAllInvitationsForClass(clazz: Class): Promise<TeacherInvitation[]> { |     public async findAllInvitationsForClass(clazz: Class): Promise<TeacherInvitation[]> { | ||||||
|         return this.findAll({ where: { class: clazz } }); |         return this.findAll({ where: { class: clazz } }); | ||||||
|     } |     } | ||||||
|     public findAllInvitationsBy(sender: Teacher): Promise<TeacherInvitation[]> { |     public async findAllInvitationsBy(sender: Teacher): Promise<TeacherInvitation[]> { | ||||||
|         return this.findAll({ where: { sender: sender } }); |         return this.findAll({ where: { sender: sender } }); | ||||||
|     } |     } | ||||||
|     public findAllInvitationsFor(receiver: Teacher): Promise<TeacherInvitation[]> { |     public async findAllInvitationsFor(receiver: Teacher): Promise<TeacherInvitation[]> { | ||||||
|         return this.findAll({ where: { receiver: receiver } }); |         return this.findAll({ where: { receiver: receiver } }); | ||||||
|     } |     } | ||||||
|     public deleteBy(clazz: Class, sender: Teacher, receiver: Teacher): Promise<void> { |     public async deleteBy(clazz: Class, sender: Teacher, receiver: Teacher): Promise<void> { | ||||||
|         return this.deleteWhere({ |         return this.deleteWhere({ | ||||||
|             sender: sender, |             sender: sender, | ||||||
|             receiver: receiver, |             receiver: receiver, | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ import { Language } from '../../entities/content/language'; | ||||||
| import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier'; | import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier'; | ||||||
| 
 | 
 | ||||||
| export class AttachmentRepository extends DwengoEntityRepository<Attachment> { | export class AttachmentRepository extends DwengoEntityRepository<Attachment> { | ||||||
|     public findByLearningObjectIdAndName(learningObjectId: LearningObjectIdentifier, name: string): Promise<Attachment | null> { |     public async findByLearningObjectIdAndName(learningObjectId: LearningObjectIdentifier, name: string): Promise<Attachment | null> { | ||||||
|         return this.findOne({ |         return this.findOne({ | ||||||
|             learningObject: { |             learningObject: { | ||||||
|                 hruid: learningObjectId.hruid, |                 hruid: learningObjectId.hruid, | ||||||
|  | @ -15,7 +15,11 @@ export class AttachmentRepository extends DwengoEntityRepository<Attachment> { | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public findByMostRecentVersionOfLearningObjectAndName(hruid: string, language: Language, attachmentName: string): Promise<Attachment | null> { |     public async findByMostRecentVersionOfLearningObjectAndName( | ||||||
|  |         hruid: string, | ||||||
|  |         language: Language, | ||||||
|  |         attachmentName: string | ||||||
|  |     ): Promise<Attachment | null> { | ||||||
|         return this.findOne( |         return this.findOne( | ||||||
|             { |             { | ||||||
|                 learningObject: { |                 learningObject: { | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ import { Language } from '../../entities/content/language.js'; | ||||||
| import { Teacher } from '../../entities/users/teacher.entity.js'; | import { Teacher } from '../../entities/users/teacher.entity.js'; | ||||||
| 
 | 
 | ||||||
| export class LearningObjectRepository extends DwengoEntityRepository<LearningObject> { | export class LearningObjectRepository extends DwengoEntityRepository<LearningObject> { | ||||||
|     public findByIdentifier(identifier: LearningObjectIdentifier): Promise<LearningObject | null> { |     public async findByIdentifier(identifier: LearningObjectIdentifier): Promise<LearningObject | null> { | ||||||
|         return this.findOne( |         return this.findOne( | ||||||
|             { |             { | ||||||
|                 hruid: identifier.hruid, |                 hruid: identifier.hruid, | ||||||
|  | @ -18,7 +18,7 @@ export class LearningObjectRepository extends DwengoEntityRepository<LearningObj | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public findLatestByHruidAndLanguage(hruid: string, language: Language) { |     public async findLatestByHruidAndLanguage(hruid: string, language: Language): Promise<LearningObject | null> { | ||||||
|         return this.findOne( |         return this.findOne( | ||||||
|             { |             { | ||||||
|                 hruid: hruid, |                 hruid: hruid, | ||||||
|  | @ -33,7 +33,7 @@ export class LearningObjectRepository extends DwengoEntityRepository<LearningObj | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public findAllByTeacher(teacher: Teacher): Promise<LearningObject[]> { |     public async findAllByTeacher(teacher: Teacher): Promise<LearningObject[]> { | ||||||
|         return this.find( |         return this.find( | ||||||
|             { admins: teacher }, |             { admins: teacher }, | ||||||
|             { populate: ['admins'] } // Make sure to load admin relations
 |             { populate: ['admins'] } // Make sure to load admin relations
 | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ import { LearningPath } from '../../entities/content/learning-path.entity.js'; | ||||||
| import { Language } from '../../entities/content/language.js'; | import { Language } from '../../entities/content/language.js'; | ||||||
| 
 | 
 | ||||||
| export class LearningPathRepository extends DwengoEntityRepository<LearningPath> { | export class LearningPathRepository extends DwengoEntityRepository<LearningPath> { | ||||||
|     public findByHruidAndLanguage(hruid: string, language: Language): Promise<LearningPath | null> { |     public async findByHruidAndLanguage(hruid: string, language: Language): Promise<LearningPath | null> { | ||||||
|         return this.findOne({ hruid: hruid, language: language }, { populate: ['nodes', 'nodes.transitions'] }); |         return this.findOne({ hruid: hruid, language: language }, { populate: ['nodes', 'nodes.transitions'] }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,12 +1,12 @@ | ||||||
| import { EntityRepository, FilterQuery } from '@mikro-orm/core'; | import { EntityRepository, FilterQuery } from '@mikro-orm/core'; | ||||||
| 
 | 
 | ||||||
| export abstract class DwengoEntityRepository<T extends object> extends EntityRepository<T> { | export abstract class DwengoEntityRepository<T extends object> extends EntityRepository<T> { | ||||||
|     public async save(entity: T) { |     public async save(entity: T): Promise<void> { | ||||||
|         const em = this.getEntityManager(); |         const em = this.getEntityManager(); | ||||||
|         em.persist(entity); |         em.persist(entity); | ||||||
|         await em.flush(); |         await em.flush(); | ||||||
|     } |     } | ||||||
|     public async deleteWhere(query: FilterQuery<T>) { |     public async deleteWhere(query: FilterQuery<T>): Promise<void> { | ||||||
|         const toDelete = await this.findOne(query); |         const toDelete = await this.findOne(query); | ||||||
|         const em = this.getEntityManager(); |         const em = this.getEntityManager(); | ||||||
|         if (toDelete) { |         if (toDelete) { | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ import { Question } from '../../entities/questions/question.entity.js'; | ||||||
| import { Teacher } from '../../entities/users/teacher.entity.js'; | import { Teacher } from '../../entities/users/teacher.entity.js'; | ||||||
| 
 | 
 | ||||||
| export class AnswerRepository extends DwengoEntityRepository<Answer> { | export class AnswerRepository extends DwengoEntityRepository<Answer> { | ||||||
|     public createAnswer(answer: { toQuestion: Question; author: Teacher; content: string }): Promise<Answer> { |     public async createAnswer(answer: { toQuestion: Question; author: Teacher; content: string }): Promise<Answer> { | ||||||
|         const answerEntity = this.create({ |         const answerEntity = this.create({ | ||||||
|             toQuestion: answer.toQuestion, |             toQuestion: answer.toQuestion, | ||||||
|             author: answer.author, |             author: answer.author, | ||||||
|  | @ -13,13 +13,13 @@ export class AnswerRepository extends DwengoEntityRepository<Answer> { | ||||||
|         }); |         }); | ||||||
|         return this.insert(answerEntity); |         return this.insert(answerEntity); | ||||||
|     } |     } | ||||||
|     public findAllAnswersToQuestion(question: Question): Promise<Answer[]> { |     public async findAllAnswersToQuestion(question: Question): Promise<Answer[]> { | ||||||
|         return this.findAll({ |         return this.findAll({ | ||||||
|             where: { toQuestion: question }, |             where: { toQuestion: question }, | ||||||
|             orderBy: { sequenceNumber: 'ASC' }, |             orderBy: { sequenceNumber: 'ASC' }, | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|     public removeAnswerByQuestionAndSequenceNumber(question: Question, sequenceNumber: number): Promise<void> { |     public async removeAnswerByQuestionAndSequenceNumber(question: Question, sequenceNumber: number): Promise<void> { | ||||||
|         return this.deleteWhere({ |         return this.deleteWhere({ | ||||||
|             toQuestion: question, |             toQuestion: question, | ||||||
|             sequenceNumber: sequenceNumber, |             sequenceNumber: sequenceNumber, | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ import { Student } from '../../entities/users/student.entity.js'; | ||||||
| import { LearningObject } from '../../entities/content/learning-object.entity.js'; | import { LearningObject } from '../../entities/content/learning-object.entity.js'; | ||||||
| 
 | 
 | ||||||
| export class QuestionRepository extends DwengoEntityRepository<Question> { | export class QuestionRepository extends DwengoEntityRepository<Question> { | ||||||
|     public createQuestion(question: { loId: LearningObjectIdentifier; author: Student; content: string }): Promise<Question> { |     public async createQuestion(question: { loId: LearningObjectIdentifier; author: Student; content: string }): Promise<Question> { | ||||||
|         const questionEntity = this.create({ |         const questionEntity = this.create({ | ||||||
|             learningObjectHruid: question.loId.hruid, |             learningObjectHruid: question.loId.hruid, | ||||||
|             learningObjectLanguage: question.loId.language, |             learningObjectLanguage: question.loId.language, | ||||||
|  | @ -21,7 +21,7 @@ export class QuestionRepository extends DwengoEntityRepository<Question> { | ||||||
|         questionEntity.content = question.content; |         questionEntity.content = question.content; | ||||||
|         return this.insert(questionEntity); |         return this.insert(questionEntity); | ||||||
|     } |     } | ||||||
|     public findAllQuestionsAboutLearningObject(loId: LearningObjectIdentifier): Promise<Question[]> { |     public async findAllQuestionsAboutLearningObject(loId: LearningObjectIdentifier): Promise<Question[]> { | ||||||
|         return this.findAll({ |         return this.findAll({ | ||||||
|             where: { |             where: { | ||||||
|                 learningObjectHruid: loId.hruid, |                 learningObjectHruid: loId.hruid, | ||||||
|  | @ -33,7 +33,7 @@ export class QuestionRepository extends DwengoEntityRepository<Question> { | ||||||
|             }, |             }, | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|     public removeQuestionByLearningObjectAndSequenceNumber(loId: LearningObjectIdentifier, sequenceNumber: number): Promise<void> { |     public async removeQuestionByLearningObjectAndSequenceNumber(loId: LearningObjectIdentifier, sequenceNumber: number): Promise<void> { | ||||||
|         return this.deleteWhere({ |         return this.deleteWhere({ | ||||||
|             learningObjectHruid: loId.hruid, |             learningObjectHruid: loId.hruid, | ||||||
|             learningObjectLanguage: loId.language, |             learningObjectLanguage: loId.language, | ||||||
|  |  | ||||||
|  | @ -34,7 +34,7 @@ let entityManager: EntityManager | undefined; | ||||||
| /** | /** | ||||||
|  * Execute all the database operations within the function f in a single transaction. |  * Execute all the database operations within the function f in a single transaction. | ||||||
|  */ |  */ | ||||||
| export function transactional<T>(f: () => Promise<T>) { | export function transactional<T>(f: () => Promise<T>): void { | ||||||
|     entityManager?.transactional(f); |     entityManager?.transactional(f); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,10 +2,10 @@ import { Student } from '../../entities/users/student.entity.js'; | ||||||
| import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; | import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; | ||||||
| 
 | 
 | ||||||
| export class StudentRepository extends DwengoEntityRepository<Student> { | export class StudentRepository extends DwengoEntityRepository<Student> { | ||||||
|     public findByUsername(username: string): Promise<Student | null> { |     public async findByUsername(username: string): Promise<Student | null> { | ||||||
|         return this.findOne({ username: username }); |         return this.findOne({ username: username }); | ||||||
|     } |     } | ||||||
|     public deleteByUsername(username: string): Promise<void> { |     public async deleteByUsername(username: string): Promise<void> { | ||||||
|         return this.deleteWhere({ username: username }); |         return this.deleteWhere({ username: username }); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,10 +2,10 @@ import { Teacher } from '../../entities/users/teacher.entity.js'; | ||||||
| import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; | import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; | ||||||
| 
 | 
 | ||||||
| export class TeacherRepository extends DwengoEntityRepository<Teacher> { | export class TeacherRepository extends DwengoEntityRepository<Teacher> { | ||||||
|     public findByUsername(username: string): Promise<Teacher | null> { |     public async findByUsername(username: string): Promise<Teacher | null> { | ||||||
|         return this.findOne({ username: username }); |         return this.findOne({ username: username }); | ||||||
|     } |     } | ||||||
|     public deleteByUsername(username: string): Promise<void> { |     public async deleteByUsername(username: string): Promise<void> { | ||||||
|         return this.deleteWhere({ username: username }); |         return this.deleteWhere({ username: username }); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,10 +2,10 @@ import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; | ||||||
| import { User } from '../../entities/users/user.entity.js'; | import { User } from '../../entities/users/user.entity.js'; | ||||||
| 
 | 
 | ||||||
| export class UserRepository<T extends User> extends DwengoEntityRepository<T> { | export class UserRepository<T extends User> extends DwengoEntityRepository<T> { | ||||||
|     public findByUsername(username: string): Promise<T | null> { |     public async findByUsername(username: string): Promise<T | null> { | ||||||
|         return this.findOne({ username } as Partial<T>); |         return this.findOne({ username } as Partial<T>); | ||||||
|     } |     } | ||||||
|     public deleteByUsername(username: string): Promise<void> { |     public async deleteByUsername(username: string): Promise<void> { | ||||||
|         return this.deleteWhere({ username } as Partial<T>); |         return this.deleteWhere({ username } as Partial<T>); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -5,5 +5,7 @@ export class LearningObjectIdentifier { | ||||||
|         public hruid: string, |         public hruid: string, | ||||||
|         public language: Language, |         public language: Language, | ||||||
|         public version: number |         public version: number | ||||||
|     ) {} |     ) { | ||||||
|  |         // Do nothing
 | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -28,7 +28,7 @@ function initializeLogger(): Logger { | ||||||
|         level: LOG_LEVEL, |         level: LOG_LEVEL, | ||||||
|         json: true, |         json: true, | ||||||
|         format: format.combine(format.timestamp(), format.json()), |         format: format.combine(format.timestamp(), format.json()), | ||||||
|         onConnectionError: (err) => { |         onConnectionError: (err): void => { | ||||||
|             // eslint-disable-next-line no-console
 |             // eslint-disable-next-line no-console
 | ||||||
|             console.error(`Connection error: ${err}`); |             console.error(`Connection error: ${err}`); | ||||||
|         }, |         }, | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ import { LokiLabels } from 'loki-logger-ts'; | ||||||
| export class MikroOrmLogger extends DefaultLogger { | export class MikroOrmLogger extends DefaultLogger { | ||||||
|     private logger: Logger = getLogger(); |     private logger: Logger = getLogger(); | ||||||
| 
 | 
 | ||||||
|     log(namespace: LoggerNamespace, message: string, context?: LogContext) { |     log(namespace: LoggerNamespace, message: string, context?: LogContext): void { | ||||||
|         if (!this.isEnabled(namespace, context)) { |         if (!this.isEnabled(namespace, context)) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  | @ -48,7 +48,7 @@ export class MikroOrmLogger extends DefaultLogger { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private createMessage(namespace: LoggerNamespace, messageArg: string, context?: LogContext) { |     private createMessage(namespace: LoggerNamespace, messageArg: string, context?: LogContext): unknown { | ||||||
|         const labels: LokiLabels = { |         const labels: LokiLabels = { | ||||||
|             service: 'ORM', |             service: 'ORM', | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import { getLogger, Logger } from './initalize.js'; | import { getLogger, Logger } from './initalize.js'; | ||||||
| import { Request, Response } from 'express'; | import { Request, Response } from 'express'; | ||||||
| 
 | 
 | ||||||
| export function responseTimeLogger(req: Request, res: Response, time: number) { | export function responseTimeLogger(req: Request, res: Response, time: number): void { | ||||||
|     const logger: Logger = getLogger(); |     const logger: Logger = getLogger(); | ||||||
| 
 | 
 | ||||||
|     const method = req.method; |     const method = req.method; | ||||||
|  |  | ||||||
|  | @ -1,9 +1,9 @@ | ||||||
| import { envVars, getEnvVar } from '../../util/envVars.js'; | import { envVars, getEnvVar } from '../../util/envVars.js'; | ||||||
| import { expressjwt } from 'express-jwt'; | import { expressjwt } from 'express-jwt'; | ||||||
|  | import * as jwt from 'jsonwebtoken'; | ||||||
| import { JwtPayload } from 'jsonwebtoken'; | import { JwtPayload } from 'jsonwebtoken'; | ||||||
| import jwksClient from 'jwks-rsa'; | import jwksClient from 'jwks-rsa'; | ||||||
| import * as express from 'express'; | import * as express from 'express'; | ||||||
| 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 { ForbiddenException, UnauthorizedException } from '../../exceptions.js'; | ||||||
|  | @ -74,7 +74,7 @@ const verifyJwtToken = expressjwt({ | ||||||
|  */ |  */ | ||||||
| function getAuthenticationInfo(req: AuthenticatedRequest): AuthenticationInfo | undefined { | function getAuthenticationInfo(req: AuthenticatedRequest): AuthenticationInfo | undefined { | ||||||
|     if (!req.jwtPayload) { |     if (!req.jwtPayload) { | ||||||
|         return; |         return undefined; | ||||||
|     } |     } | ||||||
|     const issuer = req.jwtPayload.iss; |     const issuer = req.jwtPayload.iss; | ||||||
|     let accountType: 'student' | 'teacher'; |     let accountType: 'student' | 'teacher'; | ||||||
|  | @ -84,8 +84,9 @@ function getAuthenticationInfo(req: AuthenticatedRequest): AuthenticationInfo | | ||||||
|     } else if (issuer === idpConfigs.teacher.issuer) { |     } else if (issuer === idpConfigs.teacher.issuer) { | ||||||
|         accountType = 'teacher'; |         accountType = 'teacher'; | ||||||
|     } else { |     } else { | ||||||
|         return; |         return undefined; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     return { |     return { | ||||||
|         accountType: accountType, |         accountType: accountType, | ||||||
|         username: req.jwtPayload[JWT_PROPERTY_NAMES.username]!, |         username: req.jwtPayload[JWT_PROPERTY_NAMES.username]!, | ||||||
|  | @ -100,10 +101,10 @@ function getAuthenticationInfo(req: AuthenticatedRequest): AuthenticationInfo | | ||||||
|  * Add the AuthenticationInfo object with the information about the current authentication to the request in order |  * Add the AuthenticationInfo object with the information about the current authentication to the request in order | ||||||
|  * to avoid that the routers have to deal with the JWT token. |  * to avoid that the routers have to deal with the JWT token. | ||||||
|  */ |  */ | ||||||
| const addAuthenticationInfo = (req: AuthenticatedRequest, _res: express.Response, next: express.NextFunction) => { | function addAuthenticationInfo(req: AuthenticatedRequest, _res: express.Response, next: express.NextFunction): void { | ||||||
|     req.auth = getAuthenticationInfo(req); |     req.auth = getAuthenticationInfo(req); | ||||||
|     next(); |     next(); | ||||||
| }; | } | ||||||
| 
 | 
 | ||||||
| export const authenticateUser = [verifyJwtToken, addAuthenticationInfo]; | export const authenticateUser = [verifyJwtToken, addAuthenticationInfo]; | ||||||
| 
 | 
 | ||||||
|  | @ -113,9 +114,8 @@ export const authenticateUser = [verifyJwtToken, addAuthenticationInfo]; | ||||||
|  * @param accessCondition Predicate over the current AuthenticationInfo. Access is only granted when this evaluates |  * @param accessCondition Predicate over the current AuthenticationInfo. Access is only granted when this evaluates | ||||||
|  *                        to true. |  *                        to true. | ||||||
|  */ |  */ | ||||||
| export const authorize = | export function authorize(accessCondition: (auth: AuthenticationInfo) => boolean) { | ||||||
|     (accessCondition: (auth: AuthenticationInfo) => boolean) => |     return (req: AuthenticatedRequest, _res: express.Response, next: express.NextFunction): void => { | ||||||
|     (req: AuthenticatedRequest, _res: express.Response, next: express.NextFunction): void => { |  | ||||||
|         if (!req.auth) { |         if (!req.auth) { | ||||||
|             throw new UnauthorizedException(); |             throw new UnauthorizedException(); | ||||||
|         } else if (!accessCondition(req.auth)) { |         } else if (!accessCondition(req.auth)) { | ||||||
|  | @ -124,6 +124,7 @@ export const authorize = | ||||||
|             next(); |             next(); | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Middleware which rejects all unauthenticated users, but accepts all authenticated users. |  * Middleware which rejects all unauthenticated users, but accepts all authenticated users. | ||||||
|  |  | ||||||
|  | @ -54,7 +54,7 @@ function config(testingMode: boolean = false): Options { | ||||||
| 
 | 
 | ||||||
|             // Workaround: vitest: `TypeError: Unknown file extension ".ts"` (ERR_UNKNOWN_FILE_EXTENSION)
 |             // Workaround: vitest: `TypeError: Unknown file extension ".ts"` (ERR_UNKNOWN_FILE_EXTENSION)
 | ||||||
|             // (see https://mikro-orm.io/docs/guide/project-setup#testing-the-endpoint)
 |             // (see https://mikro-orm.io/docs/guide/project-setup#testing-the-endpoint)
 | ||||||
|             dynamicImportProvider: (id) => import(id), |             dynamicImportProvider: async (id) => import(id), | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ import { envVars, getEnvVar } from './util/envVars.js'; | ||||||
| import { getLogger, Logger } from './logging/initalize.js'; | import { getLogger, Logger } from './logging/initalize.js'; | ||||||
| 
 | 
 | ||||||
| let orm: MikroORM | undefined; | let orm: MikroORM | undefined; | ||||||
| export async function initORM(testingMode: boolean = false) { | export async function initORM(testingMode: boolean = false): Promise<void> { | ||||||
|     const logger: Logger = getLogger(); |     const logger: Logger = getLogger(); | ||||||
| 
 | 
 | ||||||
|     logger.info('Initializing ORM'); |     logger.info('Initializing ORM'); | ||||||
|  |  | ||||||
|  | @ -79,7 +79,7 @@ export async function getAssignmentsSubmissions(classid: string, assignmentNumbe | ||||||
|     const groups = await groupRepository.findAllGroupsForAssignment(assignment); |     const groups = await groupRepository.findAllGroupsForAssignment(assignment); | ||||||
| 
 | 
 | ||||||
|     const submissionRepository = getSubmissionRepository(); |     const submissionRepository = getSubmissionRepository(); | ||||||
|     const submissions = (await Promise.all(groups.map((group) => submissionRepository.findAllSubmissionsForGroup(group)))).flat(); |     const submissions = (await Promise.all(groups.map(async (group) => submissionRepository.findAllSubmissionsForGroup(group)))).flat(); | ||||||
| 
 | 
 | ||||||
|     return submissions.map(mapToSubmissionDTO); |     return submissions.map(mapToSubmissionDTO); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -24,11 +24,15 @@ export async function getAllClasses(full: boolean): Promise<ClassDTO[] | string[ | ||||||
| export async function createClass(classData: ClassDTO): Promise<Class | null> { | export async function createClass(classData: ClassDTO): Promise<Class | null> { | ||||||
|     const teacherRepository = getTeacherRepository(); |     const teacherRepository = getTeacherRepository(); | ||||||
|     const teacherUsernames = classData.teachers || []; |     const teacherUsernames = classData.teachers || []; | ||||||
|     const teachers = (await Promise.all(teacherUsernames.map((id) => teacherRepository.findByUsername(id)))).filter((teacher) => teacher != null); |     const teachers = (await Promise.all(teacherUsernames.map(async (id) => teacherRepository.findByUsername(id)))).filter( | ||||||
|  |         (teacher) => teacher != null | ||||||
|  |     ); | ||||||
| 
 | 
 | ||||||
|     const studentRepository = getStudentRepository(); |     const studentRepository = getStudentRepository(); | ||||||
|     const studentUsernames = classData.students || []; |     const studentUsernames = classData.students || []; | ||||||
|     const students = (await Promise.all(studentUsernames.map((id) => studentRepository.findByUsername(id)))).filter((student) => student != null); |     const students = (await Promise.all(studentUsernames.map(async (id) => studentRepository.findByUsername(id)))).filter( | ||||||
|  |         (student) => student != null | ||||||
|  |     ); | ||||||
| 
 | 
 | ||||||
|     //Const cls = mapToClass(classData, teachers, students);
 |     //Const cls = mapToClass(classData, teachers, students);
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -43,7 +43,9 @@ export async function createGroup(groupData: GroupDTO, classid: string, assignme | ||||||
|     const studentRepository = getStudentRepository(); |     const studentRepository = getStudentRepository(); | ||||||
| 
 | 
 | ||||||
|     const memberUsernames = (groupData.members as string[]) || []; // TODO check if groupdata.members is a list
 |     const memberUsernames = (groupData.members as string[]) || []; // TODO check if groupdata.members is a list
 | ||||||
|     const members = (await Promise.all([...memberUsernames].map((id) => studentRepository.findByUsername(id)))).filter((student) => student != null); |     const members = (await Promise.all([...memberUsernames].map(async (id) => studentRepository.findByUsername(id)))).filter( | ||||||
|  |         (student) => student != null | ||||||
|  |     ); | ||||||
| 
 | 
 | ||||||
|     getLogger().debug(members); |     getLogger().debug(members); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ import { Attachment } from '../../entities/content/attachment.entity.js'; | ||||||
| import { LearningObjectIdentifier } from '../../interfaces/learning-content.js'; | import { LearningObjectIdentifier } from '../../interfaces/learning-content.js'; | ||||||
| 
 | 
 | ||||||
| const attachmentService = { | const attachmentService = { | ||||||
|     getAttachment(learningObjectId: LearningObjectIdentifier, attachmentName: string): Promise<Attachment | null> { |     async getAttachment(learningObjectId: LearningObjectIdentifier, attachmentName: string): Promise<Attachment | null> { | ||||||
|         const attachmentRepo = getAttachmentRepository(); |         const attachmentRepo = getAttachmentRepository(); | ||||||
| 
 | 
 | ||||||
|         if (learningObjectId.version) { |         if (learningObjectId.version) { | ||||||
|  |  | ||||||
|  | @ -41,7 +41,7 @@ function convertLearningObject(learningObject: LearningObject | null): FilteredL | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function findLearningObjectEntityById(id: LearningObjectIdentifier): Promise<LearningObject | null> { | async function findLearningObjectEntityById(id: LearningObjectIdentifier): Promise<LearningObject | null> { | ||||||
|     const learningObjectRepo = getLearningObjectRepository(); |     const learningObjectRepo = getLearningObjectRepository(); | ||||||
| 
 | 
 | ||||||
|     return learningObjectRepo.findLatestByHruidAndLanguage(id.hruid, id.language as Language); |     return learningObjectRepo.findLatestByHruidAndLanguage(id.hruid, id.language as Language); | ||||||
|  | @ -69,7 +69,7 @@ const databaseLearningObjectProvider: LearningObjectProvider = { | ||||||
|         if (!learningObject) { |         if (!learningObject) { | ||||||
|             return null; |             return null; | ||||||
|         } |         } | ||||||
|         return await processingService.render(learningObject, (id) => findLearningObjectEntityById(id)); |         return await processingService.render(learningObject, async (id) => findLearningObjectEntityById(id)); | ||||||
|     }, |     }, | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | @ -96,7 +96,7 @@ const databaseLearningObjectProvider: LearningObjectProvider = { | ||||||
|             throw new NotFoundError('The learning path with the given ID could not be found.'); |             throw new NotFoundError('The learning path with the given ID could not be found.'); | ||||||
|         } |         } | ||||||
|         const learningObjects = await Promise.all( |         const learningObjects = await Promise.all( | ||||||
|             learningPath.nodes.map((it) => { |             learningPath.nodes.map(async (it) => { | ||||||
|                 const learningObject = learningObjectService.getLearningObjectById({ |                 const learningObject = learningObjectService.getLearningObjectById({ | ||||||
|                     hruid: it.learningObjectHruid, |                     hruid: it.learningObjectHruid, | ||||||
|                     language: it.language, |                     language: it.language, | ||||||
|  |  | ||||||
|  | @ -18,28 +18,28 @@ const learningObjectService = { | ||||||
|     /** |     /** | ||||||
|      * Fetches a single learning object by its HRUID |      * Fetches a single learning object by its HRUID | ||||||
|      */ |      */ | ||||||
|     getLearningObjectById(id: LearningObjectIdentifier): Promise<FilteredLearningObject | null> { |     async getLearningObjectById(id: LearningObjectIdentifier): Promise<FilteredLearningObject | null> { | ||||||
|         return getProvider(id).getLearningObjectById(id); |         return getProvider(id).getLearningObjectById(id); | ||||||
|     }, |     }, | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Fetch full learning object data (metadata) |      * Fetch full learning object data (metadata) | ||||||
|      */ |      */ | ||||||
|     getLearningObjectsFromPath(id: LearningPathIdentifier): Promise<FilteredLearningObject[]> { |     async getLearningObjectsFromPath(id: LearningPathIdentifier): Promise<FilteredLearningObject[]> { | ||||||
|         return getProvider(id).getLearningObjectsFromPath(id); |         return getProvider(id).getLearningObjectsFromPath(id); | ||||||
|     }, |     }, | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Fetch only learning object HRUIDs |      * Fetch only learning object HRUIDs | ||||||
|      */ |      */ | ||||||
|     getLearningObjectIdsFromPath(id: LearningPathIdentifier): Promise<string[]> { |     async getLearningObjectIdsFromPath(id: LearningPathIdentifier): Promise<string[]> { | ||||||
|         return getProvider(id).getLearningObjectIdsFromPath(id); |         return getProvider(id).getLearningObjectIdsFromPath(id); | ||||||
|     }, |     }, | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Obtain a HTML-rendering of the learning object with the given identifier (as a string). |      * Obtain a HTML-rendering of the learning object with the given identifier (as a string). | ||||||
|      */ |      */ | ||||||
|     getLearningObjectHTML(id: LearningObjectIdentifier): Promise<string | null> { |     async getLearningObjectHTML(id: LearningObjectIdentifier): Promise<string | null> { | ||||||
|         return getProvider(id).getLearningObjectHTML(id); |         return getProvider(id).getLearningObjectHTML(id); | ||||||
|     }, |     }, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ class ExternProcessor extends StringProcessor { | ||||||
|         super(DwengoContentType.EXTERN); |         super(DwengoContentType.EXTERN); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override renderFn(externURL: string) { |     override renderFn(externURL: string): string { | ||||||
|         if (!isValidHttpUrl(externURL)) { |         if (!isValidHttpUrl(externURL)) { | ||||||
|             throw new ProcessingError('The url is not valid: ' + externURL); |             throw new ProcessingError('The url is not valid: ' + externURL); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -32,7 +32,7 @@ class GiftProcessor extends StringProcessor { | ||||||
|         super(DwengoContentType.GIFT); |         super(DwengoContentType.GIFT); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override renderFn(giftString: string) { |     override renderFn(giftString: string): string { | ||||||
|         const quizQuestions: GIFTQuestion[] = parse(giftString); |         const quizQuestions: GIFTQuestion[] = parse(giftString); | ||||||
| 
 | 
 | ||||||
|         let html = "<div class='learning-object-gift'>\n"; |         let html = "<div class='learning-object-gift'>\n"; | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ class BlockImageProcessor extends InlineImageProcessor { | ||||||
|         super(); |         super(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override renderFn(imageUrl: string) { |     override renderFn(imageUrl: string): string { | ||||||
|         const inlineHtml = super.render(imageUrl); |         const inlineHtml = super.render(imageUrl); | ||||||
|         return DOMPurify.sanitize(`<div>${inlineHtml}</div>`); |         return DOMPurify.sanitize(`<div>${inlineHtml}</div>`); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -13,7 +13,7 @@ class InlineImageProcessor extends StringProcessor { | ||||||
|         super(contentType); |         super(contentType); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override renderFn(imageUrl: string) { |     override renderFn(imageUrl: string): string { | ||||||
|         if (!isValidHttpUrl(imageUrl)) { |         if (!isValidHttpUrl(imageUrl)) { | ||||||
|             throw new ProcessingError(`Image URL is invalid: ${imageUrl}`); |             throw new ProcessingError(`Image URL is invalid: ${imageUrl}`); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -14,7 +14,7 @@ class MarkdownProcessor extends StringProcessor { | ||||||
|         super(DwengoContentType.TEXT_MARKDOWN); |         super(DwengoContentType.TEXT_MARKDOWN); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override renderFn(mdText: string) { |     override renderFn(mdText: string): string { | ||||||
|         try { |         try { | ||||||
|             marked.use({ renderer: dwengoMarkedRenderer }); |             marked.use({ renderer: dwengoMarkedRenderer }); | ||||||
|             const html = marked(mdText, { async: false }); |             const html = marked(mdText, { async: false }); | ||||||
|  | @ -24,7 +24,7 @@ class MarkdownProcessor extends StringProcessor { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     replaceLinks(html: string) { |     replaceLinks(html: string): string { | ||||||
|         const proc = new InlineImageProcessor(); |         const proc = new InlineImageProcessor(); | ||||||
|         html = html.replace( |         html = html.replace( | ||||||
|             /<img.*?src="(.*?)".*?(alt="(.*?)")?.*?(title="(.*?)")?.*?>/g, |             /<img.*?src="(.*?)".*?(alt="(.*?)")?.*?(title="(.*?)")?.*?>/g, | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ class PdfProcessor extends StringProcessor { | ||||||
|         super(DwengoContentType.APPLICATION_PDF); |         super(DwengoContentType.APPLICATION_PDF); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override renderFn(pdfUrl: string) { |     override renderFn(pdfUrl: string): string { | ||||||
|         if (!isValidHttpUrl(pdfUrl)) { |         if (!isValidHttpUrl(pdfUrl)) { | ||||||
|             throw new ProcessingError(`PDF URL is invalid: ${pdfUrl}`); |             throw new ProcessingError(`PDF URL is invalid: ${pdfUrl}`); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -9,7 +9,9 @@ import { DwengoContentType } from './content-type.js'; | ||||||
|  * Based on https://github.com/dwengovzw/Learning-Object-Repository/blob/main/app/processors/processor.js
 |  * Based on https://github.com/dwengovzw/Learning-Object-Repository/blob/main/app/processors/processor.js
 | ||||||
|  */ |  */ | ||||||
| abstract class Processor<T> { | abstract class Processor<T> { | ||||||
|     protected constructor(public contentType: DwengoContentType) {} |     protected constructor(public contentType: DwengoContentType) { | ||||||
|  |         // Do nothing
 | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Render the given object. |      * Render the given object. | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ class TextProcessor extends StringProcessor { | ||||||
|         super(DwengoContentType.TEXT_PLAIN); |         super(DwengoContentType.TEXT_PLAIN); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override renderFn(text: string) { |     override renderFn(text: string): string { | ||||||
|         // Sanitize plain text to prevent xss.
 |         // Sanitize plain text to prevent xss.
 | ||||||
|         return DOMPurify.sanitize(text); |         return DOMPurify.sanitize(text); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -18,7 +18,7 @@ async function getLearningObjectsForNodes(nodes: LearningPathNode[]): Promise<Ma | ||||||
|     // Its corresponding learning object.
 |     // Its corresponding learning object.
 | ||||||
|     const nullableNodesToLearningObjects = new Map<LearningPathNode, FilteredLearningObject | null>( |     const nullableNodesToLearningObjects = new Map<LearningPathNode, FilteredLearningObject | null>( | ||||||
|         await Promise.all( |         await Promise.all( | ||||||
|             nodes.map((node) => |             nodes.map(async (node) => | ||||||
|                 learningObjectService |                 learningObjectService | ||||||
|                     .getLearningObjectById({ |                     .getLearningObjectById({ | ||||||
|                         hruid: node.learningObjectHruid, |                         hruid: node.learningObjectHruid, | ||||||
|  | @ -117,7 +117,7 @@ async function convertNodes( | ||||||
|     nodesToLearningObjects: Map<LearningPathNode, FilteredLearningObject>, |     nodesToLearningObjects: Map<LearningPathNode, FilteredLearningObject>, | ||||||
|     personalizedFor?: PersonalizationTarget |     personalizedFor?: PersonalizationTarget | ||||||
| ): Promise<LearningObjectNode[]> { | ): Promise<LearningObjectNode[]> { | ||||||
|     const nodesPromise = Array.from(nodesToLearningObjects.entries()).map((entry) => |     const nodesPromise = Array.from(nodesToLearningObjects.entries()).map(async (entry) => | ||||||
|         convertNode(entry[0], entry[1], personalizedFor, nodesToLearningObjects) |         convertNode(entry[0], entry[1], personalizedFor, nodesToLearningObjects) | ||||||
|     ); |     ); | ||||||
|     return await Promise.all(nodesPromise); |     return await Promise.all(nodesPromise); | ||||||
|  | @ -179,11 +179,11 @@ const databaseLearningPathProvider: LearningPathProvider = { | ||||||
|     ): Promise<LearningPathResponse> { |     ): Promise<LearningPathResponse> { | ||||||
|         const learningPathRepo = getLearningPathRepository(); |         const learningPathRepo = getLearningPathRepository(); | ||||||
| 
 | 
 | ||||||
|         const learningPaths = (await Promise.all(hruids.map((hruid) => learningPathRepo.findByHruidAndLanguage(hruid, language)))).filter( |         const learningPaths = (await Promise.all(hruids.map(async (hruid) => learningPathRepo.findByHruidAndLanguage(hruid, language)))).filter( | ||||||
|             (learningPath) => learningPath !== null |             (learningPath) => learningPath !== null | ||||||
|         ); |         ); | ||||||
|         const filteredLearningPaths = await Promise.all( |         const filteredLearningPaths = await Promise.all( | ||||||
|             learningPaths.map((learningPath, index) => convertLearningPath(learningPath, index, personalizedFor)) |             learningPaths.map(async (learningPath, index) => convertLearningPath(learningPath, index, personalizedFor)) | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         return { |         return { | ||||||
|  | @ -200,7 +200,7 @@ const databaseLearningPathProvider: LearningPathProvider = { | ||||||
|         const learningPathRepo = getLearningPathRepository(); |         const learningPathRepo = getLearningPathRepository(); | ||||||
| 
 | 
 | ||||||
|         const searchResults = await learningPathRepo.findByQueryStringAndLanguage(query, language); |         const searchResults = await learningPathRepo.findByQueryStringAndLanguage(query, language); | ||||||
|         return await Promise.all(searchResults.map((result, index) => convertLearningPath(result, index, personalizedFor))); |         return await Promise.all(searchResults.map(async (result, index) => convertLearningPath(result, index, personalizedFor))); | ||||||
|     }, |     }, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -49,7 +49,9 @@ const learningPathService = { | ||||||
|      * Search learning paths in the data source using the given search string. |      * Search learning paths in the data source using the given search string. | ||||||
|      */ |      */ | ||||||
|     async searchLearningPaths(query: string, language: Language, personalizedFor?: PersonalizationTarget): Promise<LearningPath[]> { |     async searchLearningPaths(query: string, language: Language, personalizedFor?: PersonalizationTarget): Promise<LearningPath[]> { | ||||||
|         const providerResponses = await Promise.all(allProviders.map((provider) => provider.searchLearningPaths(query, language, personalizedFor))); |         const providerResponses = await Promise.all( | ||||||
|  |             allProviders.map(async (provider) => provider.searchLearningPaths(query, language, personalizedFor)) | ||||||
|  |         ); | ||||||
|         return providerResponses.flat(); |         return providerResponses.flat(); | ||||||
|     }, |     }, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import { getAnswerRepository, getQuestionRepository } from '../data/repositories | ||||||
| import { mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId } from '../interfaces/question.js'; | import { mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId } from '../interfaces/question.js'; | ||||||
| import { Question } from '../entities/questions/question.entity.js'; | import { Question } from '../entities/questions/question.entity.js'; | ||||||
| import { Answer } from '../entities/questions/answer.entity.js'; | import { Answer } from '../entities/questions/answer.entity.js'; | ||||||
| import { mapToAnswerDTO, mapToAnswerId } from '../interfaces/answer.js'; | import { AnswerDTO, AnswerId, mapToAnswerDTO, mapToAnswerId } from '../interfaces/answer.js'; | ||||||
| import { QuestionRepository } from '../data/questions/question-repository.js'; | import { QuestionRepository } from '../data/questions/question-repository.js'; | ||||||
| import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; | import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; | ||||||
| import { mapToStudent } from '../interfaces/student.js'; | import { mapToStudent } from '../interfaces/student.js'; | ||||||
|  | @ -45,7 +45,7 @@ export async function getQuestion(questionId: QuestionId): Promise<QuestionDTO | | ||||||
|     return mapToQuestionDTO(question); |     return mapToQuestionDTO(question); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getAnswersByQuestion(questionId: QuestionId, full: boolean) { | export async function getAnswersByQuestion(questionId: QuestionId, full: boolean): Promise<AnswerDTO[] | AnswerId[]> { | ||||||
|     const answerRepository = getAnswerRepository(); |     const answerRepository = getAnswerRepository(); | ||||||
|     const question = await fetchQuestion(questionId); |     const question = await fetchQuestion(questionId); | ||||||
| 
 | 
 | ||||||
|  | @ -68,7 +68,7 @@ export async function getAnswersByQuestion(questionId: QuestionId, full: boolean | ||||||
|     return answersDTO.map(mapToAnswerId); |     return answersDTO.map(mapToAnswerId); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function createQuestion(questionDTO: QuestionDTO) { | export async function createQuestion(questionDTO: QuestionDTO): Promise<QuestionDTO | null> { | ||||||
|     const questionRepository = getQuestionRepository(); |     const questionRepository = getQuestionRepository(); | ||||||
| 
 | 
 | ||||||
|     const author = mapToStudent(questionDTO.author); |     const author = mapToStudent(questionDTO.author); | ||||||
|  | @ -86,7 +86,7 @@ export async function createQuestion(questionDTO: QuestionDTO) { | ||||||
|     return questionDTO; |     return questionDTO; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function deleteQuestion(questionId: QuestionId) { | export async function deleteQuestion(questionId: QuestionId): Promise<Question | null> { | ||||||
|     const questionRepository = getQuestionRepository(); |     const questionRepository = getQuestionRepository(); | ||||||
| 
 | 
 | ||||||
|     const question = await fetchQuestion(questionId); |     const question = await fetchQuestion(questionId); | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ import { getSubmissionRepository } from '../data/repositories.js'; | ||||||
| import { Language } from '../entities/content/language.js'; | import { Language } from '../entities/content/language.js'; | ||||||
| import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; | import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; | ||||||
| import { mapToSubmission, mapToSubmissionDTO, SubmissionDTO } from '../interfaces/submission.js'; | import { mapToSubmission, mapToSubmissionDTO, SubmissionDTO } from '../interfaces/submission.js'; | ||||||
|  | import { Submission } from '../entities/assignments/submission.entity.js'; | ||||||
| 
 | 
 | ||||||
| export async function getSubmission( | export async function getSubmission( | ||||||
|     learningObjectHruid: string, |     learningObjectHruid: string, | ||||||
|  | @ -21,7 +22,7 @@ export async function getSubmission( | ||||||
|     return mapToSubmissionDTO(submission); |     return mapToSubmissionDTO(submission); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function createSubmission(submissionDTO: SubmissionDTO) { | export async function createSubmission(submissionDTO: SubmissionDTO): Promise<Submission | null> { | ||||||
|     const submissionRepository = getSubmissionRepository(); |     const submissionRepository = getSubmissionRepository(); | ||||||
|     const submission = mapToSubmission(submissionDTO); |     const submission = mapToSubmission(submissionDTO); | ||||||
| 
 | 
 | ||||||
|  | @ -35,7 +36,12 @@ export async function createSubmission(submissionDTO: SubmissionDTO) { | ||||||
|     return submission; |     return submission; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function deleteSubmission(learningObjectHruid: string, language: Language, version: number, submissionNumber: number) { | export async function deleteSubmission( | ||||||
|  |     learningObjectHruid: string, | ||||||
|  |     language: Language, | ||||||
|  |     version: number, | ||||||
|  |     submissionNumber: number | ||||||
|  | ): Promise<SubmissionDTO | null> { | ||||||
|     const submissionRepository = getSubmissionRepository(); |     const submissionRepository = getSubmissionRepository(); | ||||||
| 
 | 
 | ||||||
|     const submission = getSubmission(learningObjectHruid, language, version, submissionNumber); |     const submission = getSubmission(learningObjectHruid, language, version, submissionNumber); | ||||||
|  |  | ||||||
|  | @ -77,7 +77,7 @@ export async function getClassIdsByTeacher(username: string): Promise<string[]> | ||||||
|     return classes.map((cls) => cls.id); |     return classes.map((cls) => cls.id); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function fetchStudentsByTeacher(username: string) { | export async function fetchStudentsByTeacher(username: string): Promise<StudentDTO[]> { | ||||||
|     const classes = await getClassIdsByTeacher(username); |     const classes = await getClassIdsByTeacher(username); | ||||||
| 
 | 
 | ||||||
|     return (await Promise.all(classes.map(async (id) => getClassStudents(id)))).flat(); |     return (await Promise.all(classes.map(async (id) => getClassStudents(id)))).flat(); | ||||||
|  |  | ||||||
|  | @ -6,7 +6,11 @@ | ||||||
|  * @param regex |  * @param regex | ||||||
|  * @param replacementFn |  * @param replacementFn | ||||||
|  */ |  */ | ||||||
| export async function replaceAsync(str: string, regex: RegExp, replacementFn: (match: string, ...args: string[]) => Promise<string>) { | export async function replaceAsync( | ||||||
|  |     str: string, | ||||||
|  |     regex: RegExp, | ||||||
|  |     replacementFn: (match: string, ...args: string[]) => Promise<string> | ||||||
|  | ): Promise<string> { | ||||||
|     const promises: Promise<string>[] = []; |     const promises: Promise<string>[] = []; | ||||||
| 
 | 
 | ||||||
|     // First run through matches: add all Promises resulting from the replacement function
 |     // First run through matches: add all Promises resulting from the replacement function
 | ||||||
|  |  | ||||||
|  | @ -9,7 +9,7 @@ export function isValidHttpUrl(url: string): boolean { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function getUrlStringForLearningObject(learningObjectId: LearningObjectIdentifier) { | export function getUrlStringForLearningObject(learningObjectId: LearningObjectIdentifier): string { | ||||||
|     let url = `/learningObject/${learningObjectId.hruid}/html?language=${learningObjectId.language}`; |     let url = `/learningObject/${learningObjectId.hruid}/html?language=${learningObjectId.language}`; | ||||||
|     if (learningObjectId.version) { |     if (learningObjectId.version) { | ||||||
|         url += `&version=${learningObjectId.version}`; |         url += `&version=${learningObjectId.version}`; | ||||||
|  |  | ||||||
|  | @ -78,7 +78,7 @@ describe('DatabaseLearningObjectProvider', () => { | ||||||
|         }); |         }); | ||||||
|         it('should throw an error if queried with a path identifier for which there is no learning path', async () => { |         it('should throw an error if queried with a path identifier for which there is no learning path', async () => { | ||||||
|             await expect( |             await expect( | ||||||
|                 (async () => { |                 (async (): Promise<void> => { | ||||||
|                     await databaseLearningObjectProvider.getLearningObjectIdsFromPath({ |                     await databaseLearningObjectProvider.getLearningObjectIdsFromPath({ | ||||||
|                         hruid: 'non_existing_hruid', |                         hruid: 'non_existing_hruid', | ||||||
|                         language: Language.Dutch, |                         language: Language.Dutch, | ||||||
|  | @ -97,7 +97,7 @@ describe('DatabaseLearningObjectProvider', () => { | ||||||
|         }); |         }); | ||||||
|         it('should throw an error if queried with a path identifier for which there is no learning path', async () => { |         it('should throw an error if queried with a path identifier for which there is no learning path', async () => { | ||||||
|             await expect( |             await expect( | ||||||
|                 (async () => { |                 (async (): Promise<void> => { | ||||||
|                     await databaseLearningObjectProvider.getLearningObjectsFromPath({ |                     await databaseLearningObjectProvider.getLearningObjectsFromPath({ | ||||||
|                         hruid: 'non_existing_hruid', |                         hruid: 'non_existing_hruid', | ||||||
|                         language: Language.Dutch, |                         language: Language.Dutch, | ||||||
|  |  | ||||||
|  | @ -124,7 +124,7 @@ describe('DatabaseLearningPathProvider', () => { | ||||||
| 
 | 
 | ||||||
|             const learningObjectsOnPath = ( |             const learningObjectsOnPath = ( | ||||||
|                 await Promise.all( |                 await Promise.all( | ||||||
|                     example.learningPath.nodes.map((node) => |                     example.learningPath.nodes.map(async (node) => | ||||||
|                         learningObjectService.getLearningObjectById({ |                         learningObjectService.getLearningObjectById({ | ||||||
|                             hruid: node.learningObjectHruid, |                             hruid: node.learningObjectHruid, | ||||||
|                             version: node.version, |                             version: node.version, | ||||||
|  |  | ||||||
|  | @ -14,7 +14,7 @@ import { makeTestQuestions } from './test_assets/questions/questions.testdata.js | ||||||
| import { makeTestAnswers } from './test_assets/questions/answers.testdata.js'; | import { makeTestAnswers } from './test_assets/questions/answers.testdata.js'; | ||||||
| import { makeTestSubmissions } from './test_assets/assignments/submission.testdata.js'; | import { makeTestSubmissions } from './test_assets/assignments/submission.testdata.js'; | ||||||
| 
 | 
 | ||||||
| export async function setupTestApp() { | export async function setupTestApp(): Promise<void> { | ||||||
|     dotenv.config({ path: '.env.test' }); |     dotenv.config({ path: '.env.test' }); | ||||||
|     await initORM(true); |     await initORM(true); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ import { envVars, getEnvVar } from '../../../../src/util/envVars'; | ||||||
|  */ |  */ | ||||||
| export function dummyLearningObject(hruid: string, language: Language, title: string): LearningObjectExample { | export function dummyLearningObject(hruid: string, language: Language, title: string): LearningObjectExample { | ||||||
|     return { |     return { | ||||||
|         createLearningObject: () => { |         createLearningObject: (): LearningObject => { | ||||||
|             const learningObject = new LearningObject(); |             const learningObject = new LearningObject(); | ||||||
|             learningObject.hruid = getEnvVar(envVars.UserContentPrefix) + hruid; |             learningObject.hruid = getEnvVar(envVars.UserContentPrefix) + hruid; | ||||||
|             learningObject.language = language; |             learningObject.language = language; | ||||||
|  |  | ||||||
|  | @ -3,7 +3,12 @@ import { LearningPathTransition } from '../../../src/entities/content/learning-p | ||||||
| import { LearningPathNode } from '../../../src/entities/content/learning-path-node.entity'; | import { LearningPathNode } from '../../../src/entities/content/learning-path-node.entity'; | ||||||
| import { LearningPath } from '../../../src/entities/content/learning-path.entity'; | import { LearningPath } from '../../../src/entities/content/learning-path.entity'; | ||||||
| 
 | 
 | ||||||
| export function createLearningPathTransition(node: LearningPathNode, transitionNumber: number, condition: string | null, to: LearningPathNode) { | export function createLearningPathTransition( | ||||||
|  |     node: LearningPathNode, | ||||||
|  |     transitionNumber: number, | ||||||
|  |     condition: string | null, | ||||||
|  |     to: LearningPathNode | ||||||
|  | ): LearningPathTransition { | ||||||
|     const trans = new LearningPathTransition(); |     const trans = new LearningPathTransition(); | ||||||
|     trans.node = node; |     trans.node = node; | ||||||
|     trans.transitionNumber = transitionNumber; |     trans.transitionNumber = transitionNumber; | ||||||
|  | @ -19,7 +24,7 @@ export function createLearningPathNode( | ||||||
|     version: number, |     version: number, | ||||||
|     language: Language, |     language: Language, | ||||||
|     startNode: boolean |     startNode: boolean | ||||||
| ) { | ): LearningPathNode { | ||||||
|     const node = new LearningPathNode(); |     const node = new LearningPathNode(); | ||||||
|     node.learningPath = learningPath; |     node.learningPath = learningPath; | ||||||
|     node.nodeNumber = nodeNumber; |     node.nodeNumber = nodeNumber; | ||||||
|  |  | ||||||
|  | @ -69,7 +69,7 @@ export function expectToBeCorrectEntity<T extends object>(actual: { entity: T; n | ||||||
|  * @param filtered the representation as FilteredLearningObject |  * @param filtered the representation as FilteredLearningObject | ||||||
|  * @param original the original entity added to the database |  * @param original the original entity added to the database | ||||||
|  */ |  */ | ||||||
| export function expectToBeCorrectFilteredLearningObject(filtered: FilteredLearningObject, original: LearningObject) { | export function expectToBeCorrectFilteredLearningObject(filtered: FilteredLearningObject, original: LearningObject): void { | ||||||
|     expect(filtered.uuid).toEqual(original.uuid); |     expect(filtered.uuid).toEqual(original.uuid); | ||||||
|     expect(filtered.version).toEqual(original.version); |     expect(filtered.version).toEqual(original.version); | ||||||
|     expect(filtered.language).toEqual(original.language); |     expect(filtered.language).toEqual(original.language); | ||||||
|  | @ -105,7 +105,7 @@ export function expectToBeCorrectLearningPath( | ||||||
|     learningPath: LearningPath, |     learningPath: LearningPath, | ||||||
|     expectedEntity: LearningPathEntity, |     expectedEntity: LearningPathEntity, | ||||||
|     learningObjectsOnPath: FilteredLearningObject[] |     learningObjectsOnPath: FilteredLearningObject[] | ||||||
| ) { | ): void { | ||||||
|     expect(learningPath.hruid).toEqual(expectedEntity.hruid); |     expect(learningPath.hruid).toEqual(expectedEntity.hruid); | ||||||
|     expect(learningPath.language).toEqual(expectedEntity.language); |     expect(learningPath.language).toEqual(expectedEntity.language); | ||||||
|     expect(learningPath.description).toEqual(expectedEntity.description); |     expect(learningPath.description).toEqual(expectedEntity.description); | ||||||
|  |  | ||||||
|  | @ -23,11 +23,20 @@ export default [ | ||||||
|         languageOptions: { |         languageOptions: { | ||||||
|             ecmaVersion: 'latest', |             ecmaVersion: 'latest', | ||||||
|             sourceType: 'module', |             sourceType: 'module', | ||||||
|  |             parserOptions: { | ||||||
|  |                 projectService: true, | ||||||
|  |                 tsconfigRootDir: import.meta.dirname, | ||||||
|  |             }, | ||||||
|         }, |         }, | ||||||
|         linterOptions: { |         linterOptions: { | ||||||
|             reportUnusedInlineConfigs: 'error', |             reportUnusedInlineConfigs: 'error', | ||||||
|         }, |         }, | ||||||
|         rules: { |         rules: { | ||||||
|  |             'consistent-return': 'off', | ||||||
|  |             '@typescript-eslint/consistent-return': 'off', | ||||||
|  | 
 | ||||||
|  |             '@typescript-eslint/explicit-function-return-type': 'warn', | ||||||
|  | 
 | ||||||
|             '@typescript-eslint/naming-convention': [ |             '@typescript-eslint/naming-convention': [ | ||||||
|                 'warn', |                 'warn', | ||||||
|                 { // Enforce that all variables, functions and properties are camelCase
 |                 { // Enforce that all variables, functions and properties are camelCase
 | ||||||
|  | @ -49,6 +58,14 @@ export default [ | ||||||
|                 } |                 } | ||||||
|             ], |             ], | ||||||
| 
 | 
 | ||||||
|  |             // 'no-empty-function': 'off',
 | ||||||
|  |             '@typescript-eslint/no-empty-function': 'error', | ||||||
|  | 
 | ||||||
|  |             'no-loop-func': 'off', | ||||||
|  |             '@typescript-eslint/no-loop-func': 'error', | ||||||
|  | 
 | ||||||
|  |             '@typescript-eslint/no-unsafe-function-type': 'error', | ||||||
|  | 
 | ||||||
|             'no-unused-expressions': 'off', |             'no-unused-expressions': 'off', | ||||||
|             '@typescript-eslint/no-unused-expressions': 'warn', |             '@typescript-eslint/no-unused-expressions': 'warn', | ||||||
|             'no-unused-vars': 'off', |             'no-unused-vars': 'off', | ||||||
|  | @ -66,6 +83,10 @@ export default [ | ||||||
|             'no-use-before-define': 'off', |             'no-use-before-define': 'off', | ||||||
|             '@typescript-eslint/no-use-before-define': 'off', |             '@typescript-eslint/no-use-before-define': 'off', | ||||||
| 
 | 
 | ||||||
|  |             '@typescript-eslint/prefer-function-type': 'error', | ||||||
|  | 
 | ||||||
|  |             '@typescript-eslint/promise-function-async': 'warn', | ||||||
|  | 
 | ||||||
|             'no-await-in-loop': 'warn', |             'no-await-in-loop': 'warn', | ||||||
|             'no-constructor-return': 'error', |             'no-constructor-return': 'error', | ||||||
|             'no-duplicate-imports': 'error', |             'no-duplicate-imports': 'error', | ||||||
|  | @ -110,7 +131,6 @@ export default [ | ||||||
|             'no-iterator': 'error', |             'no-iterator': 'error', | ||||||
|             'no-label-var': 'warn', |             'no-label-var': 'warn', | ||||||
|             'no-labels': 'warn', |             'no-labels': 'warn', | ||||||
|             'no-loop-func': 'error', |  | ||||||
|             'no-multi-assign': 'error', |             'no-multi-assign': 'error', | ||||||
|             'no-nested-ternary': 'error', |             'no-nested-ternary': 'error', | ||||||
|             'no-object-constructor': 'error', |             'no-object-constructor': 'error', | ||||||
|  |  | ||||||
		Reference in a new issue