From c67baf216c6c12e166b11a061b11ac99a23f52fd Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Sun, 16 Mar 2025 17:15:09 +0100 Subject: [PATCH 01/43] actions: Testing workflow aangemaakt Op PRs naar dev die geen draft zijn worden automatisch testen uitgevoerd --- .github/workflows/testing.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/testing.yml diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml new file mode 100644 index 00000000..4913f9fc --- /dev/null +++ b/.github/workflows/testing.yml @@ -0,0 +1,32 @@ +# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs + +name: Testing + +on: + push: + branches: [ "dev" ] + pull_request: + branches: [ "dev" ] + +jobs: + test: + name: Run unit tests + if: '! github.event.pull_request.draft' + runs-on: [self-hosted, Linux, X64] + + strategy: + matrix: + node-version: [22.x] + # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + + steps: + - uses: actions/checkout@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + - run: npm ci + - run: npm run build --if-present + - run: npm run test:unit From fbf53c3d367388450731be2d2a13bed65eb02e67 Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Sun, 16 Mar 2025 17:15:51 +0100 Subject: [PATCH 02/43] fix: update vite config config aangepast zodat project kan gebuild worden met top-level awaits --- frontend/vite.config.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 9ebf424b..845e025f 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -12,4 +12,7 @@ export default defineConfig({ "@": fileURLToPath(new URL("./src", import.meta.url)), }, }, + build: { + target: 'esnext' //browsers can handle the latest ES features + }, }); From 0cb1436772a4471465cc27196ffdf10d4dfb275c Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Sun, 16 Mar 2025 17:21:23 +0100 Subject: [PATCH 03/43] actions: lint-action workflow update linters worden enkel uitgevoerd op PRs die geen draft zijn --- .github/workflows/lint-action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint-action.yml b/.github/workflows/lint-action.yml index b136a850..b8137275 100644 --- a/.github/workflows/lint-action.yml +++ b/.github/workflows/lint-action.yml @@ -11,7 +11,6 @@ on: pull_request: branches: - dev - types: [ready_for_review] # Down scope as necessary via https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token @@ -22,6 +21,7 @@ permissions: jobs: run-linters: name: Run linters + if: '! github.event.pull_request.draft' runs-on: [self-hosted, Linux, X64] steps: From 27d5c5cbabda74e15988748f52850977defd86a6 Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Tue, 18 Mar 2025 10:28:13 +0100 Subject: [PATCH 04/43] actions: workflows update workflows worden op PR uitgevoerd voor elke nodige mogelijke update van een PR --- .github/workflows/lint-action.yml | 1 + .github/workflows/testing.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/lint-action.yml b/.github/workflows/lint-action.yml index b8137275..c59191ed 100644 --- a/.github/workflows/lint-action.yml +++ b/.github/workflows/lint-action.yml @@ -11,6 +11,7 @@ on: pull_request: branches: - dev + types: ["synchronize", "ready_for_review", "opened", "reopened"] # Down scope as necessary via https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 4913f9fc..ce08509b 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -8,6 +8,7 @@ on: branches: [ "dev" ] pull_request: branches: [ "dev" ] + types: ["synchronize", "ready_for_review", "opened", "reopened"] jobs: test: From f4397255397635182ed1212fa868fd31af8a944d Mon Sep 17 00:00:00 2001 From: Lint Action Date: Tue, 18 Mar 2025 09:29:23 +0000 Subject: [PATCH 05/43] style: fix linting issues met ESLint --- frontend/vite.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 845e025f..2fad8e54 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -13,6 +13,6 @@ export default defineConfig({ }, }, build: { - target: 'esnext' //browsers can handle the latest ES features + target: 'esnext' //Browsers can handle the latest ES features }, }); From 641266f0fe7e893fe184f0acf05c6389c39b5c8a Mon Sep 17 00:00:00 2001 From: Lint Action Date: Tue, 18 Mar 2025 09:29:27 +0000 Subject: [PATCH 06/43] style: fix linting issues met Prettier --- frontend/vite.config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 2fad8e54..8f5f992b 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -13,6 +13,6 @@ export default defineConfig({ }, }, build: { - target: 'esnext' //Browsers can handle the latest ES features - }, + target: "esnext", //Browsers can handle the latest ES features + }, }); From 08d45996640f38980179aec81c5129c65b545f47 Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Tue, 18 Mar 2025 10:57:58 +0100 Subject: [PATCH 07/43] actions: update testing workflow npm run build is niet nodig om te testen --- .github/workflows/testing.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index ce08509b..20e7d221 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -29,5 +29,4 @@ jobs: node-version: ${{ matrix.node-version }} cache: 'npm' - run: npm ci - - run: npm run build --if-present - run: npm run test:unit From ec8efc554ea1b9ad5f71667c83c099c6c8c8fea2 Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Tue, 18 Mar 2025 11:02:51 +0100 Subject: [PATCH 08/43] test: test:unit script update unit tests worden enkel uitgevoerd in de back- en frontend workspaces --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index db7f5ba3..b96114ae 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "format-check": "npm run format-check --ws", "generate-docs": "python3 -m venv .venv && source .venv/bin/activate && pip install -r docs/requirements.txt && python docs/architecture/schema.py", "lint": "npm run lint --ws", - "test:unit": "npm run test:unit --ws" + "test:unit": "npm run test:unit -w backend -w frontend" }, "workspaces": [ "backend", From c14d6c53da11d3d48d9f9fbe3a038d74f5af71ea Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sat, 22 Mar 2025 17:16:33 +0100 Subject: [PATCH 09/43] refactor(backend): logger i.p.v. console --- backend/src/controllers/classes.ts | 3 ++- backend/src/controllers/students.ts | 3 ++- backend/src/controllers/teachers.ts | 7 ++++--- backend/src/controllers/users.ts | 11 ++++++----- backend/src/interfaces/assignment.ts | 3 ++- backend/src/services/groups.ts | 8 ++++---- backend/src/services/learning-objects.ts | 7 ++++--- backend/src/services/students.ts | 5 +++-- backend/src/services/teachers.ts | 5 +++-- 9 files changed, 30 insertions(+), 22 deletions(-) diff --git a/backend/src/controllers/classes.ts b/backend/src/controllers/classes.ts index ca2f5698..fbbd6737 100644 --- a/backend/src/controllers/classes.ts +++ b/backend/src/controllers/classes.ts @@ -1,6 +1,7 @@ import { Request, Response } from 'express'; import { createClass, getAllClasses, getClass, getClassStudents, getClassStudentsIds, getClassTeacherInvitations } from '../services/class.js'; import { ClassDTO } from '../interfaces/class.js'; +import { getLogger } from '../logging/initalize.js'; export async function getAllClassesHandler(req: Request, res: Response): Promise { const full = req.query.full === 'true'; @@ -49,7 +50,7 @@ export async function getClassHandler(req: Request, res: Response): Promise { const full = req.query.full === 'true'; @@ -100,7 +101,7 @@ export async function getTeacherClassHandler(req: Request, res: Response): Promi res.status(201).json(classes); } catch (error) { - console.error('Error fetching classes by teacher:', error); + getLogger().error('Error fetching classes by teacher:', error); res.status(500).json({ error: 'Internal server error' }); } } @@ -119,7 +120,7 @@ export async function getTeacherStudentHandler(req: Request, res: Response): Pro res.status(201).json(students); } catch (error) { - console.error('Error fetching students by teacher:', error); + getLogger().error('Error fetching students by teacher:', error); res.status(500).json({ error: 'Internal server error' }); } } @@ -138,7 +139,7 @@ export async function getTeacherQuestionHandler(req: Request, res: Response): Pr res.status(201).json(questions); } catch (error) { - console.error('Error fetching questions by teacher:', error); + getLogger().error('Error fetching questions by teacher:', error); res.status(500).json({ error: 'Internal server error' }); } } diff --git a/backend/src/controllers/users.ts b/backend/src/controllers/users.ts index 850c6549..b9a6be8e 100644 --- a/backend/src/controllers/users.ts +++ b/backend/src/controllers/users.ts @@ -2,6 +2,7 @@ import { Request, Response } from 'express'; import { UserService } from '../services/users.js'; import { UserDTO } from '../interfaces/user.js'; import { User } from '../entities/users/user.entity.js'; +import { getLogger } from '../logging/initalize.js'; export async function getAllUsersHandler(req: Request, res: Response, service: UserService): Promise { try { @@ -16,7 +17,7 @@ export async function getAllUsersHandler(req: Request, res: Resp res.status(201).json(users); } catch (error) { - console.error('❌ Error fetching users:', error); + getLogger().error('❌ Error fetching users:', error); res.status(500).json({ error: 'Internal server error' }); } } @@ -41,14 +42,14 @@ export async function getUserHandler(req: Request, res: Response res.status(201).json(user); } catch (error) { - console.error('❌ Error fetching users:', error); + getLogger().error('❌ Error fetching users:', error); res.status(500).json({ error: 'Internal server error' }); } } export async function createUserHandler(req: Request, res: Response, service: UserService, UserClass: new () => T) { try { - console.log('req', req); + getLogger().debug({ req: req }); const userData = req.body as UserDTO; if (!userData.username || !userData.firstName || !userData.lastName) { @@ -61,7 +62,7 @@ export async function createUserHandler(req: Request, res: Respo const newUser = await service.createUser(userData, UserClass); res.status(201).json(newUser); } catch (error) { - console.error('❌ Error creating user:', error); + getLogger().error('❌ Error creating user:', error); res.status(500).json({ error: 'Internal server error' }); } } @@ -85,7 +86,7 @@ export async function deleteUserHandler(req: Request, res: Respo res.status(200).json(deletedUser); } catch (error) { - console.error('❌ Error deleting user:', error); + getLogger().error('❌ Error deleting user:', error); res.status(500).json({ error: 'Internal server error' }); } } diff --git a/backend/src/interfaces/assignment.ts b/backend/src/interfaces/assignment.ts index 8f6120b6..45023399 100644 --- a/backend/src/interfaces/assignment.ts +++ b/backend/src/interfaces/assignment.ts @@ -3,6 +3,7 @@ import { Assignment } from '../entities/assignments/assignment.entity.js'; import { Class } from '../entities/classes/class.entity.js'; import { languageMap } from '../entities/content/language.js'; import { GroupDTO, mapToGroupDTO } from './group.js'; +import { getLogger } from '../logging/initalize.js'; export interface AssignmentDTO { id: number; @@ -46,7 +47,7 @@ export function mapToAssignment(assignmentData: AssignmentDTO, cls: Class): Assi assignment.learningPathLanguage = languageMap[assignmentData.language] || FALLBACK_LANG; assignment.within = cls; - console.log(assignment); + getLogger().debug(assignment); return assignment; } diff --git a/backend/src/services/groups.ts b/backend/src/services/groups.ts index 91091703..329d230d 100644 --- a/backend/src/services/groups.ts +++ b/backend/src/services/groups.ts @@ -9,6 +9,7 @@ import { import { Group } from '../entities/assignments/group.entity.js'; import { GroupDTO, mapToGroupDTO, mapToGroupDTOId } from '../interfaces/group.js'; import { mapToSubmissionDTO, SubmissionDTO } from '../interfaces/submission.js'; +import { getLogger } from '../logging/initalize.js'; export async function getGroup(classId: string, assignmentNumber: number, groupNumber: number, full: boolean): Promise { const classRepository = getClassRepository(); @@ -45,7 +46,7 @@ export async function createGroup(groupData: GroupDTO, classid: string, assignme 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); - console.log(members); + getLogger().debug(members); const classRepository = getClassRepository(); const cls = await classRepository.findById(classid); @@ -71,7 +72,7 @@ export async function createGroup(groupData: GroupDTO, classid: string, assignme return newGroup; } catch (e) { - console.log(e); + getLogger().error(e); return null; } } @@ -95,8 +96,7 @@ export async function getAllGroups(classId: string, assignmentNumber: number, fu const groups = await groupRepository.findAllGroupsForAssignment(assignment); if (full) { - console.log('full'); - console.log(groups); + getLogger().debug({ full: full, groups: groups }); return groups.map(mapToGroupDTO); } diff --git a/backend/src/services/learning-objects.ts b/backend/src/services/learning-objects.ts index fb579471..87856bc9 100644 --- a/backend/src/services/learning-objects.ts +++ b/backend/src/services/learning-objects.ts @@ -1,6 +1,7 @@ import { DWENGO_API_BASE } from '../config.js'; import { fetchWithLogging } from '../util/api-helper.js'; import { FilteredLearningObject, LearningObjectMetadata, LearningObjectNode, LearningPathResponse } from '../interfaces/learning-content.js'; +import { getLogger } from '../logging/initalize.js'; function filterData(data: LearningObjectMetadata, htmlUrl: string): FilteredLearningObject { return { @@ -37,7 +38,7 @@ export async function getLearningObjectById(hruid: string, language: string): Pr ); if (!metadata) { - console.error(`⚠️ WARNING: Learning object "${hruid}" not found.`); + getLogger().error(`⚠️ WARNING: Learning object "${hruid}" not found.`); return null; } @@ -53,7 +54,7 @@ async function fetchLearningObjects(hruid: string, full: boolean, language: stri const learningPathResponse: LearningPathResponse = await fetchLearningPaths([hruid], language, `Learning path for HRUID "${hruid}"`); if (!learningPathResponse.success || !learningPathResponse.data?.length) { - console.error(`⚠️ WARNING: Learning path "${hruid}" exists but contains no learning objects.`); + getLogger().error(`⚠️ WARNING: Learning path "${hruid}" exists but contains no learning objects.`); return []; } @@ -67,7 +68,7 @@ async function fetchLearningObjects(hruid: string, full: boolean, language: stri objects.filter((obj): obj is FilteredLearningObject => obj !== null) ); } catch (error) { - console.error('❌ Error fetching learning objects:', error); + getLogger().error('❌ Error fetching learning objects:', error); return []; } } diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts index 5099a18d..98fdfe0a 100644 --- a/backend/src/services/students.ts +++ b/backend/src/services/students.ts @@ -8,6 +8,7 @@ import { mapToStudent, mapToStudentDTO, StudentDTO } from '../interfaces/student import { mapToSubmissionDTO, SubmissionDTO } from '../interfaces/submission.js'; import { getAllAssignments } from './assignments.js'; import { UserService } from './users.js'; +import { getLogger } from '../logging/initalize.js'; export async function getAllStudents(): Promise { const studentRepository = getStudentRepository(); @@ -35,7 +36,7 @@ export async function createStudent(userData: StudentDTO): Promise { const teacherRepository = getTeacherRepository(); @@ -40,7 +41,7 @@ export async function createTeacher(userData: TeacherDTO): Promise Date: Sat, 22 Mar 2025 17:22:28 +0100 Subject: [PATCH 10/43] refactor(backend): no-unused-vars --- backend/src/config.ts | 1 - backend/src/controllers/students.ts | 8 ------- backend/src/controllers/teachers.ts | 4 ---- backend/src/data/repositories.ts | 2 -- backend/src/data/users/student-repository.ts | 4 ---- backend/src/data/users/teacher-repository.ts | 1 - .../entities/assignments/assignment.entity.ts | 2 +- .../src/entities/assignments/group.entity.ts | 2 +- backend/src/interfaces/assignment.ts | 2 +- backend/src/interfaces/question.ts | 2 -- backend/src/interfaces/submission.ts | 2 -- backend/src/middleware/auth/auth.ts | 4 ++-- backend/src/routes/assignments.ts | 2 +- backend/src/routes/auth.ts | 8 +++---- backend/src/routes/groups.ts | 2 +- backend/src/routes/students.ts | 4 ++-- backend/src/routes/submissions.ts | 2 +- backend/src/routes/teachers.ts | 2 +- backend/src/services/assignments.ts | 2 +- backend/src/services/groups.ts | 1 - backend/src/services/learning-objects.ts | 2 +- .../category-question-renderer.ts | 2 +- .../description-question-renderer.ts | 2 +- .../matching-question-renderer.ts | 2 +- .../numerical-question-renderer.ts | 2 +- .../short-question-renderer.ts | 2 +- .../true-false-question-renderer.ts | 2 +- .../processing/markdown/markdown-processor.ts | 8 +++---- backend/src/services/questions.ts | 6 ++---- backend/src/services/students.ts | 3 --- backend/src/services/submissions.ts | 4 ++-- backend/src/services/teachers.ts | 11 +--------- backend/src/util/links.ts | 2 +- .../data/classes/class-join-request.test.ts | 2 -- .../tests/data/questions/questions.test.ts | 5 +---- .../learning-paths/test-conditions-example.ts | 2 +- eslint.config.ts | 21 +++++++++++++++---- 37 files changed, 51 insertions(+), 84 deletions(-) diff --git a/backend/src/config.ts b/backend/src/config.ts index 69af5d74..7e1b3b6a 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -1,5 +1,4 @@ import { EnvVars, getEnvVar } from './util/envvars.js'; -import { Language } from './entities/content/language.js'; // API export const DWENGO_API_BASE = getEnvVar(EnvVars.LearningContentRepoApiBaseUrl); diff --git a/backend/src/controllers/students.ts b/backend/src/controllers/students.ts index 3ab89e31..96cdd4dd 100644 --- a/backend/src/controllers/students.ts +++ b/backend/src/controllers/students.ts @@ -9,13 +9,7 @@ import { getStudentGroups, getStudentSubmissions, } from '../services/students.js'; -import { ClassDTO } from '../interfaces/class.js'; -import { getAllAssignments } from '../services/assignments.js'; -import { getUserHandler } from './users.js'; -import { Student } from '../entities/users/student.entity.js'; import { StudentDTO } from '../interfaces/student.js'; -import { getStudentRepository } from '../data/repositories.js'; -import { UserDTO } from '../interfaces/user.js'; import { getLogger } from '../logging/initalize.js'; // TODO: accept arguments (full, ...) @@ -23,8 +17,6 @@ import { getLogger } from '../logging/initalize.js'; export async function getAllStudentsHandler(req: Request, res: Response): Promise { const full = req.query.full === 'true'; - const studentRepository = getStudentRepository(); - const students: StudentDTO[] | string[] = full ? await getAllStudents() : await getAllStudents(); if (!students) { diff --git a/backend/src/controllers/teachers.ts b/backend/src/controllers/teachers.ts index 55213bc0..7bcbb5f4 100644 --- a/backend/src/controllers/teachers.ts +++ b/backend/src/controllers/teachers.ts @@ -14,16 +14,12 @@ import { import { ClassDTO } from '../interfaces/class.js'; import { StudentDTO } from '../interfaces/student.js'; import { QuestionDTO, QuestionId } from '../interfaces/question.js'; -import { Teacher } from '../entities/users/teacher.entity.js'; import { TeacherDTO } from '../interfaces/teacher.js'; -import { getTeacherRepository } from '../data/repositories.js'; import { getLogger } from '../logging/initalize.js'; export async function getAllTeachersHandler(req: Request, res: Response): Promise { const full = req.query.full === 'true'; - const teacherRepository = getTeacherRepository(); - const teachers: TeacherDTO[] | string[] = full ? await getAllTeachers() : await getAllTeachers(); if (!teachers) { diff --git a/backend/src/data/repositories.ts b/backend/src/data/repositories.ts index 02385109..cdeb50c1 100644 --- a/backend/src/data/repositories.ts +++ b/backend/src/data/repositories.ts @@ -2,8 +2,6 @@ import { AnyEntity, EntityManager, EntityName, EntityRepository } from '@mikro-o import { forkEntityManager } from '../orm.js'; import { StudentRepository } from './users/student-repository.js'; import { Student } from '../entities/users/student.entity.js'; -import { User } from '../entities/users/user.entity.js'; -import { UserRepository } from './users/user-repository.js'; import { Teacher } from '../entities/users/teacher.entity.js'; import { TeacherRepository } from './users/teacher-repository.js'; import { Class } from '../entities/classes/class.entity.js'; diff --git a/backend/src/data/users/student-repository.ts b/backend/src/data/users/student-repository.ts index 0792678d..34b24b8b 100644 --- a/backend/src/data/users/student-repository.ts +++ b/backend/src/data/users/student-repository.ts @@ -1,9 +1,5 @@ import { Student } from '../../entities/users/student.entity.js'; -import { User } from '../../entities/users/user.entity.js'; import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; -// Import { UserRepository } from './user-repository.js'; - -// Export class StudentRepository extends UserRepository {} export class StudentRepository extends DwengoEntityRepository { public findByUsername(username: string): Promise { diff --git a/backend/src/data/users/teacher-repository.ts b/backend/src/data/users/teacher-repository.ts index 2b2bee75..825b4d18 100644 --- a/backend/src/data/users/teacher-repository.ts +++ b/backend/src/data/users/teacher-repository.ts @@ -1,6 +1,5 @@ import { Teacher } from '../../entities/users/teacher.entity.js'; import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; -import { UserRepository } from './user-repository.js'; export class TeacherRepository extends DwengoEntityRepository { public findByUsername(username: string): Promise { diff --git a/backend/src/entities/assignments/assignment.entity.ts b/backend/src/entities/assignments/assignment.entity.ts index 692e2112..daa71ed6 100644 --- a/backend/src/entities/assignments/assignment.entity.ts +++ b/backend/src/entities/assignments/assignment.entity.ts @@ -1,4 +1,4 @@ -import { Collection, Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; +import { Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; import { Class } from '../classes/class.entity.js'; import { Group } from './group.entity.js'; import { Language } from '../content/language.js'; diff --git a/backend/src/entities/assignments/group.entity.ts b/backend/src/entities/assignments/group.entity.ts index 213e0f38..cfe21f7f 100644 --- a/backend/src/entities/assignments/group.entity.ts +++ b/backend/src/entities/assignments/group.entity.ts @@ -1,4 +1,4 @@ -import { Collection, Entity, ManyToMany, ManyToOne, PrimaryKey } from '@mikro-orm/core'; +import { Entity, ManyToMany, ManyToOne, PrimaryKey } from '@mikro-orm/core'; import { Assignment } from './assignment.entity.js'; import { Student } from '../users/student.entity.js'; import { GroupRepository } from '../../data/assignments/group-repository.js'; diff --git a/backend/src/interfaces/assignment.ts b/backend/src/interfaces/assignment.ts index 45023399..698b5b40 100644 --- a/backend/src/interfaces/assignment.ts +++ b/backend/src/interfaces/assignment.ts @@ -2,7 +2,7 @@ import { FALLBACK_LANG } from '../config.js'; import { Assignment } from '../entities/assignments/assignment.entity.js'; import { Class } from '../entities/classes/class.entity.js'; import { languageMap } from '../entities/content/language.js'; -import { GroupDTO, mapToGroupDTO } from './group.js'; +import { GroupDTO } from './group.js'; import { getLogger } from '../logging/initalize.js'; export interface AssignmentDTO { diff --git a/backend/src/interfaces/question.ts b/backend/src/interfaces/question.ts index 8cca42f6..0da87eb7 100644 --- a/backend/src/interfaces/question.ts +++ b/backend/src/interfaces/question.ts @@ -1,8 +1,6 @@ import { Question } from '../entities/questions/question.entity.js'; -import { UserDTO } from './user.js'; import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; import { mapToStudentDTO, StudentDTO } from './student.js'; -import { TeacherDTO } from './teacher.js'; export interface QuestionDTO { learningObjectIdentifier: LearningObjectIdentifier; diff --git a/backend/src/interfaces/submission.ts b/backend/src/interfaces/submission.ts index fbaf520d..522d48cf 100644 --- a/backend/src/interfaces/submission.ts +++ b/backend/src/interfaces/submission.ts @@ -2,8 +2,6 @@ import { Submission } from '../entities/assignments/submission.entity.js'; import { Language } from '../entities/content/language.js'; import { GroupDTO, mapToGroupDTO } from './group.js'; import { mapToStudent, mapToStudentDTO, StudentDTO } from './student.js'; -import { mapToUser } from './user'; -import { Student } from '../entities/users/student.entity'; export interface SubmissionDTO { learningObjectHruid: string; diff --git a/backend/src/middleware/auth/auth.ts b/backend/src/middleware/auth/auth.ts index 5ff5a53c..0c8d31ff 100644 --- a/backend/src/middleware/auth/auth.ts +++ b/backend/src/middleware/auth/auth.ts @@ -100,7 +100,7 @@ function getAuthenticationInfo(req: AuthenticatedRequest): AuthenticationInfo | * 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. */ -const addAuthenticationInfo = (req: AuthenticatedRequest, res: express.Response, next: express.NextFunction) => { +const addAuthenticationInfo = (req: AuthenticatedRequest, _res: express.Response, next: express.NextFunction) => { req.auth = getAuthenticationInfo(req); next(); }; @@ -115,7 +115,7 @@ export const authenticateUser = [verifyJwtToken, addAuthenticationInfo]; */ export const authorize = (accessCondition: (auth: AuthenticationInfo) => boolean) => - (req: AuthenticatedRequest, res: express.Response, next: express.NextFunction): void => { + (req: AuthenticatedRequest, _res: express.Response, next: express.NextFunction): void => { if (!req.auth) { throw new UnauthorizedException(); } else if (!accessCondition(req.auth)) { diff --git a/backend/src/routes/assignments.ts b/backend/src/routes/assignments.ts index a733d093..3652dcc6 100644 --- a/backend/src/routes/assignments.ts +++ b/backend/src/routes/assignments.ts @@ -19,7 +19,7 @@ router.get('/:id', getAssignmentHandler); router.get('/:id/submissions', getAssignmentsSubmissionsHandler); -router.get('/:id/questions', (req, res) => { +router.get('/:id/questions', (_req, res) => { res.json({ questions: ['0'], }); diff --git a/backend/src/routes/auth.ts b/backend/src/routes/auth.ts index 778e51fd..4a1f27d2 100644 --- a/backend/src/routes/auth.ts +++ b/backend/src/routes/auth.ts @@ -4,21 +4,21 @@ import { authenticatedOnly, studentsOnly, teachersOnly } from '../middleware/aut const router = express.Router(); // Returns auth configuration for frontend -router.get('/config', (req, res) => { +router.get('/config', (_req, res) => { res.json(getFrontendAuthConfig()); }); -router.get('/testAuthenticatedOnly', authenticatedOnly, (req, res) => { +router.get('/testAuthenticatedOnly', authenticatedOnly, (_req, res) => { /* #swagger.security = [{ "student": [ ] }, { "teacher": [ ] }] */ res.json({ message: 'If you see this, you should be authenticated!' }); }); -router.get('/testStudentsOnly', studentsOnly, (req, res) => { +router.get('/testStudentsOnly', studentsOnly, (_req, res) => { /* #swagger.security = [{ "student": [ ] }] */ res.json({ message: 'If you see this, you should be a student!' }); }); -router.get('/testTeachersOnly', teachersOnly, (req, res) => { +router.get('/testTeachersOnly', teachersOnly, (_req, res) => { /* #swagger.security = [{ "teacher": [ ] }] */ res.json({ message: 'If you see this, you should be a teacher!' }); }); diff --git a/backend/src/routes/groups.ts b/backend/src/routes/groups.ts index 0c9692b0..dc8917bd 100644 --- a/backend/src/routes/groups.ts +++ b/backend/src/routes/groups.ts @@ -14,7 +14,7 @@ router.get('/:groupid', getGroupHandler); router.get('/:groupid', getGroupSubmissionsHandler); // The list of questions a group has made -router.get('/:id/questions', (req, res) => { +router.get('/:id/questions', (_req, res) => { res.json({ questions: ['0'], }); diff --git a/backend/src/routes/students.ts b/backend/src/routes/students.ts index 7ed7a666..d58c2adc 100644 --- a/backend/src/routes/students.ts +++ b/backend/src/routes/students.ts @@ -9,7 +9,7 @@ import { getStudentHandler, getStudentSubmissionsHandler, } from '../controllers/students.js'; -import { getStudentGroups } from '../services/students.js'; + const router = express.Router(); // Root endpoint used to search objects @@ -37,7 +37,7 @@ router.get('/:id/assignments', getStudentAssignmentsHandler); router.get('/:id/groups', getStudentGroupsHandler); // A list of questions a user has created -router.get('/:id/questions', (req, res) => { +router.get('/:id/questions', (_req, res) => { res.json({ questions: ['0'], }); diff --git a/backend/src/routes/submissions.ts b/backend/src/routes/submissions.ts index 4db93027..8e9831b9 100644 --- a/backend/src/routes/submissions.ts +++ b/backend/src/routes/submissions.ts @@ -3,7 +3,7 @@ import { createSubmissionHandler, deleteSubmissionHandler, getSubmissionHandler const router = express.Router({ mergeParams: true }); // Root endpoint used to search objects -router.get('/', (req, res) => { +router.get('/', (_req, res) => { res.json({ submissions: ['0', '1'], }); diff --git a/backend/src/routes/teachers.ts b/backend/src/routes/teachers.ts index c04e1575..3782a6ca 100644 --- a/backend/src/routes/teachers.ts +++ b/backend/src/routes/teachers.ts @@ -28,7 +28,7 @@ router.get('/:username/students', getTeacherStudentHandler); router.get('/:username/questions', getTeacherQuestionHandler); // Invitations to other classes a teacher received -router.get('/:id/invitations', (req, res) => { +router.get('/:id/invitations', (_req, res) => { res.json({ invitations: ['0'], }); diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts index be121810..c637b822 100644 --- a/backend/src/services/assignments.ts +++ b/backend/src/services/assignments.ts @@ -37,7 +37,7 @@ export async function createAssignment(classid: string, assignmentData: Assignme await assignmentRepository.save(newAssignment); return newAssignment; - } catch (e) { + } catch (_) { return null; } } diff --git a/backend/src/services/groups.ts b/backend/src/services/groups.ts index 329d230d..ecc92293 100644 --- a/backend/src/services/groups.ts +++ b/backend/src/services/groups.ts @@ -1,4 +1,3 @@ -import { GroupRepository } from '../data/assignments/group-repository.js'; import { getAssignmentRepository, getClassRepository, diff --git a/backend/src/services/learning-objects.ts b/backend/src/services/learning-objects.ts index 87856bc9..5298e5b3 100644 --- a/backend/src/services/learning-objects.ts +++ b/backend/src/services/learning-objects.ts @@ -86,6 +86,6 @@ export async function getLearningObjectsFromPath(hruid: string, language: string export async function getLearningObjectIdsFromPath(hruid: string, language: string): Promise { return (await fetchLearningObjects(hruid, false, language)) as string[]; } -function fetchLearningPaths(arg0: string[], language: string, arg2: string): LearningPathResponse | PromiseLike { +function fetchLearningPaths(_arg0: string[], _language: string, _arg2: string): LearningPathResponse | PromiseLike { throw new Error('Function not implemented.'); } diff --git a/backend/src/services/learning-objects/processing/gift/question-renderers/category-question-renderer.ts b/backend/src/services/learning-objects/processing/gift/question-renderers/category-question-renderer.ts index 507e5ada..256a38a7 100644 --- a/backend/src/services/learning-objects/processing/gift/question-renderers/category-question-renderer.ts +++ b/backend/src/services/learning-objects/processing/gift/question-renderers/category-question-renderer.ts @@ -3,7 +3,7 @@ import { Category } from 'gift-pegjs'; import { ProcessingError } from '../../processing-error.js'; export class CategoryQuestionRenderer extends GIFTQuestionRenderer { - render(question: Category, questionNumber: number): string { + render(_question: Category, _questionNumber: number): string { throw new ProcessingError("The question type 'Category' is not supported yet!"); } } diff --git a/backend/src/services/learning-objects/processing/gift/question-renderers/description-question-renderer.ts b/backend/src/services/learning-objects/processing/gift/question-renderers/description-question-renderer.ts index 0238d76a..7bab255f 100644 --- a/backend/src/services/learning-objects/processing/gift/question-renderers/description-question-renderer.ts +++ b/backend/src/services/learning-objects/processing/gift/question-renderers/description-question-renderer.ts @@ -3,7 +3,7 @@ import { Description } from 'gift-pegjs'; import { ProcessingError } from '../../processing-error.js'; export class DescriptionQuestionRenderer extends GIFTQuestionRenderer { - render(question: Description, questionNumber: number): string { + render(_question: Description, _questionNumber: number): string { throw new ProcessingError("The question type 'Description' is not supported yet!"); } } diff --git a/backend/src/services/learning-objects/processing/gift/question-renderers/matching-question-renderer.ts b/backend/src/services/learning-objects/processing/gift/question-renderers/matching-question-renderer.ts index bb6e9737..21713920 100644 --- a/backend/src/services/learning-objects/processing/gift/question-renderers/matching-question-renderer.ts +++ b/backend/src/services/learning-objects/processing/gift/question-renderers/matching-question-renderer.ts @@ -3,7 +3,7 @@ import { Matching } from 'gift-pegjs'; import { ProcessingError } from '../../processing-error.js'; export class MatchingQuestionRenderer extends GIFTQuestionRenderer { - render(question: Matching, questionNumber: number): string { + render(_question: Matching, _questionNumber: number): string { throw new ProcessingError("The question type 'Matching' is not supported yet!"); } } diff --git a/backend/src/services/learning-objects/processing/gift/question-renderers/numerical-question-renderer.ts b/backend/src/services/learning-objects/processing/gift/question-renderers/numerical-question-renderer.ts index 32fdb06e..2286cd42 100644 --- a/backend/src/services/learning-objects/processing/gift/question-renderers/numerical-question-renderer.ts +++ b/backend/src/services/learning-objects/processing/gift/question-renderers/numerical-question-renderer.ts @@ -3,7 +3,7 @@ import { Numerical } from 'gift-pegjs'; import { ProcessingError } from '../../processing-error.js'; export class NumericalQuestionRenderer extends GIFTQuestionRenderer { - render(question: Numerical, questionNumber: number): string { + render(_question: Numerical, _questionNumber: number): string { throw new ProcessingError("The question type 'Numerical' is not supported yet!"); } } diff --git a/backend/src/services/learning-objects/processing/gift/question-renderers/short-question-renderer.ts b/backend/src/services/learning-objects/processing/gift/question-renderers/short-question-renderer.ts index 5a63531f..1e5527e0 100644 --- a/backend/src/services/learning-objects/processing/gift/question-renderers/short-question-renderer.ts +++ b/backend/src/services/learning-objects/processing/gift/question-renderers/short-question-renderer.ts @@ -3,7 +3,7 @@ import { ShortAnswer } from 'gift-pegjs'; import { ProcessingError } from '../../processing-error.js'; export class ShortQuestionRenderer extends GIFTQuestionRenderer { - render(question: ShortAnswer, questionNumber: number): string { + render(_question: ShortAnswer, _questionNumber: number): string { throw new ProcessingError("The question type 'ShortAnswer' is not supported yet!"); } } diff --git a/backend/src/services/learning-objects/processing/gift/question-renderers/true-false-question-renderer.ts b/backend/src/services/learning-objects/processing/gift/question-renderers/true-false-question-renderer.ts index 98148130..fabbc634 100644 --- a/backend/src/services/learning-objects/processing/gift/question-renderers/true-false-question-renderer.ts +++ b/backend/src/services/learning-objects/processing/gift/question-renderers/true-false-question-renderer.ts @@ -3,7 +3,7 @@ import { TrueFalse } from 'gift-pegjs'; import { ProcessingError } from '../../processing-error.js'; export class TrueFalseQuestionRenderer extends GIFTQuestionRenderer { - render(question: TrueFalse, questionNumber: number): string { + render(_question: TrueFalse, _questionNumber: number): string { throw new ProcessingError("The question type 'TrueFalse' is not supported yet!"); } } diff --git a/backend/src/services/learning-objects/processing/markdown/markdown-processor.ts b/backend/src/services/learning-objects/processing/markdown/markdown-processor.ts index 1de6b3d5..39f2adc8 100644 --- a/backend/src/services/learning-objects/processing/markdown/markdown-processor.ts +++ b/backend/src/services/learning-objects/processing/markdown/markdown-processor.ts @@ -15,22 +15,20 @@ class MarkdownProcessor extends StringProcessor { } override renderFn(mdText: string) { - let html = ''; try { marked.use({ renderer: dwengoMarkedRenderer }); - html = marked(mdText, { async: false }); - html = this.replaceLinks(html); // Replace html image links path + const html = marked(mdText, { async: false }); + return this.replaceLinks(html); // Replace html image links path } catch (e: any) { throw new ProcessingError(e.message); } - return html; } replaceLinks(html: string) { const proc = new InlineImageProcessor(); html = html.replace( //g, - (match: string, src: string, alt: string, altText: string, title: string, titleText: string) => proc.render(src) + (_match: string, src: string, _alt: string, _altText: string, _title: string, _titleText: string) => proc.render(src) ); return html; } diff --git a/backend/src/services/questions.ts b/backend/src/services/questions.ts index ee003bcd..0433b69f 100644 --- a/backend/src/services/questions.ts +++ b/backend/src/services/questions.ts @@ -5,8 +5,6 @@ import { Answer } from '../entities/questions/answer.entity.js'; import { mapToAnswerDTO, mapToAnswerId } from '../interfaces/answer.js'; import { QuestionRepository } from '../data/questions/question-repository.js'; import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; -import { mapToUser } from '../interfaces/user.js'; -import { Student } from '../entities/users/student.entity.js'; import { mapToStudent } from '../interfaces/student.js'; export async function getAllQuestions(id: LearningObjectIdentifier, full: boolean): Promise { @@ -81,7 +79,7 @@ export async function createQuestion(questionDTO: QuestionDTO) { author, content: questionDTO.content, }); - } catch (e) { + } catch (_) { return null; } @@ -99,7 +97,7 @@ export async function deleteQuestion(questionId: QuestionId) { try { await questionRepository.removeQuestionByLearningObjectAndSequenceNumber(questionId.learningObjectIdentifier, questionId.sequenceNumber); - } catch (e) { + } catch (_) { return null; } diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts index 98fdfe0a..935cfe54 100644 --- a/backend/src/services/students.ts +++ b/backend/src/services/students.ts @@ -1,13 +1,10 @@ import { getClassRepository, getGroupRepository, getStudentRepository, getSubmissionRepository } from '../data/repositories.js'; -import { Class } from '../entities/classes/class.entity.js'; -import { Student } from '../entities/users/student.entity.js'; import { AssignmentDTO } from '../interfaces/assignment.js'; import { ClassDTO, mapToClassDTO } from '../interfaces/class.js'; import { GroupDTO, mapToGroupDTO, mapToGroupDTOId } from '../interfaces/group.js'; import { mapToStudent, mapToStudentDTO, StudentDTO } from '../interfaces/student.js'; import { mapToSubmissionDTO, SubmissionDTO } from '../interfaces/submission.js'; import { getAllAssignments } from './assignments.js'; -import { UserService } from './users.js'; import { getLogger } from '../logging/initalize.js'; export async function getAllStudents(): Promise { diff --git a/backend/src/services/submissions.ts b/backend/src/services/submissions.ts index a8fa96c7..153ce169 100644 --- a/backend/src/services/submissions.ts +++ b/backend/src/services/submissions.ts @@ -1,4 +1,4 @@ -import { getGroupRepository, getSubmissionRepository } from '../data/repositories.js'; +import { getSubmissionRepository } from '../data/repositories.js'; import { Language } from '../entities/content/language.js'; import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; import { mapToSubmission, mapToSubmissionDTO, SubmissionDTO } from '../interfaces/submission.js'; @@ -28,7 +28,7 @@ export async function createSubmission(submissionDTO: SubmissionDTO) { try { const newSubmission = await submissionRepository.create(submission); await submissionRepository.save(newSubmission); - } catch (e) { + } catch (_) { return null; } diff --git a/backend/src/services/teachers.ts b/backend/src/services/teachers.ts index 5e52b822..1949152f 100644 --- a/backend/src/services/teachers.ts +++ b/backend/src/services/teachers.ts @@ -1,17 +1,8 @@ -import { - getClassRepository, - getLearningObjectRepository, - getQuestionRepository, - getStudentRepository, - getTeacherRepository, -} from '../data/repositories.js'; -import { Teacher } from '../entities/users/teacher.entity.js'; +import { getClassRepository, getLearningObjectRepository, getQuestionRepository, getTeacherRepository } from '../data/repositories.js'; import { ClassDTO, mapToClassDTO } from '../interfaces/class.js'; import { getClassStudents } from './class.js'; import { StudentDTO } from '../interfaces/student.js'; import { mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId } from '../interfaces/question.js'; -import { UserService } from './users.js'; -import { mapToUser } from '../interfaces/user.js'; import { mapToTeacher, mapToTeacherDTO, TeacherDTO } from '../interfaces/teacher.js'; import { getLogger } from '../logging/initalize.js'; diff --git a/backend/src/util/links.ts b/backend/src/util/links.ts index 73e27965..c4b54c72 100644 --- a/backend/src/util/links.ts +++ b/backend/src/util/links.ts @@ -4,7 +4,7 @@ export function isValidHttpUrl(url: string): boolean { try { const parsedUrl = new URL(url, 'http://test.be'); return parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:'; - } catch (e) { + } catch (_) { return false; } } diff --git a/backend/tests/data/classes/class-join-request.test.ts b/backend/tests/data/classes/class-join-request.test.ts index f0aa2f62..cd53bf05 100644 --- a/backend/tests/data/classes/class-join-request.test.ts +++ b/backend/tests/data/classes/class-join-request.test.ts @@ -3,9 +3,7 @@ import { setupTestApp } from '../../setup-tests'; import { ClassJoinRequestRepository } from '../../../src/data/classes/class-join-request-repository'; import { getClassJoinRequestRepository, getClassRepository, getStudentRepository } from '../../../src/data/repositories'; import { StudentRepository } from '../../../src/data/users/student-repository'; -import { Class } from '../../../src/entities/classes/class.entity'; import { ClassRepository } from '../../../src/data/classes/class-repository'; -import { Student } from '../../../src/entities/users/student.entity'; describe('ClassJoinRequestRepository', () => { let classJoinRequestRepository: ClassJoinRequestRepository; diff --git a/backend/tests/data/questions/questions.test.ts b/backend/tests/data/questions/questions.test.ts index 7b408df4..c1d2b483 100644 --- a/backend/tests/data/questions/questions.test.ts +++ b/backend/tests/data/questions/questions.test.ts @@ -1,22 +1,19 @@ import { beforeAll, describe, expect, it } from 'vitest'; import { setupTestApp } from '../../setup-tests'; import { QuestionRepository } from '../../../src/data/questions/question-repository'; -import { getLearningObjectRepository, getQuestionRepository, getStudentRepository } from '../../../src/data/repositories'; +import { getQuestionRepository, getStudentRepository } from '../../../src/data/repositories'; import { StudentRepository } from '../../../src/data/users/student-repository'; -import { LearningObjectRepository } from '../../../src/data/content/learning-object-repository'; import { LearningObjectIdentifier } from '../../../src/entities/content/learning-object-identifier'; import { Language } from '../../../src/entities/content/language'; describe('QuestionRepository', () => { let questionRepository: QuestionRepository; let studentRepository: StudentRepository; - let learningObjectRepository: LearningObjectRepository; beforeAll(async () => { await setupTestApp(); questionRepository = getQuestionRepository(); studentRepository = getStudentRepository(); - learningObjectRepository = getLearningObjectRepository(); }); it('should return all questions part of the given learning object', async () => { diff --git a/backend/tests/test-assets/learning-paths/test-conditions-example.ts b/backend/tests/test-assets/learning-paths/test-conditions-example.ts index 07857235..ee957f91 100644 --- a/backend/tests/test-assets/learning-paths/test-conditions-example.ts +++ b/backend/tests/test-assets/learning-paths/test-conditions-example.ts @@ -79,6 +79,6 @@ export function createConditionTestLearningPathAndLearningObjects() { }; } -const example: LearningPathExample = { +const _example: LearningPathExample = { createLearningPath: () => createConditionTestLearningPathAndLearningObjects().learningPath, }; diff --git a/eslint.config.ts b/eslint.config.ts index 52a36775..a3001cc5 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -28,6 +28,23 @@ export default [ reportUnusedInlineConfigs: 'error', }, rules: { + 'no-unused-expressions': 'off', + '@typescript-eslint/no-unused-expressions': 'warn', + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': [ + 'warn', + { + 'args': 'all', + 'argsIgnorePattern': '^_', + 'caughtErrors': 'all', + 'caughtErrorsIgnorePattern': '^_', + 'varsIgnorePattern': '^_', + 'ignoreRestSiblings': true, + } + ], + 'no-use-before-define': 'off', + '@typescript-eslint/no-use-before-define': 'off', + 'no-await-in-loop': 'warn', 'no-constructor-return': 'error', 'no-duplicate-imports': 'error', @@ -36,12 +53,8 @@ export default [ 'no-template-curly-in-string': 'error', 'no-unmodified-loop-condition': 'warn', 'no-unreachable-loop': 'warn', - 'no-use-before-define': 'error', 'no-useless-assignment': 'error', - 'no-unused-vars': 'off', - '@typescript-eslint/no-unused-vars': 'error', - 'arrow-body-style': ['warn', 'as-needed'], 'block-scoped-var': 'warn', camelcase: 'warn', From 7a286f5650a995467aeb873f91b4d2564ac0dbd2 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sat, 22 Mar 2025 17:38:10 +0100 Subject: [PATCH 11/43] refactor(backend): naming-convention --- backend/src/app.ts | 4 ++-- backend/src/config.ts | 6 ++--- backend/src/controllers/auth.ts | 10 ++++----- backend/src/controllers/learning-objects.ts | 4 ++-- backend/src/controllers/users.ts | 4 ++-- backend/src/interfaces/teacher.ts | 4 ++-- backend/src/logging/initalize.ts | 4 ++-- backend/src/middleware/auth/auth.ts | 12 +++++----- backend/src/middleware/cors.ts | 6 ++--- backend/src/mikro-orm.config.ts | 14 ++++++------ backend/src/orm.ts | 4 ++-- .../learning-object-service.ts | 4 ++-- .../learning-paths/learning-path-service.ts | 4 ++-- backend/src/services/users.ts | 4 ++-- backend/src/util/{envvars.ts => envVars.ts} | 2 +- .../learning-object-service.test.ts | 4 ++-- .../dummy/dummy-learning-object-example.ts | 4 ++-- .../pn-werkingnotebooks-example.ts | 4 ++-- .../test-essay/test-essay-example.ts | 4 ++-- .../test-multiple-choice-example.ts | 4 ++-- .../learning-paths/pn-werking-example.ts | 4 ++-- .../learning-paths/test-conditions-example.ts | 8 ++----- eslint.config.ts | 22 ++++++++++++++++++- 23 files changed, 78 insertions(+), 62 deletions(-) rename backend/src/util/{envvars.ts => envVars.ts} (98%) diff --git a/backend/src/app.ts b/backend/src/app.ts index 0c5e8892..c8658cd5 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -5,7 +5,7 @@ import cors from './middleware/cors.js'; import { getLogger, Logger } from './logging/initalize.js'; import { responseTimeLogger } from './logging/responseTimeLogger.js'; import responseTime from 'response-time'; -import { EnvVars, getNumericEnvVar } from './util/envvars.js'; +import { envVars, getNumericEnvVar } from './util/envVars.js'; import apiRouter from './routes/router.js'; import swaggerMiddleware from './swagger.js'; import swaggerUi from 'swagger-ui-express'; @@ -13,7 +13,7 @@ import swaggerUi from 'swagger-ui-express'; const logger: Logger = getLogger(); const app: Express = express(); -const port: string | number = getNumericEnvVar(EnvVars.Port); +const port: string | number = getNumericEnvVar(envVars.Port); app.use(express.json()); app.use(cors); diff --git a/backend/src/config.ts b/backend/src/config.ts index 7e1b3b6a..825f20b8 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -1,8 +1,8 @@ -import { EnvVars, getEnvVar } from './util/envvars.js'; +import { envVars, getEnvVar } from './util/envVars.js'; // API -export const DWENGO_API_BASE = getEnvVar(EnvVars.LearningContentRepoApiBaseUrl); -export const FALLBACK_LANG = getEnvVar(EnvVars.FallbackLanguage); +export const DWENGO_API_BASE = getEnvVar(envVars.LearningContentRepoApiBaseUrl); +export const FALLBACK_LANG = getEnvVar(envVars.FallbackLanguage); // Logging export const LOG_LEVEL: string = 'development' === process.env.NODE_ENV ? 'debug' : 'info'; diff --git a/backend/src/controllers/auth.ts b/backend/src/controllers/auth.ts index 409ead0c..d3f3affb 100644 --- a/backend/src/controllers/auth.ts +++ b/backend/src/controllers/auth.ts @@ -1,4 +1,4 @@ -import { EnvVars, getEnvVar } from '../util/envvars.js'; +import { envVars, getEnvVar } from '../util/envVars.js'; type FrontendIdpConfig = { authority: string; @@ -18,14 +18,14 @@ const RESPONSE_TYPE = 'code'; export function getFrontendAuthConfig(): FrontendAuthConfig { return { student: { - authority: getEnvVar(EnvVars.IdpStudentUrl), - clientId: getEnvVar(EnvVars.IdpStudentClientId), + authority: getEnvVar(envVars.IdpStudentUrl), + clientId: getEnvVar(envVars.IdpStudentClientId), scope: SCOPE, responseType: RESPONSE_TYPE, }, teacher: { - authority: getEnvVar(EnvVars.IdpTeacherUrl), - clientId: getEnvVar(EnvVars.IdpTeacherClientId), + authority: getEnvVar(envVars.IdpTeacherUrl), + clientId: getEnvVar(envVars.IdpTeacherClientId), scope: SCOPE, responseType: RESPONSE_TYPE, }, diff --git a/backend/src/controllers/learning-objects.ts b/backend/src/controllers/learning-objects.ts index 455a4006..6e331ff3 100644 --- a/backend/src/controllers/learning-objects.ts +++ b/backend/src/controllers/learning-objects.ts @@ -2,7 +2,7 @@ import { Request, Response } from 'express'; import { FALLBACK_LANG } from '../config.js'; import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from '../interfaces/learning-content.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 { BadRequestException } from '../exceptions.js'; import attachmentService from '../services/learning-objects/attachment-service.js'; @@ -14,7 +14,7 @@ function getLearningObjectIdentifierFromRequest(req: Request): LearningObjectIde } return { hruid: req.params.hruid as string, - language: (req.query.language || getEnvVar(EnvVars.FallbackLanguage)) as Language, + language: (req.query.language || getEnvVar(envVars.FallbackLanguage)) as Language, version: parseInt(req.query.version as string), }; } diff --git a/backend/src/controllers/users.ts b/backend/src/controllers/users.ts index b9a6be8e..ac3f824b 100644 --- a/backend/src/controllers/users.ts +++ b/backend/src/controllers/users.ts @@ -47,7 +47,7 @@ export async function getUserHandler(req: Request, res: Response } } -export async function createUserHandler(req: Request, res: Response, service: UserService, UserClass: new () => T) { +export async function createUserHandler(req: Request, res: Response, service: UserService, userClass: new () => T) { try { getLogger().debug({ req: req }); const userData = req.body as UserDTO; @@ -59,7 +59,7 @@ export async function createUserHandler(req: Request, res: Respo return; } - const newUser = await service.createUser(userData, UserClass); + const newUser = await service.createUser(userData, userClass); res.status(201).json(newUser); } catch (error) { getLogger().error('❌ Error creating user:', error); diff --git a/backend/src/interfaces/teacher.ts b/backend/src/interfaces/teacher.ts index 4dd6adb4..00ba8dc9 100644 --- a/backend/src/interfaces/teacher.ts +++ b/backend/src/interfaces/teacher.ts @@ -22,8 +22,8 @@ export function mapToTeacherDTO(teacher: Teacher): TeacherDTO { }; } -export function mapToTeacher(TeacherData: TeacherDTO): Teacher { - const teacher = new Teacher(TeacherData.username, TeacherData.firstName, TeacherData.lastName); +export function mapToTeacher(teacherDTO: TeacherDTO): Teacher { + const teacher = new Teacher(teacherDTO.username, teacherDTO.firstName, teacherDTO.lastName); return teacher; } diff --git a/backend/src/logging/initalize.ts b/backend/src/logging/initalize.ts index 1ff761c9..7375aa37 100644 --- a/backend/src/logging/initalize.ts +++ b/backend/src/logging/initalize.ts @@ -9,7 +9,7 @@ export class Logger extends WinstonLogger { } } -const Labels: LokiLabels = { +const lokiLabels: LokiLabels = { source: 'Dwengo-Backend', service: 'API', host: 'localhost', @@ -24,7 +24,7 @@ function initializeLogger(): Logger { const lokiTransport: LokiTransport = new LokiTransport({ host: LOKI_HOST, - labels: Labels, + labels: lokiLabels, level: LOG_LEVEL, json: true, format: format.combine(format.timestamp(), format.json()), diff --git a/backend/src/middleware/auth/auth.ts b/backend/src/middleware/auth/auth.ts index 0c8d31ff..5389dc77 100644 --- a/backend/src/middleware/auth/auth.ts +++ b/backend/src/middleware/auth/auth.ts @@ -1,4 +1,4 @@ -import { EnvVars, getEnvVar } from '../../util/envvars.js'; +import { envVars, getEnvVar } from '../../util/envVars.js'; import { expressjwt } from 'express-jwt'; import { JwtPayload } from 'jsonwebtoken'; import jwksClient from 'jwks-rsa'; @@ -32,12 +32,12 @@ function createJwksClient(uri: string): jwksClient.JwksClient { const idpConfigs = { student: { - issuer: getEnvVar(EnvVars.IdpStudentUrl), - jwksClient: createJwksClient(getEnvVar(EnvVars.IdpStudentJwksEndpoint)), + issuer: getEnvVar(envVars.IdpStudentUrl), + jwksClient: createJwksClient(getEnvVar(envVars.IdpStudentJwksEndpoint)), }, teacher: { - issuer: getEnvVar(EnvVars.IdpTeacherUrl), - jwksClient: createJwksClient(getEnvVar(EnvVars.IdpTeacherJwksEndpoint)), + issuer: getEnvVar(envVars.IdpTeacherUrl), + jwksClient: createJwksClient(getEnvVar(envVars.IdpTeacherJwksEndpoint)), }, }; @@ -63,7 +63,7 @@ const verifyJwtToken = expressjwt({ } return signingKey.getPublicKey(); }, - audience: getEnvVar(EnvVars.IdpAudience), + audience: getEnvVar(envVars.IdpAudience), algorithms: [JWT_ALGORITHM], credentialsRequired: false, requestProperty: REQUEST_PROPERTY_FOR_JWT_PAYLOAD, diff --git a/backend/src/middleware/cors.ts b/backend/src/middleware/cors.ts index 3d2c9be0..48e0704d 100644 --- a/backend/src/middleware/cors.ts +++ b/backend/src/middleware/cors.ts @@ -1,7 +1,7 @@ import cors from 'cors'; -import { EnvVars, getEnvVar } from '../util/envvars.js'; +import { envVars, getEnvVar } from '../util/envVars.js'; export default cors({ - origin: getEnvVar(EnvVars.CorsAllowedOrigins).split(','), - allowedHeaders: getEnvVar(EnvVars.CorsAllowedHeaders).split(','), + origin: getEnvVar(envVars.CorsAllowedOrigins).split(','), + allowedHeaders: getEnvVar(envVars.CorsAllowedHeaders).split(','), }); diff --git a/backend/src/mikro-orm.config.ts b/backend/src/mikro-orm.config.ts index c9cf6ed9..d53e0398 100644 --- a/backend/src/mikro-orm.config.ts +++ b/backend/src/mikro-orm.config.ts @@ -1,6 +1,6 @@ import { LoggerOptions, Options } from '@mikro-orm/core'; import { PostgreSqlDriver } from '@mikro-orm/postgresql'; -import { EnvVars, getEnvVar, getNumericEnvVar } from './util/envvars.js'; +import { envVars, getEnvVar, getNumericEnvVar } from './util/envVars.js'; import { SqliteDriver } from '@mikro-orm/sqlite'; import { MikroOrmLogger } from './logging/mikroOrmLogger.js'; import { LOG_LEVEL } from './config.js'; @@ -47,7 +47,7 @@ function config(testingMode: boolean = false): Options { if (testingMode) { return { driver: SqliteDriver, - dbName: getEnvVar(EnvVars.DbName), + dbName: getEnvVar(envVars.DbName), subscribers: [new SqliteAutoincrementSubscriber()], entities: entities, // EntitiesTs: entitiesTs, @@ -60,11 +60,11 @@ function config(testingMode: boolean = false): Options { return { driver: PostgreSqlDriver, - host: getEnvVar(EnvVars.DbHost), - port: getNumericEnvVar(EnvVars.DbPort), - dbName: getEnvVar(EnvVars.DbName), - user: getEnvVar(EnvVars.DbUsername), - password: getEnvVar(EnvVars.DbPassword), + host: getEnvVar(envVars.DbHost), + port: getNumericEnvVar(envVars.DbPort), + dbName: getEnvVar(envVars.DbName), + user: getEnvVar(envVars.DbUsername), + password: getEnvVar(envVars.DbPassword), entities: entities, // EntitiesTs: entitiesTs, diff --git a/backend/src/orm.ts b/backend/src/orm.ts index 93feea7a..8ab5258a 100644 --- a/backend/src/orm.ts +++ b/backend/src/orm.ts @@ -1,6 +1,6 @@ import { EntityManager, MikroORM } from '@mikro-orm/core'; import config from './mikro-orm.config.js'; -import { EnvVars, getEnvVar } from './util/envvars.js'; +import { envVars, getEnvVar } from './util/envVars.js'; import { getLogger, Logger } from './logging/initalize.js'; let orm: MikroORM | undefined; @@ -12,7 +12,7 @@ export async function initORM(testingMode: boolean = false) { orm = await MikroORM.init(config(testingMode)); // Update the database scheme if necessary and enabled. - if (getEnvVar(EnvVars.DbUpdate)) { + if (getEnvVar(envVars.DbUpdate)) { await orm.schema.updateSchema(); } else { const diff = await orm.schema.getUpdateSchemaSQL(); diff --git a/backend/src/services/learning-objects/learning-object-service.ts b/backend/src/services/learning-objects/learning-object-service.ts index 8289660b..95e5daff 100644 --- a/backend/src/services/learning-objects/learning-object-service.ts +++ b/backend/src/services/learning-objects/learning-object-service.ts @@ -1,11 +1,11 @@ import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from '../../interfaces/learning-content.js'; import dwengoApiLearningObjectProvider from './dwengo-api-learning-object-provider.js'; import { LearningObjectProvider } from './learning-object-provider.js'; -import { EnvVars, getEnvVar } from '../../util/envvars.js'; +import { envVars, getEnvVar } from '../../util/envVars.js'; import databaseLearningObjectProvider from './database-learning-object-provider.js'; function getProvider(id: LearningObjectIdentifier): LearningObjectProvider { - if (id.hruid.startsWith(getEnvVar(EnvVars.UserContentPrefix))) { + if (id.hruid.startsWith(getEnvVar(envVars.UserContentPrefix))) { return databaseLearningObjectProvider; } return dwengoApiLearningObjectProvider; diff --git a/backend/src/services/learning-paths/learning-path-service.ts b/backend/src/services/learning-paths/learning-path-service.ts index 2fceb46c..4dd21407 100644 --- a/backend/src/services/learning-paths/learning-path-service.ts +++ b/backend/src/services/learning-paths/learning-path-service.ts @@ -1,11 +1,11 @@ import { LearningPath, LearningPathResponse } from '../../interfaces/learning-content.js'; import dwengoApiLearningPathProvider from './dwengo-api-learning-path-provider.js'; import databaseLearningPathProvider from './database-learning-path-provider.js'; -import { EnvVars, getEnvVar } from '../../util/envvars.js'; +import { envVars, getEnvVar } from '../../util/envVars.js'; import { Language } from '../../entities/content/language.js'; import { PersonalizationTarget } from './learning-path-personalization-util.js'; -const userContentPrefix = getEnvVar(EnvVars.UserContentPrefix); +const userContentPrefix = getEnvVar(envVars.UserContentPrefix); const allProviders = [dwengoApiLearningPathProvider, databaseLearningPathProvider]; /** diff --git a/backend/src/services/users.ts b/backend/src/services/users.ts index 65ed5d4c..dd5a733b 100644 --- a/backend/src/services/users.ts +++ b/backend/src/services/users.ts @@ -24,8 +24,8 @@ export class UserService { return user ? mapToUserDTO(user) : null; } - async createUser(userData: UserDTO, UserClass: new () => T): Promise { - const newUser = mapToUser(userData, new UserClass()); + async createUser(userData: UserDTO, userClass: new () => T): Promise { + const newUser = mapToUser(userData, new userClass()); await this.repository.save(newUser); return newUser; } diff --git a/backend/src/util/envvars.ts b/backend/src/util/envVars.ts similarity index 98% rename from backend/src/util/envvars.ts rename to backend/src/util/envVars.ts index 115291af..15ad5928 100644 --- a/backend/src/util/envvars.ts +++ b/backend/src/util/envVars.ts @@ -7,7 +7,7 @@ const CORS_PREFIX = PREFIX + 'CORS_'; type EnvVar = { key: string; required?: boolean; defaultValue?: any }; -export const EnvVars: { [key: string]: EnvVar } = { +export const envVars: { [key: string]: EnvVar } = { Port: { key: PREFIX + 'PORT', defaultValue: 3000 }, DbHost: { key: DB_PREFIX + 'HOST', required: true }, DbPort: { key: DB_PREFIX + 'PORT', defaultValue: 5432 }, diff --git a/backend/tests/services/learning-objects/learning-object-service.test.ts b/backend/tests/services/learning-objects/learning-object-service.test.ts index c06cca39..f284b939 100644 --- a/backend/tests/services/learning-objects/learning-object-service.test.ts +++ b/backend/tests/services/learning-objects/learning-object-service.test.ts @@ -6,7 +6,7 @@ import learningObjectExample from '../../test-assets/learning-objects/pn-werking import learningObjectService from '../../../src/services/learning-objects/learning-object-service'; import { LearningObjectIdentifier, LearningPathIdentifier } from '../../../src/interfaces/learning-content'; import { Language } from '../../../src/entities/content/language'; -import { EnvVars, getEnvVar } from '../../../src/util/envvars'; +import { envVars, getEnvVar } from '../../../src/util/envVars'; import { LearningPath } from '../../../src/entities/content/learning-path.entity'; import learningPathExample from '../../test-assets/learning-paths/pn-werking-example'; @@ -79,7 +79,7 @@ describe('LearningObjectService', () => { expect(result).not.toBeNull(); const responseFromDwengoApi = await fetch( - getEnvVar(EnvVars.LearningContentRepoApiBaseUrl) + + getEnvVar(envVars.LearningContentRepoApiBaseUrl) + `/learningObject/getRaw?hruid=${DWENGO_TEST_LEARNING_OBJECT_ID.hruid}&language=${DWENGO_TEST_LEARNING_OBJECT_ID.language}&version=${DWENGO_TEST_LEARNING_OBJECT_ID.version}` ); const responseHtml = await responseFromDwengoApi.text(); diff --git a/backend/tests/test-assets/learning-objects/dummy/dummy-learning-object-example.ts b/backend/tests/test-assets/learning-objects/dummy/dummy-learning-object-example.ts index 2f2e78ad..b1bd3089 100644 --- a/backend/tests/test-assets/learning-objects/dummy/dummy-learning-object-example.ts +++ b/backend/tests/test-assets/learning-objects/dummy/dummy-learning-object-example.ts @@ -3,7 +3,7 @@ import { LearningObject } from '../../../../src/entities/content/learning-object import { Language } from '../../../../src/entities/content/language'; import { loadTestAsset } from '../../../test-utils/load-test-asset'; import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type'; -import { EnvVars, getEnvVar } from '../../../../src/util/envvars'; +import { envVars, getEnvVar } from '../../../../src/util/envVars'; /** * Create a dummy learning object to be used in tests where multiple learning objects are needed (for example for use @@ -13,7 +13,7 @@ export function dummyLearningObject(hruid: string, language: Language, title: st return { createLearningObject: () => { const learningObject = new LearningObject(); - learningObject.hruid = getEnvVar(EnvVars.UserContentPrefix) + hruid; + learningObject.hruid = getEnvVar(envVars.UserContentPrefix) + hruid; learningObject.language = language; learningObject.version = 1; learningObject.title = title; diff --git a/backend/tests/test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example.ts b/backend/tests/test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example.ts index 600a4305..34f49e64 100644 --- a/backend/tests/test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example.ts +++ b/backend/tests/test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example.ts @@ -4,14 +4,14 @@ import { DwengoContentType } from '../../../../src/services/learning-objects/pro import { loadTestAsset } from '../../../test-utils/load-test-asset'; import { EducationalGoal, LearningObject, ReturnValue } from '../../../../src/entities/content/learning-object.entity'; import { Attachment } from '../../../../src/entities/content/attachment.entity'; -import { EnvVars, getEnvVar } from '../../../../src/util/envvars'; +import { envVars, getEnvVar } from '../../../../src/util/envVars'; const ASSETS_PREFIX = 'learning-objects/pn-werkingnotebooks/'; const example: LearningObjectExample = { createLearningObject: () => { const learningObject = new LearningObject(); - learningObject.hruid = `${getEnvVar(EnvVars.UserContentPrefix)}pn_werkingnotebooks`; + learningObject.hruid = `${getEnvVar(envVars.UserContentPrefix)}pn_werkingnotebooks`; learningObject.version = 3; learningObject.language = Language.Dutch; learningObject.title = 'Werken met notebooks'; diff --git a/backend/tests/test-assets/learning-objects/test-essay/test-essay-example.ts b/backend/tests/test-assets/learning-objects/test-essay/test-essay-example.ts index d57c7a33..943a10c7 100644 --- a/backend/tests/test-assets/learning-objects/test-essay/test-essay-example.ts +++ b/backend/tests/test-assets/learning-objects/test-essay/test-essay-example.ts @@ -1,14 +1,14 @@ import { LearningObjectExample } from '../learning-object-example'; import { LearningObject } from '../../../../src/entities/content/learning-object.entity'; import { loadTestAsset } from '../../../test-utils/load-test-asset'; -import { EnvVars, getEnvVar } from '../../../../src/util/envvars'; +import { envVars, getEnvVar } from '../../../../src/util/envVars'; import { Language } from '../../../../src/entities/content/language'; import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type'; const example: LearningObjectExample = { createLearningObject: () => { const learningObject = new LearningObject(); - learningObject.hruid = `${getEnvVar(EnvVars.UserContentPrefix)}test_essay`; + learningObject.hruid = `${getEnvVar(envVars.UserContentPrefix)}test_essay`; learningObject.language = Language.English; learningObject.version = 1; learningObject.title = 'Essay question for testing'; diff --git a/backend/tests/test-assets/learning-objects/test-multiple-choice/test-multiple-choice-example.ts b/backend/tests/test-assets/learning-objects/test-multiple-choice/test-multiple-choice-example.ts index a634878a..276a41bb 100644 --- a/backend/tests/test-assets/learning-objects/test-multiple-choice/test-multiple-choice-example.ts +++ b/backend/tests/test-assets/learning-objects/test-multiple-choice/test-multiple-choice-example.ts @@ -1,14 +1,14 @@ import { LearningObjectExample } from '../learning-object-example'; import { LearningObject } from '../../../../src/entities/content/learning-object.entity'; import { loadTestAsset } from '../../../test-utils/load-test-asset'; -import { EnvVars, getEnvVar } from '../../../../src/util/envvars'; +import { envVars, getEnvVar } from '../../../../src/util/envVars'; import { Language } from '../../../../src/entities/content/language'; import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type'; const example: LearningObjectExample = { createLearningObject: () => { const learningObject = new LearningObject(); - learningObject.hruid = `${getEnvVar(EnvVars.UserContentPrefix)}test_multiple_choice`; + learningObject.hruid = `${getEnvVar(envVars.UserContentPrefix)}test_multiple_choice`; learningObject.language = Language.English; learningObject.version = 1; learningObject.title = 'Multiple choice question for testing'; diff --git a/backend/tests/test-assets/learning-paths/pn-werking-example.ts b/backend/tests/test-assets/learning-paths/pn-werking-example.ts index 810b4da5..06a29007 100644 --- a/backend/tests/test-assets/learning-paths/pn-werking-example.ts +++ b/backend/tests/test-assets/learning-paths/pn-werking-example.ts @@ -1,6 +1,6 @@ import { LearningPath } from '../../../src/entities/content/learning-path.entity'; import { Language } from '../../../src/entities/content/language'; -import { EnvVars, getEnvVar } from '../../../src/util/envvars'; +import { envVars, getEnvVar } from '../../../src/util/envVars'; import { createLearningPathNode, createLearningPathTransition } from './learning-path-utils'; import { LearningPathNode } from '../../../src/entities/content/learning-path-node.entity'; @@ -19,7 +19,7 @@ const example: LearningPathExample = { createLearningPath: () => { const path = new LearningPath(); path.language = Language.Dutch; - path.hruid = `${getEnvVar(EnvVars.UserContentPrefix)}pn_werking`; + path.hruid = `${getEnvVar(envVars.UserContentPrefix)}pn_werking`; path.title = 'Werken met notebooks'; path.description = 'Een korte inleiding tot Python notebooks. Hoe ga je gemakkelijk en efficiënt met de notebooks aan de slag?'; path.nodes = createNodes(path); diff --git a/backend/tests/test-assets/learning-paths/test-conditions-example.ts b/backend/tests/test-assets/learning-paths/test-conditions-example.ts index ee957f91..2980253e 100644 --- a/backend/tests/test-assets/learning-paths/test-conditions-example.ts +++ b/backend/tests/test-assets/learning-paths/test-conditions-example.ts @@ -4,7 +4,7 @@ import testMultipleChoiceExample from '../learning-objects/test-multiple-choice/ import { dummyLearningObject } from '../learning-objects/dummy/dummy-learning-object-example'; import { createLearningPathNode, createLearningPathTransition } from './learning-path-utils'; import { LearningObject } from '../../../src/entities/content/learning-object.entity'; -import { EnvVars, getEnvVar } from '../../../src/util/envvars'; +import { envVars, getEnvVar } from '../../../src/util/envVars'; export type ConditionTestLearningPathAndLearningObjects = { branchingObject: LearningObject; @@ -15,7 +15,7 @@ export type ConditionTestLearningPathAndLearningObjects = { export function createConditionTestLearningPathAndLearningObjects() { const learningPath = new LearningPath(); - learningPath.hruid = `${getEnvVar(EnvVars.UserContentPrefix)}test_conditions`; + learningPath.hruid = `${getEnvVar(envVars.UserContentPrefix)}test_conditions`; learningPath.language = Language.English; learningPath.title = 'Example learning path with conditional transitions'; learningPath.description = 'This learning path was made for the purpose of testing conditional transitions'; @@ -78,7 +78,3 @@ export function createConditionTestLearningPathAndLearningObjects() { learningPath: learningPath, }; } - -const _example: LearningPathExample = { - createLearningPath: () => createConditionTestLearningPathAndLearningObjects().learningPath, -}; diff --git a/eslint.config.ts b/eslint.config.ts index a3001cc5..e4534398 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -28,6 +28,27 @@ export default [ reportUnusedInlineConfigs: 'error', }, rules: { + '@typescript-eslint/naming-convention': [ + 'warn', + { // Enforce that all variables, functions and properties are camelCase + selector: 'variableLike', + format: ['camelCase'], + leadingUnderscore: 'allow' + }, + { + selector: 'variable', + modifiers: ['const'], + format: ['camelCase', 'UPPER_CASE'], + trailingUnderscore: 'allow' + }, + { // Enforce that private members are prefixed with an underscore + selector: 'memberLike', + modifiers: ['private'], + format: ['camelCase'], + leadingUnderscore: 'allow', + } + ], + 'no-unused-expressions': 'off', '@typescript-eslint/no-unused-expressions': 'warn', 'no-unused-vars': 'off', @@ -57,7 +78,6 @@ export default [ 'arrow-body-style': ['warn', 'as-needed'], 'block-scoped-var': 'warn', - camelcase: 'warn', 'capitalized-comments': 'warn', 'consistent-return': 'warn', 'consistent-this': 'error', From 5ec62554e3c0c96e4fb331a9aa9bcc7f3ce9e8be Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sat, 22 Mar 2025 18:01:17 +0100 Subject: [PATCH 12/43] refactor(backend): explicit-function-return-type --- .../tests/test-assets/learning-paths/test-conditions-example.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/tests/test-assets/learning-paths/test-conditions-example.ts b/backend/tests/test-assets/learning-paths/test-conditions-example.ts index 2980253e..816cd735 100644 --- a/backend/tests/test-assets/learning-paths/test-conditions-example.ts +++ b/backend/tests/test-assets/learning-paths/test-conditions-example.ts @@ -13,7 +13,7 @@ export type ConditionTestLearningPathAndLearningObjects = { learningPath: LearningPath; }; -export function createConditionTestLearningPathAndLearningObjects() { +export function createConditionTestLearningPathAndLearningObjects(): ConditionTestLearningPathAndLearningObjects { const learningPath = new LearningPath(); learningPath.hruid = `${getEnvVar(envVars.UserContentPrefix)}test_conditions`; learningPath.language = Language.English; From 65c1a5e6b694474840f7f1bea9bfa8e1842c10a3 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sat, 22 Mar 2025 18:38:38 +0100 Subject: [PATCH 13/43] refactor(backend): Functions --- backend/src/app.ts | 2 +- backend/src/controllers/students.ts | 4 ++-- backend/src/controllers/submissions.ts | 4 ++-- backend/src/controllers/teachers.ts | 4 ++-- backend/src/controllers/themes.ts | 4 ++-- backend/src/controllers/users.ts | 4 ++-- .../data/assignments/assignment-repository.ts | 6 ++--- .../src/data/assignments/group-repository.ts | 8 +++---- .../data/assignments/submission-repository.ts | 15 ++++++++----- .../classes/class-join-request-repository.ts | 6 ++--- backend/src/data/classes/class-repository.ts | 8 +++---- .../classes/teacher-invitation-repository.ts | 8 +++---- .../src/data/content/attachment-repository.ts | 8 +++++-- .../content/learning-object-repository.ts | 6 ++--- .../data/content/learning-path-repository.ts | 2 +- backend/src/data/dwengo-entity-repository.ts | 4 ++-- .../src/data/questions/answer-repository.ts | 6 ++--- .../src/data/questions/question-repository.ts | 6 ++--- backend/src/data/repositories.ts | 2 +- backend/src/data/users/student-repository.ts | 4 ++-- backend/src/data/users/teacher-repository.ts | 4 ++-- backend/src/data/users/user-repository.ts | 4 ++-- .../content/learning-object-identifier.ts | 4 +++- backend/src/logging/initalize.ts | 2 +- backend/src/logging/mikroOrmLogger.ts | 4 ++-- backend/src/logging/responseTimeLogger.ts | 2 +- backend/src/middleware/auth/auth.ts | 17 +++++++------- backend/src/mikro-orm.config.ts | 2 +- backend/src/orm.ts | 2 +- backend/src/services/assignments.ts | 2 +- backend/src/services/class.ts | 8 +++++-- backend/src/services/groups.ts | 4 +++- .../learning-objects/attachment-service.ts | 2 +- .../database-learning-object-provider.ts | 6 ++--- .../learning-object-service.ts | 8 +++---- .../processing/extern/extern-processor.ts | 2 +- .../processing/gift/gift-processor.ts | 2 +- .../processing/image/block-image-processor.ts | 2 +- .../image/inline-image-processor.ts | 2 +- .../processing/markdown/markdown-processor.ts | 4 ++-- .../processing/pdf/pdf-processor.ts | 2 +- .../learning-objects/processing/processor.ts | 4 +++- .../processing/text/text-processor.ts | 2 +- .../database-learning-path-provider.ts | 10 ++++----- .../learning-paths/learning-path-service.ts | 4 +++- backend/src/services/questions.ts | 8 +++---- backend/src/services/submissions.ts | 10 +++++++-- backend/src/services/teachers.ts | 2 +- backend/src/util/async.ts | 6 ++++- backend/src/util/links.ts | 2 +- .../database-learning-object-provider.test.ts | 4 ++-- .../database-learning-path-provider.test.ts | 2 +- backend/tests/setup-tests.ts | 2 +- .../dummy/dummy-learning-object-example.ts | 2 +- .../learning-paths/learning-path-utils.ts | 9 ++++++-- backend/tests/test-utils/expectations.ts | 4 ++-- eslint.config.ts | 22 ++++++++++++++++++- 57 files changed, 172 insertions(+), 117 deletions(-) diff --git a/backend/src/app.ts b/backend/src/app.ts index c8658cd5..f4c941e5 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -26,7 +26,7 @@ app.use('/api', apiRouter); // Swagger app.use('/api-docs', swaggerUi.serve, swaggerMiddleware); -async function startServer() { +async function startServer(): Promise { await initORM(); app.listen(port, () => { diff --git a/backend/src/controllers/students.ts b/backend/src/controllers/students.ts index 96cdd4dd..d71571e5 100644 --- a/backend/src/controllers/students.ts +++ b/backend/src/controllers/students.ts @@ -47,7 +47,7 @@ export async function getStudentHandler(req: Request, res: Response): Promise { const userData = req.body as StudentDTO; if (!userData.username || !userData.firstName || !userData.lastName) { @@ -61,7 +61,7 @@ export async function createStudentHandler(req: Request, res: Response) { res.status(201).json(newUser); } -export async function deleteStudentHandler(req: Request, res: Response) { +export async function deleteStudentHandler(req: Request, res: Response): Promise { const username = req.params.username; if (!username) { diff --git a/backend/src/controllers/submissions.ts b/backend/src/controllers/submissions.ts index 1e66dbe9..cb8f2de2 100644 --- a/backend/src/controllers/submissions.ts +++ b/backend/src/controllers/submissions.ts @@ -30,7 +30,7 @@ export async function getSubmissionHandler(req: Request, res: res.json(submission); } -export async function createSubmissionHandler(req: Request, res: Response) { +export async function createSubmissionHandler(req: Request, res: Response): Promise { const submissionDTO = req.body as 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 { const hruid = req.params.hruid; const submissionNumber = +req.params.id; diff --git a/backend/src/controllers/teachers.ts b/backend/src/controllers/teachers.ts index 7bcbb5f4..e5f1fc87 100644 --- a/backend/src/controllers/teachers.ts +++ b/backend/src/controllers/teachers.ts @@ -50,7 +50,7 @@ export async function getTeacherHandler(req: Request, res: Response): Promise { const userData = req.body as TeacherDTO; if (!userData.username || !userData.firstName || !userData.lastName) { @@ -64,7 +64,7 @@ export async function createTeacherHandler(req: Request, res: Response) { res.status(201).json(newUser); } -export async function deleteTeacherHandler(req: Request, res: Response) { +export async function deleteTeacherHandler(req: Request, res: Response): Promise { const username = req.params.username; if (!username) { diff --git a/backend/src/controllers/themes.ts b/backend/src/controllers/themes.ts index 61a1a834..1ef113cd 100644 --- a/backend/src/controllers/themes.ts +++ b/backend/src/controllers/themes.ts @@ -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 translations = loadTranslations(language); const themeList = themes.map((theme) => ({ @@ -21,7 +21,7 @@ export function getThemes(req: Request, res: Response) { res.json(themeList); } -export function getThemeByTitle(req: Request, res: Response) { +export function getThemeByTitle(req: Request, res: Response): void { const themeKey = req.params.theme; const theme = themes.find((t) => t.title === themeKey); diff --git a/backend/src/controllers/users.ts b/backend/src/controllers/users.ts index ac3f824b..7706e2ee 100644 --- a/backend/src/controllers/users.ts +++ b/backend/src/controllers/users.ts @@ -47,7 +47,7 @@ export async function getUserHandler(req: Request, res: Response } } -export async function createUserHandler(req: Request, res: Response, service: UserService, userClass: new () => T) { +export async function createUserHandler(req: Request, res: Response, service: UserService, userClass: new () => T): Promise { try { getLogger().debug({ req: req }); const userData = req.body as UserDTO; @@ -67,7 +67,7 @@ export async function createUserHandler(req: Request, res: Respo } } -export async function deleteUserHandler(req: Request, res: Response, service: UserService) { +export async function deleteUserHandler(req: Request, res: Response, service: UserService): Promise { try { const username = req.params.username; diff --git a/backend/src/data/assignments/assignment-repository.ts b/backend/src/data/assignments/assignment-repository.ts index c3c457d3..3de5031d 100644 --- a/backend/src/data/assignments/assignment-repository.ts +++ b/backend/src/data/assignments/assignment-repository.ts @@ -3,13 +3,13 @@ import { Assignment } from '../../entities/assignments/assignment.entity.js'; import { Class } from '../../entities/classes/class.entity.js'; export class AssignmentRepository extends DwengoEntityRepository { - public findByClassAndId(within: Class, id: number): Promise { + public async findByClassAndId(within: Class, id: number): Promise { return this.findOne({ within: within, id: id }); } - public findAllAssignmentsInClass(within: Class): Promise { + public async findAllAssignmentsInClass(within: Class): Promise { return this.findAll({ where: { within: within } }); } - public deleteByClassAndId(within: Class, id: number): Promise { + public async deleteByClassAndId(within: Class, id: number): Promise { return this.deleteWhere({ within: within, id: id }); } } diff --git a/backend/src/data/assignments/group-repository.ts b/backend/src/data/assignments/group-repository.ts index eb1b09e2..f06080f7 100644 --- a/backend/src/data/assignments/group-repository.ts +++ b/backend/src/data/assignments/group-repository.ts @@ -4,7 +4,7 @@ import { Assignment } from '../../entities/assignments/assignment.entity.js'; import { Student } from '../../entities/users/student.entity.js'; export class GroupRepository extends DwengoEntityRepository { - public findByAssignmentAndGroupNumber(assignment: Assignment, groupNumber: number): Promise { + public async findByAssignmentAndGroupNumber(assignment: Assignment, groupNumber: number): Promise { return this.findOne( { assignment: assignment, @@ -13,16 +13,16 @@ export class GroupRepository extends DwengoEntityRepository { { populate: ['members'] } ); } - public findAllGroupsForAssignment(assignment: Assignment): Promise { + public async findAllGroupsForAssignment(assignment: Assignment): Promise { return this.findAll({ where: { assignment: assignment }, populate: ['members'], }); } - public findAllGroupsWithStudent(student: Student): Promise { + public async findAllGroupsWithStudent(student: Student): Promise { return this.find({ members: student }, { populate: ['members'] }); } - public deleteByAssignmentAndGroupNumber(assignment: Assignment, groupNumber: number) { + public async deleteByAssignmentAndGroupNumber(assignment: Assignment, groupNumber: number): Promise { return this.deleteWhere({ assignment: assignment, groupNumber: groupNumber, diff --git a/backend/src/data/assignments/submission-repository.ts b/backend/src/data/assignments/submission-repository.ts index 251823fa..f5090adc 100644 --- a/backend/src/data/assignments/submission-repository.ts +++ b/backend/src/data/assignments/submission-repository.ts @@ -5,7 +5,10 @@ import { LearningObjectIdentifier } from '../../entities/content/learning-object import { Student } from '../../entities/users/student.entity.js'; export class SubmissionRepository extends DwengoEntityRepository { - public findSubmissionByLearningObjectAndSubmissionNumber(loId: LearningObjectIdentifier, submissionNumber: number): Promise { + public async findSubmissionByLearningObjectAndSubmissionNumber( + loId: LearningObjectIdentifier, + submissionNumber: number + ): Promise { return this.findOne({ learningObjectHruid: loId.hruid, learningObjectLanguage: loId.language, @@ -14,7 +17,7 @@ export class SubmissionRepository extends DwengoEntityRepository { }); } - public findMostRecentSubmissionForStudent(loId: LearningObjectIdentifier, submitter: Student): Promise { + public async findMostRecentSubmissionForStudent(loId: LearningObjectIdentifier, submitter: Student): Promise { return this.findOne( { learningObjectHruid: loId.hruid, @@ -26,7 +29,7 @@ export class SubmissionRepository extends DwengoEntityRepository { ); } - public findMostRecentSubmissionForGroup(loId: LearningObjectIdentifier, group: Group): Promise { + public async findMostRecentSubmissionForGroup(loId: LearningObjectIdentifier, group: Group): Promise { return this.findOne( { learningObjectHruid: loId.hruid, @@ -38,15 +41,15 @@ export class SubmissionRepository extends DwengoEntityRepository { ); } - public findAllSubmissionsForGroup(group: Group): Promise { + public async findAllSubmissionsForGroup(group: Group): Promise { return this.find({ onBehalfOf: group }); } - public findAllSubmissionsForStudent(student: Student): Promise { + public async findAllSubmissionsForStudent(student: Student): Promise { return this.find({ submitter: student }); } - public deleteSubmissionByLearningObjectAndSubmissionNumber(loId: LearningObjectIdentifier, submissionNumber: number): Promise { + public async deleteSubmissionByLearningObjectAndSubmissionNumber(loId: LearningObjectIdentifier, submissionNumber: number): Promise { return this.deleteWhere({ learningObjectHruid: loId.hruid, learningObjectLanguage: loId.language, diff --git a/backend/src/data/classes/class-join-request-repository.ts b/backend/src/data/classes/class-join-request-repository.ts index c1443c1c..1cd0288c 100644 --- a/backend/src/data/classes/class-join-request-repository.ts +++ b/backend/src/data/classes/class-join-request-repository.ts @@ -4,13 +4,13 @@ import { ClassJoinRequest } from '../../entities/classes/class-join-request.enti import { Student } from '../../entities/users/student.entity.js'; export class ClassJoinRequestRepository extends DwengoEntityRepository { - public findAllRequestsBy(requester: Student): Promise { + public async findAllRequestsBy(requester: Student): Promise { return this.findAll({ where: { requester: requester } }); } - public findAllOpenRequestsTo(clazz: Class): Promise { + public async findAllOpenRequestsTo(clazz: Class): Promise { return this.findAll({ where: { class: clazz } }); } - public deleteBy(requester: Student, clazz: Class): Promise { + public async deleteBy(requester: Student, clazz: Class): Promise { return this.deleteWhere({ requester: requester, class: clazz }); } } diff --git a/backend/src/data/classes/class-repository.ts b/backend/src/data/classes/class-repository.ts index 0ceed98e..f4e0723f 100644 --- a/backend/src/data/classes/class-repository.ts +++ b/backend/src/data/classes/class-repository.ts @@ -4,20 +4,20 @@ import { Student } from '../../entities/users/student.entity.js'; import { Teacher } from '../../entities/users/teacher.entity'; export class ClassRepository extends DwengoEntityRepository { - public findById(id: string): Promise { + public async findById(id: string): Promise { return this.findOne({ classId: id }, { populate: ['students', 'teachers'] }); } - public deleteById(id: string): Promise { + public async deleteById(id: string): Promise { return this.deleteWhere({ classId: id }); } - public findByStudent(student: Student): Promise { + public async findByStudent(student: Student): Promise { return this.find( { students: student }, { populate: ['students', 'teachers'] } // Voegt student en teacher objecten toe ); } - public findByTeacher(teacher: Teacher): Promise { + public async findByTeacher(teacher: Teacher): Promise { return this.find({ teachers: teacher }, { populate: ['students', 'teachers'] }); } } diff --git a/backend/src/data/classes/teacher-invitation-repository.ts b/backend/src/data/classes/teacher-invitation-repository.ts index 6b94deec..ce059ca8 100644 --- a/backend/src/data/classes/teacher-invitation-repository.ts +++ b/backend/src/data/classes/teacher-invitation-repository.ts @@ -4,16 +4,16 @@ import { TeacherInvitation } from '../../entities/classes/teacher-invitation.ent import { Teacher } from '../../entities/users/teacher.entity.js'; export class TeacherInvitationRepository extends DwengoEntityRepository { - public findAllInvitationsForClass(clazz: Class): Promise { + public async findAllInvitationsForClass(clazz: Class): Promise { return this.findAll({ where: { class: clazz } }); } - public findAllInvitationsBy(sender: Teacher): Promise { + public async findAllInvitationsBy(sender: Teacher): Promise { return this.findAll({ where: { sender: sender } }); } - public findAllInvitationsFor(receiver: Teacher): Promise { + public async findAllInvitationsFor(receiver: Teacher): Promise { return this.findAll({ where: { receiver: receiver } }); } - public deleteBy(clazz: Class, sender: Teacher, receiver: Teacher): Promise { + public async deleteBy(clazz: Class, sender: Teacher, receiver: Teacher): Promise { return this.deleteWhere({ sender: sender, receiver: receiver, diff --git a/backend/src/data/content/attachment-repository.ts b/backend/src/data/content/attachment-repository.ts index 95c5ab1c..73baa943 100644 --- a/backend/src/data/content/attachment-repository.ts +++ b/backend/src/data/content/attachment-repository.ts @@ -4,7 +4,7 @@ import { Language } from '../../entities/content/language'; import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier'; export class AttachmentRepository extends DwengoEntityRepository { - public findByLearningObjectIdAndName(learningObjectId: LearningObjectIdentifier, name: string): Promise { + public async findByLearningObjectIdAndName(learningObjectId: LearningObjectIdentifier, name: string): Promise { return this.findOne({ learningObject: { hruid: learningObjectId.hruid, @@ -15,7 +15,11 @@ export class AttachmentRepository extends DwengoEntityRepository { }); } - public findByMostRecentVersionOfLearningObjectAndName(hruid: string, language: Language, attachmentName: string): Promise { + public async findByMostRecentVersionOfLearningObjectAndName( + hruid: string, + language: Language, + attachmentName: string + ): Promise { return this.findOne( { learningObject: { diff --git a/backend/src/data/content/learning-object-repository.ts b/backend/src/data/content/learning-object-repository.ts index 49b4c536..4684c6cc 100644 --- a/backend/src/data/content/learning-object-repository.ts +++ b/backend/src/data/content/learning-object-repository.ts @@ -5,7 +5,7 @@ import { Language } from '../../entities/content/language.js'; import { Teacher } from '../../entities/users/teacher.entity.js'; export class LearningObjectRepository extends DwengoEntityRepository { - public findByIdentifier(identifier: LearningObjectIdentifier): Promise { + public async findByIdentifier(identifier: LearningObjectIdentifier): Promise { return this.findOne( { hruid: identifier.hruid, @@ -18,7 +18,7 @@ export class LearningObjectRepository extends DwengoEntityRepository { return this.findOne( { hruid: hruid, @@ -33,7 +33,7 @@ export class LearningObjectRepository extends DwengoEntityRepository { + public async findAllByTeacher(teacher: Teacher): Promise { return this.find( { admins: teacher }, { populate: ['admins'] } // Make sure to load admin relations diff --git a/backend/src/data/content/learning-path-repository.ts b/backend/src/data/content/learning-path-repository.ts index a2f9b47e..e34508ec 100644 --- a/backend/src/data/content/learning-path-repository.ts +++ b/backend/src/data/content/learning-path-repository.ts @@ -3,7 +3,7 @@ import { LearningPath } from '../../entities/content/learning-path.entity.js'; import { Language } from '../../entities/content/language.js'; export class LearningPathRepository extends DwengoEntityRepository { - public findByHruidAndLanguage(hruid: string, language: Language): Promise { + public async findByHruidAndLanguage(hruid: string, language: Language): Promise { return this.findOne({ hruid: hruid, language: language }, { populate: ['nodes', 'nodes.transitions'] }); } diff --git a/backend/src/data/dwengo-entity-repository.ts b/backend/src/data/dwengo-entity-repository.ts index 6538d6f5..f64b8408 100644 --- a/backend/src/data/dwengo-entity-repository.ts +++ b/backend/src/data/dwengo-entity-repository.ts @@ -1,12 +1,12 @@ import { EntityRepository, FilterQuery } from '@mikro-orm/core'; export abstract class DwengoEntityRepository extends EntityRepository { - public async save(entity: T) { + public async save(entity: T): Promise { const em = this.getEntityManager(); em.persist(entity); await em.flush(); } - public async deleteWhere(query: FilterQuery) { + public async deleteWhere(query: FilterQuery): Promise { const toDelete = await this.findOne(query); const em = this.getEntityManager(); if (toDelete) { diff --git a/backend/src/data/questions/answer-repository.ts b/backend/src/data/questions/answer-repository.ts index a28342bd..a50bfd28 100644 --- a/backend/src/data/questions/answer-repository.ts +++ b/backend/src/data/questions/answer-repository.ts @@ -4,7 +4,7 @@ import { Question } from '../../entities/questions/question.entity.js'; import { Teacher } from '../../entities/users/teacher.entity.js'; export class AnswerRepository extends DwengoEntityRepository { - public createAnswer(answer: { toQuestion: Question; author: Teacher; content: string }): Promise { + public async createAnswer(answer: { toQuestion: Question; author: Teacher; content: string }): Promise { const answerEntity = this.create({ toQuestion: answer.toQuestion, author: answer.author, @@ -13,13 +13,13 @@ export class AnswerRepository extends DwengoEntityRepository { }); return this.insert(answerEntity); } - public findAllAnswersToQuestion(question: Question): Promise { + public async findAllAnswersToQuestion(question: Question): Promise { return this.findAll({ where: { toQuestion: question }, orderBy: { sequenceNumber: 'ASC' }, }); } - public removeAnswerByQuestionAndSequenceNumber(question: Question, sequenceNumber: number): Promise { + public async removeAnswerByQuestionAndSequenceNumber(question: Question, sequenceNumber: number): Promise { return this.deleteWhere({ toQuestion: question, sequenceNumber: sequenceNumber, diff --git a/backend/src/data/questions/question-repository.ts b/backend/src/data/questions/question-repository.ts index 9207e1dd..596b562c 100644 --- a/backend/src/data/questions/question-repository.ts +++ b/backend/src/data/questions/question-repository.ts @@ -5,7 +5,7 @@ import { Student } from '../../entities/users/student.entity.js'; import { LearningObject } from '../../entities/content/learning-object.entity.js'; export class QuestionRepository extends DwengoEntityRepository { - public createQuestion(question: { loId: LearningObjectIdentifier; author: Student; content: string }): Promise { + public async createQuestion(question: { loId: LearningObjectIdentifier; author: Student; content: string }): Promise { const questionEntity = this.create({ learningObjectHruid: question.loId.hruid, learningObjectLanguage: question.loId.language, @@ -21,7 +21,7 @@ export class QuestionRepository extends DwengoEntityRepository { questionEntity.content = question.content; return this.insert(questionEntity); } - public findAllQuestionsAboutLearningObject(loId: LearningObjectIdentifier): Promise { + public async findAllQuestionsAboutLearningObject(loId: LearningObjectIdentifier): Promise { return this.findAll({ where: { learningObjectHruid: loId.hruid, @@ -33,7 +33,7 @@ export class QuestionRepository extends DwengoEntityRepository { }, }); } - public removeQuestionByLearningObjectAndSequenceNumber(loId: LearningObjectIdentifier, sequenceNumber: number): Promise { + public async removeQuestionByLearningObjectAndSequenceNumber(loId: LearningObjectIdentifier, sequenceNumber: number): Promise { return this.deleteWhere({ learningObjectHruid: loId.hruid, learningObjectLanguage: loId.language, diff --git a/backend/src/data/repositories.ts b/backend/src/data/repositories.ts index cdeb50c1..852d9d6a 100644 --- a/backend/src/data/repositories.ts +++ b/backend/src/data/repositories.ts @@ -34,7 +34,7 @@ let entityManager: EntityManager | undefined; /** * Execute all the database operations within the function f in a single transaction. */ -export function transactional(f: () => Promise) { +export function transactional(f: () => Promise): void { entityManager?.transactional(f); } diff --git a/backend/src/data/users/student-repository.ts b/backend/src/data/users/student-repository.ts index 34b24b8b..2efca048 100644 --- a/backend/src/data/users/student-repository.ts +++ b/backend/src/data/users/student-repository.ts @@ -2,10 +2,10 @@ import { Student } from '../../entities/users/student.entity.js'; import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; export class StudentRepository extends DwengoEntityRepository { - public findByUsername(username: string): Promise { + public async findByUsername(username: string): Promise { return this.findOne({ username: username }); } - public deleteByUsername(username: string): Promise { + public async deleteByUsername(username: string): Promise { return this.deleteWhere({ username: username }); } } diff --git a/backend/src/data/users/teacher-repository.ts b/backend/src/data/users/teacher-repository.ts index 825b4d18..aa915627 100644 --- a/backend/src/data/users/teacher-repository.ts +++ b/backend/src/data/users/teacher-repository.ts @@ -2,10 +2,10 @@ import { Teacher } from '../../entities/users/teacher.entity.js'; import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; export class TeacherRepository extends DwengoEntityRepository { - public findByUsername(username: string): Promise { + public async findByUsername(username: string): Promise { return this.findOne({ username: username }); } - public deleteByUsername(username: string): Promise { + public async deleteByUsername(username: string): Promise { return this.deleteWhere({ username: username }); } } diff --git a/backend/src/data/users/user-repository.ts b/backend/src/data/users/user-repository.ts index 21497b79..44eb0bc7 100644 --- a/backend/src/data/users/user-repository.ts +++ b/backend/src/data/users/user-repository.ts @@ -2,10 +2,10 @@ import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; import { User } from '../../entities/users/user.entity.js'; export class UserRepository extends DwengoEntityRepository { - public findByUsername(username: string): Promise { + public async findByUsername(username: string): Promise { return this.findOne({ username } as Partial); } - public deleteByUsername(username: string): Promise { + public async deleteByUsername(username: string): Promise { return this.deleteWhere({ username } as Partial); } } diff --git a/backend/src/entities/content/learning-object-identifier.ts b/backend/src/entities/content/learning-object-identifier.ts index 3c020bd7..9234afa7 100644 --- a/backend/src/entities/content/learning-object-identifier.ts +++ b/backend/src/entities/content/learning-object-identifier.ts @@ -5,5 +5,7 @@ export class LearningObjectIdentifier { public hruid: string, public language: Language, public version: number - ) {} + ) { + // Do nothing + } } diff --git a/backend/src/logging/initalize.ts b/backend/src/logging/initalize.ts index 7375aa37..2d3ec44d 100644 --- a/backend/src/logging/initalize.ts +++ b/backend/src/logging/initalize.ts @@ -28,7 +28,7 @@ function initializeLogger(): Logger { level: LOG_LEVEL, json: true, format: format.combine(format.timestamp(), format.json()), - onConnectionError: (err) => { + onConnectionError: (err): void => { // eslint-disable-next-line no-console console.error(`Connection error: ${err}`); }, diff --git a/backend/src/logging/mikroOrmLogger.ts b/backend/src/logging/mikroOrmLogger.ts index 25bbac13..f6907691 100644 --- a/backend/src/logging/mikroOrmLogger.ts +++ b/backend/src/logging/mikroOrmLogger.ts @@ -5,7 +5,7 @@ import { LokiLabels } from 'loki-logger-ts'; export class MikroOrmLogger extends DefaultLogger { private logger: Logger = getLogger(); - log(namespace: LoggerNamespace, message: string, context?: LogContext) { + log(namespace: LoggerNamespace, message: string, context?: LogContext): void { if (!this.isEnabled(namespace, context)) { 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 = { service: 'ORM', }; diff --git a/backend/src/logging/responseTimeLogger.ts b/backend/src/logging/responseTimeLogger.ts index c1bb1e33..7fcc6c93 100644 --- a/backend/src/logging/responseTimeLogger.ts +++ b/backend/src/logging/responseTimeLogger.ts @@ -1,7 +1,7 @@ import { getLogger, Logger } from './initalize.js'; 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 method = req.method; diff --git a/backend/src/middleware/auth/auth.ts b/backend/src/middleware/auth/auth.ts index 5389dc77..345bfa59 100644 --- a/backend/src/middleware/auth/auth.ts +++ b/backend/src/middleware/auth/auth.ts @@ -1,9 +1,9 @@ import { envVars, getEnvVar } from '../../util/envVars.js'; import { expressjwt } from 'express-jwt'; +import * as jwt from 'jsonwebtoken'; import { JwtPayload } from 'jsonwebtoken'; import jwksClient from 'jwks-rsa'; import * as express from 'express'; -import * as jwt from 'jsonwebtoken'; import { AuthenticatedRequest } from './authenticated-request.js'; import { AuthenticationInfo } from './authentication-info.js'; import { ForbiddenException, UnauthorizedException } from '../../exceptions.js'; @@ -74,7 +74,7 @@ const verifyJwtToken = expressjwt({ */ function getAuthenticationInfo(req: AuthenticatedRequest): AuthenticationInfo | undefined { if (!req.jwtPayload) { - return; + return undefined; } const issuer = req.jwtPayload.iss; let accountType: 'student' | 'teacher'; @@ -84,8 +84,9 @@ function getAuthenticationInfo(req: AuthenticatedRequest): AuthenticationInfo | } else if (issuer === idpConfigs.teacher.issuer) { accountType = 'teacher'; } else { - return; + return undefined; } + return { accountType: accountType, 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 * 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); next(); -}; +} 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 * to true. */ -export const authorize = - (accessCondition: (auth: AuthenticationInfo) => boolean) => - (req: AuthenticatedRequest, _res: express.Response, next: express.NextFunction): void => { +export function authorize(accessCondition: (auth: AuthenticationInfo) => boolean) { + return (req: AuthenticatedRequest, _res: express.Response, next: express.NextFunction): void => { if (!req.auth) { throw new UnauthorizedException(); } else if (!accessCondition(req.auth)) { @@ -124,6 +124,7 @@ export const authorize = next(); } }; +} /** * Middleware which rejects all unauthenticated users, but accepts all authenticated users. diff --git a/backend/src/mikro-orm.config.ts b/backend/src/mikro-orm.config.ts index d53e0398..ffa12c82 100644 --- a/backend/src/mikro-orm.config.ts +++ b/backend/src/mikro-orm.config.ts @@ -54,7 +54,7 @@ function config(testingMode: boolean = false): Options { // Workaround: vitest: `TypeError: Unknown file extension ".ts"` (ERR_UNKNOWN_FILE_EXTENSION) // (see https://mikro-orm.io/docs/guide/project-setup#testing-the-endpoint) - dynamicImportProvider: (id) => import(id), + dynamicImportProvider: async (id) => import(id), }; } diff --git a/backend/src/orm.ts b/backend/src/orm.ts index 8ab5258a..419d5990 100644 --- a/backend/src/orm.ts +++ b/backend/src/orm.ts @@ -4,7 +4,7 @@ import { envVars, getEnvVar } from './util/envVars.js'; import { getLogger, Logger } from './logging/initalize.js'; let orm: MikroORM | undefined; -export async function initORM(testingMode: boolean = false) { +export async function initORM(testingMode: boolean = false): Promise { const logger: Logger = getLogger(); logger.info('Initializing ORM'); diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts index c637b822..7ac52844 100644 --- a/backend/src/services/assignments.ts +++ b/backend/src/services/assignments.ts @@ -79,7 +79,7 @@ export async function getAssignmentsSubmissions(classid: string, assignmentNumbe const groups = await groupRepository.findAllGroupsForAssignment(assignment); 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); } diff --git a/backend/src/services/class.ts b/backend/src/services/class.ts index 9f6e1efe..4f3cbc9d 100644 --- a/backend/src/services/class.ts +++ b/backend/src/services/class.ts @@ -24,11 +24,15 @@ export async function getAllClasses(full: boolean): Promise { const teacherRepository = getTeacherRepository(); 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 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); diff --git a/backend/src/services/groups.ts b/backend/src/services/groups.ts index ecc92293..6abcd3fc 100644 --- a/backend/src/services/groups.ts +++ b/backend/src/services/groups.ts @@ -43,7 +43,9 @@ export async function createGroup(groupData: GroupDTO, classid: string, assignme const studentRepository = getStudentRepository(); 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); diff --git a/backend/src/services/learning-objects/attachment-service.ts b/backend/src/services/learning-objects/attachment-service.ts index aacc7187..4ff4ec47 100644 --- a/backend/src/services/learning-objects/attachment-service.ts +++ b/backend/src/services/learning-objects/attachment-service.ts @@ -3,7 +3,7 @@ import { Attachment } from '../../entities/content/attachment.entity.js'; import { LearningObjectIdentifier } from '../../interfaces/learning-content.js'; const attachmentService = { - getAttachment(learningObjectId: LearningObjectIdentifier, attachmentName: string): Promise { + async getAttachment(learningObjectId: LearningObjectIdentifier, attachmentName: string): Promise { const attachmentRepo = getAttachmentRepository(); if (learningObjectId.version) { diff --git a/backend/src/services/learning-objects/database-learning-object-provider.ts b/backend/src/services/learning-objects/database-learning-object-provider.ts index bab0b9b1..03dbfdff 100644 --- a/backend/src/services/learning-objects/database-learning-object-provider.ts +++ b/backend/src/services/learning-objects/database-learning-object-provider.ts @@ -41,7 +41,7 @@ function convertLearningObject(learningObject: LearningObject | null): FilteredL }; } -function findLearningObjectEntityById(id: LearningObjectIdentifier): Promise { +async function findLearningObjectEntityById(id: LearningObjectIdentifier): Promise { const learningObjectRepo = getLearningObjectRepository(); return learningObjectRepo.findLatestByHruidAndLanguage(id.hruid, id.language as Language); @@ -69,7 +69,7 @@ const databaseLearningObjectProvider: LearningObjectProvider = { if (!learningObject) { 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.'); } const learningObjects = await Promise.all( - learningPath.nodes.map((it) => { + learningPath.nodes.map(async (it) => { const learningObject = learningObjectService.getLearningObjectById({ hruid: it.learningObjectHruid, language: it.language, diff --git a/backend/src/services/learning-objects/learning-object-service.ts b/backend/src/services/learning-objects/learning-object-service.ts index 95e5daff..59ffb643 100644 --- a/backend/src/services/learning-objects/learning-object-service.ts +++ b/backend/src/services/learning-objects/learning-object-service.ts @@ -18,28 +18,28 @@ const learningObjectService = { /** * Fetches a single learning object by its HRUID */ - getLearningObjectById(id: LearningObjectIdentifier): Promise { + async getLearningObjectById(id: LearningObjectIdentifier): Promise { return getProvider(id).getLearningObjectById(id); }, /** * Fetch full learning object data (metadata) */ - getLearningObjectsFromPath(id: LearningPathIdentifier): Promise { + async getLearningObjectsFromPath(id: LearningPathIdentifier): Promise { return getProvider(id).getLearningObjectsFromPath(id); }, /** * Fetch only learning object HRUIDs */ - getLearningObjectIdsFromPath(id: LearningPathIdentifier): Promise { + async getLearningObjectIdsFromPath(id: LearningPathIdentifier): Promise { return getProvider(id).getLearningObjectIdsFromPath(id); }, /** * Obtain a HTML-rendering of the learning object with the given identifier (as a string). */ - getLearningObjectHTML(id: LearningObjectIdentifier): Promise { + async getLearningObjectHTML(id: LearningObjectIdentifier): Promise { return getProvider(id).getLearningObjectHTML(id); }, }; diff --git a/backend/src/services/learning-objects/processing/extern/extern-processor.ts b/backend/src/services/learning-objects/processing/extern/extern-processor.ts index 453e998b..162f80d2 100644 --- a/backend/src/services/learning-objects/processing/extern/extern-processor.ts +++ b/backend/src/services/learning-objects/processing/extern/extern-processor.ts @@ -15,7 +15,7 @@ class ExternProcessor extends StringProcessor { super(DwengoContentType.EXTERN); } - override renderFn(externURL: string) { + override renderFn(externURL: string): string { if (!isValidHttpUrl(externURL)) { throw new ProcessingError('The url is not valid: ' + externURL); } diff --git a/backend/src/services/learning-objects/processing/gift/gift-processor.ts b/backend/src/services/learning-objects/processing/gift/gift-processor.ts index 5396236a..8d548f56 100644 --- a/backend/src/services/learning-objects/processing/gift/gift-processor.ts +++ b/backend/src/services/learning-objects/processing/gift/gift-processor.ts @@ -32,7 +32,7 @@ class GiftProcessor extends StringProcessor { super(DwengoContentType.GIFT); } - override renderFn(giftString: string) { + override renderFn(giftString: string): string { const quizQuestions: GIFTQuestion[] = parse(giftString); let html = "
\n"; diff --git a/backend/src/services/learning-objects/processing/image/block-image-processor.ts b/backend/src/services/learning-objects/processing/image/block-image-processor.ts index f4f8a773..6ad427df 100644 --- a/backend/src/services/learning-objects/processing/image/block-image-processor.ts +++ b/backend/src/services/learning-objects/processing/image/block-image-processor.ts @@ -10,7 +10,7 @@ class BlockImageProcessor extends InlineImageProcessor { super(); } - override renderFn(imageUrl: string) { + override renderFn(imageUrl: string): string { const inlineHtml = super.render(imageUrl); return DOMPurify.sanitize(`
${inlineHtml}
`); } diff --git a/backend/src/services/learning-objects/processing/image/inline-image-processor.ts b/backend/src/services/learning-objects/processing/image/inline-image-processor.ts index 478ce326..22e6313e 100644 --- a/backend/src/services/learning-objects/processing/image/inline-image-processor.ts +++ b/backend/src/services/learning-objects/processing/image/inline-image-processor.ts @@ -13,7 +13,7 @@ class InlineImageProcessor extends StringProcessor { super(contentType); } - override renderFn(imageUrl: string) { + override renderFn(imageUrl: string): string { if (!isValidHttpUrl(imageUrl)) { throw new ProcessingError(`Image URL is invalid: ${imageUrl}`); } diff --git a/backend/src/services/learning-objects/processing/markdown/markdown-processor.ts b/backend/src/services/learning-objects/processing/markdown/markdown-processor.ts index 39f2adc8..77673cbf 100644 --- a/backend/src/services/learning-objects/processing/markdown/markdown-processor.ts +++ b/backend/src/services/learning-objects/processing/markdown/markdown-processor.ts @@ -14,7 +14,7 @@ class MarkdownProcessor extends StringProcessor { super(DwengoContentType.TEXT_MARKDOWN); } - override renderFn(mdText: string) { + override renderFn(mdText: string): string { try { marked.use({ renderer: dwengoMarkedRenderer }); 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(); html = html.replace( //g, diff --git a/backend/src/services/learning-objects/processing/pdf/pdf-processor.ts b/backend/src/services/learning-objects/processing/pdf/pdf-processor.ts index 26cb4d94..c41de802 100644 --- a/backend/src/services/learning-objects/processing/pdf/pdf-processor.ts +++ b/backend/src/services/learning-objects/processing/pdf/pdf-processor.ts @@ -15,7 +15,7 @@ class PdfProcessor extends StringProcessor { super(DwengoContentType.APPLICATION_PDF); } - override renderFn(pdfUrl: string) { + override renderFn(pdfUrl: string): string { if (!isValidHttpUrl(pdfUrl)) { throw new ProcessingError(`PDF URL is invalid: ${pdfUrl}`); } diff --git a/backend/src/services/learning-objects/processing/processor.ts b/backend/src/services/learning-objects/processing/processor.ts index a11c4416..1dcca255 100644 --- a/backend/src/services/learning-objects/processing/processor.ts +++ b/backend/src/services/learning-objects/processing/processor.ts @@ -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 */ abstract class Processor { - protected constructor(public contentType: DwengoContentType) {} + protected constructor(public contentType: DwengoContentType) { + // Do nothing + } /** * Render the given object. diff --git a/backend/src/services/learning-objects/processing/text/text-processor.ts b/backend/src/services/learning-objects/processing/text/text-processor.ts index db6d37df..a2c84150 100644 --- a/backend/src/services/learning-objects/processing/text/text-processor.ts +++ b/backend/src/services/learning-objects/processing/text/text-processor.ts @@ -11,7 +11,7 @@ class TextProcessor extends StringProcessor { super(DwengoContentType.TEXT_PLAIN); } - override renderFn(text: string) { + override renderFn(text: string): string { // Sanitize plain text to prevent xss. return DOMPurify.sanitize(text); } diff --git a/backend/src/services/learning-paths/database-learning-path-provider.ts b/backend/src/services/learning-paths/database-learning-path-provider.ts index bbcb6485..cc12c44c 100644 --- a/backend/src/services/learning-paths/database-learning-path-provider.ts +++ b/backend/src/services/learning-paths/database-learning-path-provider.ts @@ -18,7 +18,7 @@ async function getLearningObjectsForNodes(nodes: LearningPathNode[]): Promise( await Promise.all( - nodes.map((node) => + nodes.map(async (node) => learningObjectService .getLearningObjectById({ hruid: node.learningObjectHruid, @@ -117,7 +117,7 @@ async function convertNodes( nodesToLearningObjects: Map, personalizedFor?: PersonalizationTarget ): Promise { - const nodesPromise = Array.from(nodesToLearningObjects.entries()).map((entry) => + const nodesPromise = Array.from(nodesToLearningObjects.entries()).map(async (entry) => convertNode(entry[0], entry[1], personalizedFor, nodesToLearningObjects) ); return await Promise.all(nodesPromise); @@ -179,11 +179,11 @@ const databaseLearningPathProvider: LearningPathProvider = { ): Promise { 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 ); const filteredLearningPaths = await Promise.all( - learningPaths.map((learningPath, index) => convertLearningPath(learningPath, index, personalizedFor)) + learningPaths.map(async (learningPath, index) => convertLearningPath(learningPath, index, personalizedFor)) ); return { @@ -200,7 +200,7 @@ const databaseLearningPathProvider: LearningPathProvider = { const learningPathRepo = getLearningPathRepository(); 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))); }, }; diff --git a/backend/src/services/learning-paths/learning-path-service.ts b/backend/src/services/learning-paths/learning-path-service.ts index 4dd21407..f8e328b2 100644 --- a/backend/src/services/learning-paths/learning-path-service.ts +++ b/backend/src/services/learning-paths/learning-path-service.ts @@ -49,7 +49,9 @@ const learningPathService = { * Search learning paths in the data source using the given search string. */ async searchLearningPaths(query: string, language: Language, personalizedFor?: PersonalizationTarget): Promise { - 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(); }, }; diff --git a/backend/src/services/questions.ts b/backend/src/services/questions.ts index 0433b69f..bb42aabd 100644 --- a/backend/src/services/questions.ts +++ b/backend/src/services/questions.ts @@ -2,7 +2,7 @@ import { getAnswerRepository, getQuestionRepository } from '../data/repositories import { mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId } from '../interfaces/question.js'; import { Question } from '../entities/questions/question.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 { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; import { mapToStudent } from '../interfaces/student.js'; @@ -45,7 +45,7 @@ export async function getQuestion(questionId: QuestionId): Promise { const answerRepository = getAnswerRepository(); const question = await fetchQuestion(questionId); @@ -68,7 +68,7 @@ export async function getAnswersByQuestion(questionId: QuestionId, full: boolean return answersDTO.map(mapToAnswerId); } -export async function createQuestion(questionDTO: QuestionDTO) { +export async function createQuestion(questionDTO: QuestionDTO): Promise { const questionRepository = getQuestionRepository(); const author = mapToStudent(questionDTO.author); @@ -86,7 +86,7 @@ export async function createQuestion(questionDTO: QuestionDTO) { return questionDTO; } -export async function deleteQuestion(questionId: QuestionId) { +export async function deleteQuestion(questionId: QuestionId): Promise { const questionRepository = getQuestionRepository(); const question = await fetchQuestion(questionId); diff --git a/backend/src/services/submissions.ts b/backend/src/services/submissions.ts index 153ce169..b47e04bf 100644 --- a/backend/src/services/submissions.ts +++ b/backend/src/services/submissions.ts @@ -2,6 +2,7 @@ import { getSubmissionRepository } from '../data/repositories.js'; import { Language } from '../entities/content/language.js'; import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; import { mapToSubmission, mapToSubmissionDTO, SubmissionDTO } from '../interfaces/submission.js'; +import { Submission } from '../entities/assignments/submission.entity.js'; export async function getSubmission( learningObjectHruid: string, @@ -21,7 +22,7 @@ export async function getSubmission( return mapToSubmissionDTO(submission); } -export async function createSubmission(submissionDTO: SubmissionDTO) { +export async function createSubmission(submissionDTO: SubmissionDTO): Promise { const submissionRepository = getSubmissionRepository(); const submission = mapToSubmission(submissionDTO); @@ -35,7 +36,12 @@ export async function createSubmission(submissionDTO: SubmissionDTO) { 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 { const submissionRepository = getSubmissionRepository(); const submission = getSubmission(learningObjectHruid, language, version, submissionNumber); diff --git a/backend/src/services/teachers.ts b/backend/src/services/teachers.ts index 1949152f..1b098778 100644 --- a/backend/src/services/teachers.ts +++ b/backend/src/services/teachers.ts @@ -77,7 +77,7 @@ export async function getClassIdsByTeacher(username: string): Promise return classes.map((cls) => cls.id); } -export async function fetchStudentsByTeacher(username: string) { +export async function fetchStudentsByTeacher(username: string): Promise { const classes = await getClassIdsByTeacher(username); return (await Promise.all(classes.map(async (id) => getClassStudents(id)))).flat(); diff --git a/backend/src/util/async.ts b/backend/src/util/async.ts index a5fc9b82..7b7b8818 100644 --- a/backend/src/util/async.ts +++ b/backend/src/util/async.ts @@ -6,7 +6,11 @@ * @param regex * @param replacementFn */ -export async function replaceAsync(str: string, regex: RegExp, replacementFn: (match: string, ...args: string[]) => Promise) { +export async function replaceAsync( + str: string, + regex: RegExp, + replacementFn: (match: string, ...args: string[]) => Promise +): Promise { const promises: Promise[] = []; // First run through matches: add all Promises resulting from the replacement function diff --git a/backend/src/util/links.ts b/backend/src/util/links.ts index c4b54c72..ff334eb5 100644 --- a/backend/src/util/links.ts +++ b/backend/src/util/links.ts @@ -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}`; if (learningObjectId.version) { url += `&version=${learningObjectId.version}`; diff --git a/backend/tests/services/learning-objects/database-learning-object-provider.test.ts b/backend/tests/services/learning-objects/database-learning-object-provider.test.ts index 4750e4bd..2c3b7dfc 100644 --- a/backend/tests/services/learning-objects/database-learning-object-provider.test.ts +++ b/backend/tests/services/learning-objects/database-learning-object-provider.test.ts @@ -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 () => { await expect( - (async () => { + (async (): Promise => { await databaseLearningObjectProvider.getLearningObjectIdsFromPath({ hruid: 'non_existing_hruid', 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 () => { await expect( - (async () => { + (async (): Promise => { await databaseLearningObjectProvider.getLearningObjectsFromPath({ hruid: 'non_existing_hruid', language: Language.Dutch, diff --git a/backend/tests/services/learning-path/database-learning-path-provider.test.ts b/backend/tests/services/learning-path/database-learning-path-provider.test.ts index 04782df3..8d9dd530 100644 --- a/backend/tests/services/learning-path/database-learning-path-provider.test.ts +++ b/backend/tests/services/learning-path/database-learning-path-provider.test.ts @@ -124,7 +124,7 @@ describe('DatabaseLearningPathProvider', () => { const learningObjectsOnPath = ( await Promise.all( - example.learningPath.nodes.map((node) => + example.learningPath.nodes.map(async (node) => learningObjectService.getLearningObjectById({ hruid: node.learningObjectHruid, version: node.version, diff --git a/backend/tests/setup-tests.ts b/backend/tests/setup-tests.ts index 9502bcb8..016099f3 100644 --- a/backend/tests/setup-tests.ts +++ b/backend/tests/setup-tests.ts @@ -14,7 +14,7 @@ import { makeTestQuestions } from './test_assets/questions/questions.testdata.js import { makeTestAnswers } from './test_assets/questions/answers.testdata.js'; import { makeTestSubmissions } from './test_assets/assignments/submission.testdata.js'; -export async function setupTestApp() { +export async function setupTestApp(): Promise { dotenv.config({ path: '.env.test' }); await initORM(true); diff --git a/backend/tests/test-assets/learning-objects/dummy/dummy-learning-object-example.ts b/backend/tests/test-assets/learning-objects/dummy/dummy-learning-object-example.ts index b1bd3089..f810d57a 100644 --- a/backend/tests/test-assets/learning-objects/dummy/dummy-learning-object-example.ts +++ b/backend/tests/test-assets/learning-objects/dummy/dummy-learning-object-example.ts @@ -11,7 +11,7 @@ import { envVars, getEnvVar } from '../../../../src/util/envVars'; */ export function dummyLearningObject(hruid: string, language: Language, title: string): LearningObjectExample { return { - createLearningObject: () => { + createLearningObject: (): LearningObject => { const learningObject = new LearningObject(); learningObject.hruid = getEnvVar(envVars.UserContentPrefix) + hruid; learningObject.language = language; diff --git a/backend/tests/test-assets/learning-paths/learning-path-utils.ts b/backend/tests/test-assets/learning-paths/learning-path-utils.ts index c567de66..eb786fe1 100644 --- a/backend/tests/test-assets/learning-paths/learning-path-utils.ts +++ b/backend/tests/test-assets/learning-paths/learning-path-utils.ts @@ -3,7 +3,12 @@ import { LearningPathTransition } from '../../../src/entities/content/learning-p import { LearningPathNode } from '../../../src/entities/content/learning-path-node.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(); trans.node = node; trans.transitionNumber = transitionNumber; @@ -19,7 +24,7 @@ export function createLearningPathNode( version: number, language: Language, startNode: boolean -) { +): LearningPathNode { const node = new LearningPathNode(); node.learningPath = learningPath; node.nodeNumber = nodeNumber; diff --git a/backend/tests/test-utils/expectations.ts b/backend/tests/test-utils/expectations.ts index 0fe63811..347f4b40 100644 --- a/backend/tests/test-utils/expectations.ts +++ b/backend/tests/test-utils/expectations.ts @@ -69,7 +69,7 @@ export function expectToBeCorrectEntity(actual: { entity: T; n * @param filtered the representation as FilteredLearningObject * @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.version).toEqual(original.version); expect(filtered.language).toEqual(original.language); @@ -105,7 +105,7 @@ export function expectToBeCorrectLearningPath( learningPath: LearningPath, expectedEntity: LearningPathEntity, learningObjectsOnPath: FilteredLearningObject[] -) { +): void { expect(learningPath.hruid).toEqual(expectedEntity.hruid); expect(learningPath.language).toEqual(expectedEntity.language); expect(learningPath.description).toEqual(expectedEntity.description); diff --git a/eslint.config.ts b/eslint.config.ts index e4534398..3596633a 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -23,11 +23,20 @@ export default [ languageOptions: { ecmaVersion: 'latest', sourceType: 'module', + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, }, linterOptions: { reportUnusedInlineConfigs: 'error', }, rules: { + 'consistent-return': 'off', + '@typescript-eslint/consistent-return': 'off', + + '@typescript-eslint/explicit-function-return-type': 'warn', + '@typescript-eslint/naming-convention': [ 'warn', { // 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', '@typescript-eslint/no-unused-expressions': 'warn', 'no-unused-vars': 'off', @@ -66,6 +83,10 @@ export default [ '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-constructor-return': 'error', 'no-duplicate-imports': 'error', @@ -110,7 +131,6 @@ export default [ 'no-iterator': 'error', 'no-label-var': 'warn', 'no-labels': 'warn', - 'no-loop-func': 'error', 'no-multi-assign': 'error', 'no-nested-ternary': 'error', 'no-object-constructor': 'error', From 10d3d0567ecd1b9ae43d23bfc67c1762f4755058 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sat, 22 Mar 2025 18:48:13 +0100 Subject: [PATCH 14/43] refactor(backend): no-implicit-coercion --- backend/src/controllers/assignments.ts | 4 ++-- backend/src/controllers/groups.ts | 12 ++++++------ backend/src/controllers/questions.ts | 2 +- backend/src/controllers/submissions.ts | 4 ++-- .../database-learning-path-provider.ts | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index 03332469..e1a39448 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -41,7 +41,7 @@ export async function createAssignmentHandler(req: Request, re } export async function getAssignmentHandler(req: Request, res: Response): Promise { - const id = +req.params.id; + const id = Number(req.params.id); const classid = req.params.classid; if (isNaN(id)) { @@ -61,7 +61,7 @@ export async function getAssignmentHandler(req: Request, res: export async function getAssignmentsSubmissionsHandler(req: Request, res: Response): Promise { const classid = req.params.classid; - const assignmentNumber = +req.params.id; + const assignmentNumber = Number(req.params.id); if (isNaN(assignmentNumber)) { res.status(400).json({ error: 'Assignment id must be a number' }); diff --git a/backend/src/controllers/groups.ts b/backend/src/controllers/groups.ts index b7bfd212..787d3761 100644 --- a/backend/src/controllers/groups.ts +++ b/backend/src/controllers/groups.ts @@ -12,14 +12,14 @@ interface GroupParams { export async function getGroupHandler(req: Request, res: Response): Promise { const classId = req.params.classid; const full = req.query.full === 'true'; - const assignmentId = +req.params.assignmentid; + const assignmentId = Number(req.params.assignmentid); if (isNaN(assignmentId)) { res.status(400).json({ error: 'Assignment id must be a number' }); return; } - const groupId = +req.params.groupid!; // Can't be undefined + const groupId = Number(req.params.groupid!); // Can't be undefined if (isNaN(groupId)) { res.status(400).json({ error: 'Group id must be a number' }); @@ -35,7 +35,7 @@ export async function getAllGroupsHandler(req: Request, res: Response): Promise< const classId = req.params.classid; const full = req.query.full === 'true'; - const assignmentId = +req.params.assignmentid; + const assignmentId = Number(req.params.assignmentid); if (isNaN(assignmentId)) { res.status(400).json({ error: 'Assignment id must be a number' }); @@ -51,7 +51,7 @@ export async function getAllGroupsHandler(req: Request, res: Response): Promise< export async function createGroupHandler(req: Request, res: Response): Promise { const classid = req.params.classid; - const assignmentId = +req.params.assignmentid; + const assignmentId = Number(req.params.assignmentid); if (isNaN(assignmentId)) { res.status(400).json({ error: 'Assignment id must be a number' }); @@ -73,14 +73,14 @@ export async function getGroupSubmissionsHandler(req: Request, res: Response): P const classId = req.params.classid; // Const full = req.query.full === 'true'; - const assignmentId = +req.params.assignmentid; + const assignmentId = Number(req.params.assignmentid); if (isNaN(assignmentId)) { res.status(400).json({ error: 'Assignment id must be a number' }); return; } - const groupId = +req.params.groupid!; // Can't be undefined + const groupId = Number(req.params.groupid!); // Can't be undefined if (isNaN(groupId)) { res.status(400).json({ error: 'Group id must be a number' }); diff --git a/backend/src/controllers/questions.ts b/backend/src/controllers/questions.ts index 917b48ae..1b3dc850 100644 --- a/backend/src/controllers/questions.ts +++ b/backend/src/controllers/questions.ts @@ -17,7 +17,7 @@ function getObjectId(req: Request, res: Response): LearningObjectIdentifier | nu return { hruid, language: (lang as Language) || FALLBACK_LANG, - version: +version, + version: Number(version), }; } diff --git a/backend/src/controllers/submissions.ts b/backend/src/controllers/submissions.ts index cb8f2de2..16836a34 100644 --- a/backend/src/controllers/submissions.ts +++ b/backend/src/controllers/submissions.ts @@ -10,7 +10,7 @@ interface SubmissionParams { export async function getSubmissionHandler(req: Request, res: Response): Promise { const lohruid = req.params.hruid; - const submissionNumber = +req.params.id; + const submissionNumber = Number(req.params.id); if (isNaN(submissionNumber)) { res.status(400).json({ error: 'Submission number is not a number' }); @@ -44,7 +44,7 @@ export async function createSubmissionHandler(req: Request, res: Response): Prom export async function deleteSubmissionHandler(req: Request, res: Response): Promise { const hruid = req.params.hruid; - const submissionNumber = +req.params.id; + const submissionNumber = Number(req.params.id); const lang = languageMap[req.query.language as string] || Language.Dutch; const version = (req.query.version || 1) as number; diff --git a/backend/src/services/learning-paths/database-learning-path-provider.ts b/backend/src/services/learning-paths/database-learning-path-provider.ts index cc12c44c..ed721517 100644 --- a/backend/src/services/learning-paths/database-learning-path-provider.ts +++ b/backend/src/services/learning-paths/database-learning-path-provider.ts @@ -152,7 +152,7 @@ function convertTransition( throw new Error(`Learning object ${transition.next.learningObjectHruid}/${transition.next.language}/${transition.next.version} not found!`); } else { return { - _id: '' + index, // Retained for backwards compatibility. The index uniquely identifies the transition within the learning path. + _id: String(index), // Retained for backwards compatibility. The index uniquely identifies the transition within the learning path. default: false, // We don't work with default transitions but retain this for backwards compatibility. next: { _id: nextNode._id + index, // Construct a unique ID for the transition for backwards compatibility. From 6ad7fbf208db68bfc3f4a6b8d7a41c31e5b96b5b Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sat, 22 Mar 2025 18:50:22 +0100 Subject: [PATCH 15/43] refactor(backend): equality --- backend/src/services/class.ts | 4 ++-- backend/src/services/groups.ts | 2 +- backend/tests/data/questions/answers.test.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/services/class.ts b/backend/src/services/class.ts index 4f3cbc9d..52d841f4 100644 --- a/backend/src/services/class.ts +++ b/backend/src/services/class.ts @@ -25,13 +25,13 @@ export async function createClass(classData: ClassDTO): Promise { const teacherRepository = getTeacherRepository(); const teacherUsernames = classData.teachers || []; const teachers = (await Promise.all(teacherUsernames.map(async (id) => teacherRepository.findByUsername(id)))).filter( - (teacher) => teacher != null + (teacher) => teacher !== null ); const studentRepository = getStudentRepository(); const studentUsernames = classData.students || []; const students = (await Promise.all(studentUsernames.map(async (id) => studentRepository.findByUsername(id)))).filter( - (student) => student != null + (student) => student !== null ); //Const cls = mapToClass(classData, teachers, students); diff --git a/backend/src/services/groups.ts b/backend/src/services/groups.ts index 6abcd3fc..30be8b86 100644 --- a/backend/src/services/groups.ts +++ b/backend/src/services/groups.ts @@ -44,7 +44,7 @@ export async function createGroup(groupData: GroupDTO, classid: string, assignme const memberUsernames = (groupData.members as string[]) || []; // TODO check if groupdata.members is a list const members = (await Promise.all([...memberUsernames].map(async (id) => studentRepository.findByUsername(id)))).filter( - (student) => student != null + (student) => student !== null ); getLogger().debug(members); diff --git a/backend/tests/data/questions/answers.test.ts b/backend/tests/data/questions/answers.test.ts index bcc62cf6..e53f1b5f 100644 --- a/backend/tests/data/questions/answers.test.ts +++ b/backend/tests/data/questions/answers.test.ts @@ -23,7 +23,7 @@ describe('AnswerRepository', () => { const id = new LearningObjectIdentifier('id05', Language.English, 1); const questions = await questionRepository.findAllQuestionsAboutLearningObject(id); - const question = questions.filter((it) => it.sequenceNumber == 2)[0]; + const question = questions.filter((it) => it.sequenceNumber === 2)[0]; const answers = await answerRepository.findAllAnswersToQuestion(question); From 25f9eb2af245746d4341c72b6d4d5453cf83ac66 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sun, 23 Mar 2025 11:14:32 +0100 Subject: [PATCH 16/43] refactor(backend): Types --- backend/src/controllers/auth.ts | 8 +++--- backend/src/controllers/learning-objects.ts | 4 +-- backend/src/controllers/teachers.ts | 6 ++--- backend/src/controllers/users.ts | 2 +- backend/src/interfaces/learning-content.ts | 2 +- .../middleware/auth/authentication-info.d.ts | 4 +-- .../database-learning-object-provider.ts | 5 ++-- .../processing/markdown/markdown-processor.ts | 9 +++++-- .../processing/processing-service.ts | 2 +- .../database-learning-path-provider.ts | 2 +- .../src/sqlite-autoincrement-workaround.ts | 4 +-- backend/src/util/api-helper.ts | 26 +++++++++++-------- backend/src/util/envVars.ts | 4 +-- .../learning-object-example.d.ts | 4 +-- .../learning-paths/learning-path-example.d.ts | 4 +-- .../learning-paths/test-conditions-example.ts | 4 +-- backend/tests/test-utils/expectations.ts | 2 +- .../assignments/assignments.testdata.ts | 4 +-- .../assignments/groups.testdata.ts | 8 ++---- .../assignments/submission.testdata.ts | 8 ++---- .../classes/class-join-requests.testdata.ts | 8 ++---- .../test_assets/classes/classes.testdata.ts | 4 +-- .../classes/teacher-invitations.testdata.ts | 8 ++---- .../content/attachments.testdata.ts | 4 +-- .../content/learning-objects.testdata.ts | 4 +-- .../content/learning-paths.testdata.ts | 4 +-- .../test_assets/questions/answers.testdata.ts | 4 +-- .../questions/questions.testdata.ts | 4 +-- .../test_assets/users/students.testdata.ts | 4 +-- .../test_assets/users/teachers.testdata.ts | 4 +-- eslint.config.ts | 18 +++++++++++-- 31 files changed, 92 insertions(+), 86 deletions(-) diff --git a/backend/src/controllers/auth.ts b/backend/src/controllers/auth.ts index d3f3affb..b87eaf7b 100644 --- a/backend/src/controllers/auth.ts +++ b/backend/src/controllers/auth.ts @@ -1,16 +1,16 @@ import { envVars, getEnvVar } from '../util/envVars.js'; -type FrontendIdpConfig = { +interface FrontendIdpConfig { authority: string; clientId: string; scope: string; responseType: string; -}; +} -type FrontendAuthConfig = { +interface FrontendAuthConfig { student: FrontendIdpConfig; teacher: FrontendIdpConfig; -}; +} const SCOPE = 'openid profile email'; const RESPONSE_TYPE = 'code'; diff --git a/backend/src/controllers/learning-objects.ts b/backend/src/controllers/learning-objects.ts index 6e331ff3..14d200ce 100644 --- a/backend/src/controllers/learning-objects.ts +++ b/backend/src/controllers/learning-objects.ts @@ -13,7 +13,7 @@ function getLearningObjectIdentifierFromRequest(req: Request): LearningObjectIde throw new BadRequestException('HRUID is required.'); } return { - hruid: req.params.hruid as string, + hruid: req.params.hruid, language: (req.query.language || getEnvVar(envVars.FallbackLanguage)) as Language, version: parseInt(req.query.version as string), }; @@ -24,7 +24,7 @@ function getLearningPathIdentifierFromRequest(req: Request): LearningPathIdentif throw new BadRequestException('HRUID is required.'); } return { - hruid: req.params.hruid as string, + hruid: req.params.hruid, language: (req.query.language as Language) || FALLBACK_LANG, }; } diff --git a/backend/src/controllers/teachers.ts b/backend/src/controllers/teachers.ts index e5f1fc87..4bf7eb53 100644 --- a/backend/src/controllers/teachers.ts +++ b/backend/src/controllers/teachers.ts @@ -85,7 +85,7 @@ export async function deleteTeacherHandler(req: Request, res: Response): Promise export async function getTeacherClassHandler(req: Request, res: Response): Promise { try { - const username = req.params.username as string; + const username = req.params.username; const full = req.query.full === 'true'; if (!username) { @@ -104,7 +104,7 @@ export async function getTeacherClassHandler(req: Request, res: Response): Promi export async function getTeacherStudentHandler(req: Request, res: Response): Promise { try { - const username = req.params.username as string; + const username = req.params.username; const full = req.query.full === 'true'; if (!username) { @@ -123,7 +123,7 @@ export async function getTeacherStudentHandler(req: Request, res: Response): Pro export async function getTeacherQuestionHandler(req: Request, res: Response): Promise { try { - const username = req.params.username as string; + const username = req.params.username; const full = req.query.full === 'true'; if (!username) { diff --git a/backend/src/controllers/users.ts b/backend/src/controllers/users.ts index 7706e2ee..255db93a 100644 --- a/backend/src/controllers/users.ts +++ b/backend/src/controllers/users.ts @@ -24,7 +24,7 @@ export async function getAllUsersHandler(req: Request, res: Resp export async function getUserHandler(req: Request, res: Response, service: UserService): Promise { try { - const username = req.params.username as string; + const username = req.params.username; if (!username) { res.status(400).json({ error: 'Missing required field: username' }); diff --git a/backend/src/interfaces/learning-content.ts b/backend/src/interfaces/learning-content.ts index 51474917..693aec37 100644 --- a/backend/src/interfaces/learning-content.ts +++ b/backend/src/interfaces/learning-content.ts @@ -58,7 +58,7 @@ export interface EducationalGoal { export interface ReturnValue { callback_url: string; - callback_schema: Record; + callback_schema: Record; } export interface LearningObjectMetadata { diff --git a/backend/src/middleware/auth/authentication-info.d.ts b/backend/src/middleware/auth/authentication-info.d.ts index 4b060dfa..e8f0d48c 100644 --- a/backend/src/middleware/auth/authentication-info.d.ts +++ b/backend/src/middleware/auth/authentication-info.d.ts @@ -1,11 +1,11 @@ /** * Object with information about the user who is currently logged in. */ -export type AuthenticationInfo = { +export interface AuthenticationInfo { accountType: 'student' | 'teacher'; username: string; name?: string; firstName?: string; lastName?: string; email?: string; -}; +} diff --git a/backend/src/services/learning-objects/database-learning-object-provider.ts b/backend/src/services/learning-objects/database-learning-object-provider.ts index 03dbfdff..a8055f2c 100644 --- a/backend/src/services/learning-objects/database-learning-object-provider.ts +++ b/backend/src/services/learning-objects/database-learning-object-provider.ts @@ -1,7 +1,6 @@ import { LearningObjectProvider } from './learning-object-provider.js'; import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from '../../interfaces/learning-content.js'; import { getLearningObjectRepository, getLearningPathRepository } from '../../data/repositories.js'; -import { Language } from '../../entities/content/language.js'; import { LearningObject } from '../../entities/content/learning-object.entity.js'; import { getUrlStringForLearningObject } from '../../util/links.js'; import processingService from './processing/processing-service.js'; @@ -44,7 +43,7 @@ function convertLearningObject(learningObject: LearningObject | null): FilteredL async function findLearningObjectEntityById(id: LearningObjectIdentifier): Promise { const learningObjectRepo = getLearningObjectRepository(); - return learningObjectRepo.findLatestByHruidAndLanguage(id.hruid, id.language as Language); + return learningObjectRepo.findLatestByHruidAndLanguage(id.hruid, id.language); } /** @@ -65,7 +64,7 @@ const databaseLearningObjectProvider: LearningObjectProvider = { async getLearningObjectHTML(id: LearningObjectIdentifier): Promise { const learningObjectRepo = getLearningObjectRepository(); - const learningObject = await learningObjectRepo.findLatestByHruidAndLanguage(id.hruid, id.language as Language); + const learningObject = await learningObjectRepo.findLatestByHruidAndLanguage(id.hruid, id.language); if (!learningObject) { return null; } diff --git a/backend/src/services/learning-objects/processing/markdown/markdown-processor.ts b/backend/src/services/learning-objects/processing/markdown/markdown-processor.ts index 77673cbf..dbbf2a37 100644 --- a/backend/src/services/learning-objects/processing/markdown/markdown-processor.ts +++ b/backend/src/services/learning-objects/processing/markdown/markdown-processor.ts @@ -8,6 +8,7 @@ import { DwengoContentType } from '../content-type.js'; import dwengoMarkedRenderer from './dwengo-marked-renderer.js'; import { StringProcessor } from '../string-processor.js'; import { ProcessingError } from '../processing-error.js'; +import { YAMLException } from 'js-yaml'; class MarkdownProcessor extends StringProcessor { constructor() { @@ -19,8 +20,12 @@ class MarkdownProcessor extends StringProcessor { marked.use({ renderer: dwengoMarkedRenderer }); const html = marked(mdText, { async: false }); return this.replaceLinks(html); // Replace html image links path - } catch (e: any) { - throw new ProcessingError(e.message); + } catch (e: unknown) { + if (e instanceof YAMLException) { + throw new ProcessingError(e.message); + } + + throw new ProcessingError('Unknown error while processing markdown: ' + e); } } diff --git a/backend/src/services/learning-objects/processing/processing-service.ts b/backend/src/services/learning-objects/processing/processing-service.ts index a6c662cc..f731eb5d 100644 --- a/backend/src/services/learning-objects/processing/processing-service.ts +++ b/backend/src/services/learning-objects/processing/processing-service.ts @@ -21,7 +21,7 @@ const EMBEDDED_LEARNING_OBJECT_PLACEHOLDER = />; + private processors!: Map>; constructor() { const processors = [ diff --git a/backend/src/services/learning-paths/database-learning-path-provider.ts b/backend/src/services/learning-paths/database-learning-path-provider.ts index ed721517..c8ab41ca 100644 --- a/backend/src/services/learning-paths/database-learning-path-provider.ts +++ b/backend/src/services/learning-paths/database-learning-path-provider.ts @@ -25,7 +25,7 @@ async function getLearningObjectsForNodes(nodes: LearningPathNode[]): Promise <[LearningPathNode, FilteredLearningObject | null]>[node, learningObject]) + .then((learningObject) => ([node, learningObject] as [LearningPathNode, FilteredLearningObject | null])) ) ) ); diff --git a/backend/src/sqlite-autoincrement-workaround.ts b/backend/src/sqlite-autoincrement-workaround.ts index a5c20dfd..29d02ffc 100644 --- a/backend/src/sqlite-autoincrement-workaround.ts +++ b/backend/src/sqlite-autoincrement-workaround.ts @@ -27,14 +27,14 @@ export class SqliteAutoincrementSubscriber implements EventSubscriber { for (const prop of Object.values(args.meta.properties)) { const property = prop as EntityProperty; - if (property.primary && property.autoincrement && !(args.entity as Record)[property.name]) { + if (property.primary && property.autoincrement && !(args.entity as Record)[property.name]) { // Obtain and increment sequence number of this entity. const propertyKey = args.meta.class.name + '.' + property.name; const nextSeqNumber = this.sequenceNumbersForEntityType.get(propertyKey) || 0; this.sequenceNumbersForEntityType.set(propertyKey, nextSeqNumber + 1); // Set the property accordingly. - (args.entity as Record)[property.name] = nextSeqNumber + 1; + (args.entity as Record)[property.name] = nextSeqNumber + 1; } } } diff --git a/backend/src/util/api-helper.ts b/backend/src/util/api-helper.ts index fff7a6a1..9aac5bd1 100644 --- a/backend/src/util/api-helper.ts +++ b/backend/src/util/api-helper.ts @@ -1,5 +1,6 @@ import axios, { AxiosRequestConfig } from 'axios'; import { getLogger, Logger } from '../logging/initalize.js'; +import { LearningObjectIdentifier } from '../interfaces/learning-content.js'; const logger: Logger = getLogger(); @@ -17,8 +18,8 @@ export async function fetchWithLogging( url: string, description: string, options?: { - params?: Record; - query?: Record; + params?: Record | LearningObjectIdentifier; + query?: Record; responseType?: 'json' | 'text'; } ): Promise { @@ -26,18 +27,21 @@ export async function fetchWithLogging( const config: AxiosRequestConfig = options || {}; const response = await axios.get(url, config); return response.data; - } catch (error: any) { - if (error.response) { - if (error.response.status === 404) { - logger.debug(`❌ ERROR: ${description} not found (404) at "${url}".`); + } catch (error: unknown) { + if (axios.isAxiosError(error)) { + if (error.response) { + if (error.response.status === 404) { + logger.debug(`❌ ERROR: ${description} not found (404) at "${url}".`); + } else { + logger.debug( + `❌ ERROR: Failed to fetch ${description}. Status: ${error.response.status} - ${error.response.statusText} (URL: "${url}")` + ); + } } else { - logger.debug( - `❌ ERROR: Failed to fetch ${description}. Status: ${error.response.status} - ${error.response.statusText} (URL: "${url}")` - ); + logger.debug(`❌ ERROR: Network or unexpected error when fetching ${description}:`, error.message); } - } else { - logger.debug(`❌ ERROR: Network or unexpected error when fetching ${description}:`, error.message); } + logger.error(`❌ ERROR: Unknown error while fetching ${description}.`, error); return null; } } diff --git a/backend/src/util/envVars.ts b/backend/src/util/envVars.ts index 15ad5928..aed0bebd 100644 --- a/backend/src/util/envVars.ts +++ b/backend/src/util/envVars.ts @@ -5,7 +5,7 @@ const STUDENT_IDP_PREFIX = IDP_PREFIX + 'STUDENT_'; const TEACHER_IDP_PREFIX = IDP_PREFIX + 'TEACHER_'; const CORS_PREFIX = PREFIX + 'CORS_'; -type EnvVar = { key: string; required?: boolean; defaultValue?: any }; +interface EnvVar { key: string; required?: boolean; defaultValue?: number | string | boolean } export const envVars: { [key: string]: EnvVar } = { Port: { key: PREFIX + 'PORT', defaultValue: 3000 }, @@ -44,7 +44,7 @@ export function getEnvVar(envVar: EnvVar): string { } else if (envVar.required) { throw new Error(`Missing environment variable: ${envVar.key}`); } else { - return envVar.defaultValue || ''; + return String(envVar.defaultValue) || ''; } } diff --git a/backend/tests/test-assets/learning-objects/learning-object-example.d.ts b/backend/tests/test-assets/learning-objects/learning-object-example.d.ts index 1d4009f8..5053ca75 100644 --- a/backend/tests/test-assets/learning-objects/learning-object-example.d.ts +++ b/backend/tests/test-assets/learning-objects/learning-object-example.d.ts @@ -1,8 +1,8 @@ import { LearningObject } from '../../../src/entities/content/learning-object.entity'; import { Attachment } from '../../../src/entities/content/attachment.entity'; -type LearningObjectExample = { +interface LearningObjectExample { createLearningObject: () => LearningObject; createAttachment: { [key: string]: (owner: LearningObject) => Attachment }; getHTMLRendering: () => string; -}; +} diff --git a/backend/tests/test-assets/learning-paths/learning-path-example.d.ts b/backend/tests/test-assets/learning-paths/learning-path-example.d.ts index 9df3ba48..d8e94dc8 100644 --- a/backend/tests/test-assets/learning-paths/learning-path-example.d.ts +++ b/backend/tests/test-assets/learning-paths/learning-path-example.d.ts @@ -1,3 +1,3 @@ -type LearningPathExample = { +interface LearningPathExample { createLearningPath: () => LearningPath; -}; +} diff --git a/backend/tests/test-assets/learning-paths/test-conditions-example.ts b/backend/tests/test-assets/learning-paths/test-conditions-example.ts index 816cd735..b5e38c24 100644 --- a/backend/tests/test-assets/learning-paths/test-conditions-example.ts +++ b/backend/tests/test-assets/learning-paths/test-conditions-example.ts @@ -6,12 +6,12 @@ import { createLearningPathNode, createLearningPathTransition } from './learning import { LearningObject } from '../../../src/entities/content/learning-object.entity'; import { envVars, getEnvVar } from '../../../src/util/envVars'; -export type ConditionTestLearningPathAndLearningObjects = { +export interface ConditionTestLearningPathAndLearningObjects { branchingObject: LearningObject; extraExerciseObject: LearningObject; finalObject: LearningObject; learningPath: LearningPath; -}; +} export function createConditionTestLearningPathAndLearningObjects(): ConditionTestLearningPathAndLearningObjects { const learningPath = new LearningPath(); diff --git a/backend/tests/test-utils/expectations.ts b/backend/tests/test-utils/expectations.ts index 347f4b40..8f33bd8f 100644 --- a/backend/tests/test-utils/expectations.ts +++ b/backend/tests/test-utils/expectations.ts @@ -25,7 +25,7 @@ export function expectToBeCorrectEntity(actual: { entity: T; n expected.entity[property] !== undefined && // If we don't expect a certain value for a property, we assume it can be filled in by the database however it wants. typeof expected.entity[property] !== 'function' // Functions obviously are not persisted via the database ) { - if (!actual.entity.hasOwnProperty(property)) { + if (!Object.prototype.hasOwnProperty.call(actual.entity, property)) { throw new AssertionError({ message: `${expected.name} has defined property ${property}, but ${actual.name} is missing it.`, }); diff --git a/backend/tests/test_assets/assignments/assignments.testdata.ts b/backend/tests/test_assets/assignments/assignments.testdata.ts index 7f909de4..5a5a3d5e 100644 --- a/backend/tests/test_assets/assignments/assignments.testdata.ts +++ b/backend/tests/test_assets/assignments/assignments.testdata.ts @@ -1,9 +1,9 @@ -import { Connection, EntityManager, IDatabaseDriver } from '@mikro-orm/core'; +import { EntityManager } from '@mikro-orm/core'; import { Assignment } from '../../../src/entities/assignments/assignment.entity'; import { Class } from '../../../src/entities/classes/class.entity'; import { Language } from '../../../src/entities/content/language'; -export function makeTestAssignemnts(em: EntityManager>, classes: Array): Array { +export function makeTestAssignemnts(em: EntityManager, classes: Array): Array { const assignment01 = em.create(Assignment, { within: classes[0], id: 1, diff --git a/backend/tests/test_assets/assignments/groups.testdata.ts b/backend/tests/test_assets/assignments/groups.testdata.ts index 0e9ef201..199d6f48 100644 --- a/backend/tests/test_assets/assignments/groups.testdata.ts +++ b/backend/tests/test_assets/assignments/groups.testdata.ts @@ -1,13 +1,9 @@ -import { Connection, EntityManager, IDatabaseDriver } from '@mikro-orm/core'; +import { EntityManager } from '@mikro-orm/core'; import { Group } from '../../../src/entities/assignments/group.entity'; import { Assignment } from '../../../src/entities/assignments/assignment.entity'; import { Student } from '../../../src/entities/users/student.entity'; -export function makeTestGroups( - em: EntityManager>, - students: Array, - assignments: Array -): Array { +export function makeTestGroups(em: EntityManager, students: Array, assignments: Array): Array { const group01 = em.create(Group, { assignment: assignments[0], groupNumber: 1, diff --git a/backend/tests/test_assets/assignments/submission.testdata.ts b/backend/tests/test_assets/assignments/submission.testdata.ts index 95dd65df..7b56a6cb 100644 --- a/backend/tests/test_assets/assignments/submission.testdata.ts +++ b/backend/tests/test_assets/assignments/submission.testdata.ts @@ -1,14 +1,10 @@ -import { Connection, EntityManager, IDatabaseDriver } from '@mikro-orm/core'; +import { EntityManager } from '@mikro-orm/core'; import { Submission } from '../../../src/entities/assignments/submission.entity'; import { Language } from '../../../src/entities/content/language'; import { Student } from '../../../src/entities/users/student.entity'; import { Group } from '../../../src/entities/assignments/group.entity'; -export function makeTestSubmissions( - em: EntityManager>, - students: Array, - groups: Array -): Array { +export function makeTestSubmissions(em: EntityManager, students: Array, groups: Array): Array { const submission01 = em.create(Submission, { learningObjectHruid: 'id03', learningObjectLanguage: Language.English, diff --git a/backend/tests/test_assets/classes/class-join-requests.testdata.ts b/backend/tests/test_assets/classes/class-join-requests.testdata.ts index 8d9e328f..f0e151ca 100644 --- a/backend/tests/test_assets/classes/class-join-requests.testdata.ts +++ b/backend/tests/test_assets/classes/class-join-requests.testdata.ts @@ -1,13 +1,9 @@ -import { Connection, EntityManager, IDatabaseDriver } from '@mikro-orm/core'; +import { EntityManager } from '@mikro-orm/core'; import { ClassJoinRequest, ClassJoinRequestStatus } from '../../../src/entities/classes/class-join-request.entity'; import { Student } from '../../../src/entities/users/student.entity'; import { Class } from '../../../src/entities/classes/class.entity'; -export function makeTestClassJoinRequests( - em: EntityManager>, - students: Array, - classes: Array -): Array { +export function makeTestClassJoinRequests(em: EntityManager, students: Array, classes: Array): Array { const classJoinRequest01 = em.create(ClassJoinRequest, { requester: students[4], class: classes[1], diff --git a/backend/tests/test_assets/classes/classes.testdata.ts b/backend/tests/test_assets/classes/classes.testdata.ts index b3e98bc8..e122d084 100644 --- a/backend/tests/test_assets/classes/classes.testdata.ts +++ b/backend/tests/test_assets/classes/classes.testdata.ts @@ -1,9 +1,9 @@ -import { Connection, EntityManager, IDatabaseDriver } from '@mikro-orm/core'; +import { EntityManager } from '@mikro-orm/core'; import { Class } from '../../../src/entities/classes/class.entity'; import { Student } from '../../../src/entities/users/student.entity'; import { Teacher } from '../../../src/entities/users/teacher.entity'; -export function makeTestClasses(em: EntityManager>, students: Array, teachers: Array): Array { +export function makeTestClasses(em: EntityManager, students: Array, teachers: Array): Array { const studentsClass01 = students.slice(0, 7); const teacherClass01: Array = teachers.slice(0, 1); diff --git a/backend/tests/test_assets/classes/teacher-invitations.testdata.ts b/backend/tests/test_assets/classes/teacher-invitations.testdata.ts index 84eeab01..35b471a4 100644 --- a/backend/tests/test_assets/classes/teacher-invitations.testdata.ts +++ b/backend/tests/test_assets/classes/teacher-invitations.testdata.ts @@ -1,13 +1,9 @@ -import { Connection, EntityManager, IDatabaseDriver } from '@mikro-orm/core'; +import { EntityManager } from '@mikro-orm/core'; import { TeacherInvitation } from '../../../src/entities/classes/teacher-invitation.entity'; import { Teacher } from '../../../src/entities/users/teacher.entity'; import { Class } from '../../../src/entities/classes/class.entity'; -export function makeTestTeacherInvitations( - em: EntityManager>, - teachers: Array, - classes: Array -): Array { +export function makeTestTeacherInvitations(em: EntityManager, teachers: Array, classes: Array): Array { const teacherInvitation01 = em.create(TeacherInvitation, { sender: teachers[1], receiver: teachers[0], diff --git a/backend/tests/test_assets/content/attachments.testdata.ts b/backend/tests/test_assets/content/attachments.testdata.ts index 9f690d9c..22242a77 100644 --- a/backend/tests/test_assets/content/attachments.testdata.ts +++ b/backend/tests/test_assets/content/attachments.testdata.ts @@ -1,8 +1,8 @@ -import { Connection, EntityManager, IDatabaseDriver } from '@mikro-orm/core'; +import { EntityManager } from '@mikro-orm/core'; import { Attachment } from '../../../src/entities/content/attachment.entity'; import { LearningObject } from '../../../src/entities/content/learning-object.entity'; -export function makeTestAttachments(em: EntityManager>, learningObjects: Array): Array { +export function makeTestAttachments(em: EntityManager, learningObjects: Array): Array { const attachment01 = em.create(Attachment, { learningObject: learningObjects[1], name: 'attachment01', diff --git a/backend/tests/test_assets/content/learning-objects.testdata.ts b/backend/tests/test_assets/content/learning-objects.testdata.ts index 17ed4f01..e45bdea0 100644 --- a/backend/tests/test_assets/content/learning-objects.testdata.ts +++ b/backend/tests/test_assets/content/learning-objects.testdata.ts @@ -1,9 +1,9 @@ -import { Connection, EntityManager, IDatabaseDriver } from '@mikro-orm/core'; +import { EntityManager } from '@mikro-orm/core'; import { LearningObject, ReturnValue } from '../../../src/entities/content/learning-object.entity'; import { Language } from '../../../src/entities/content/language'; import { DwengoContentType } from '../../../src/services/learning-objects/processing/content-type'; -export function makeTestLearningObjects(em: EntityManager>): Array { +export function makeTestLearningObjects(em: EntityManager): Array { const returnValue: ReturnValue = new ReturnValue(); returnValue.callbackSchema = ''; returnValue.callbackUrl = ''; diff --git a/backend/tests/test_assets/content/learning-paths.testdata.ts b/backend/tests/test_assets/content/learning-paths.testdata.ts index 10de885c..3b62b55c 100644 --- a/backend/tests/test_assets/content/learning-paths.testdata.ts +++ b/backend/tests/test_assets/content/learning-paths.testdata.ts @@ -1,10 +1,10 @@ -import { Connection, EntityManager, IDatabaseDriver } from '@mikro-orm/core'; +import { EntityManager } from '@mikro-orm/core'; import { LearningPath } from '../../../src/entities/content/learning-path.entity'; import { Language } from '../../../src/entities/content/language'; import { LearningPathTransition } from '../../../src/entities/content/learning-path-transition.entity'; import { LearningPathNode } from '../../../src/entities/content/learning-path-node.entity'; -export function makeTestLearningPaths(em: EntityManager>): Array { +export function makeTestLearningPaths(em: EntityManager): Array { const learningPathNode01: LearningPathNode = new LearningPathNode(); const learningPathNode02: LearningPathNode = new LearningPathNode(); const learningPathNode03: LearningPathNode = new LearningPathNode(); diff --git a/backend/tests/test_assets/questions/answers.testdata.ts b/backend/tests/test_assets/questions/answers.testdata.ts index 20e816da..4332a70f 100644 --- a/backend/tests/test_assets/questions/answers.testdata.ts +++ b/backend/tests/test_assets/questions/answers.testdata.ts @@ -1,9 +1,9 @@ -import { Connection, EntityManager, IDatabaseDriver } from '@mikro-orm/core'; +import { EntityManager } from '@mikro-orm/core'; import { Answer } from '../../../src/entities/questions/answer.entity'; import { Teacher } from '../../../src/entities/users/teacher.entity'; import { Question } from '../../../src/entities/questions/question.entity'; -export function makeTestAnswers(em: EntityManager>, teachers: Array, questions: Array): Array { +export function makeTestAnswers(em: EntityManager, teachers: Array, questions: Array): Array { const answer01 = em.create(Answer, { author: teachers[0], toQuestion: questions[1], diff --git a/backend/tests/test_assets/questions/questions.testdata.ts b/backend/tests/test_assets/questions/questions.testdata.ts index cea43e18..0c19109a 100644 --- a/backend/tests/test_assets/questions/questions.testdata.ts +++ b/backend/tests/test_assets/questions/questions.testdata.ts @@ -1,9 +1,9 @@ -import { Connection, EntityManager, IDatabaseDriver } from '@mikro-orm/core'; +import { EntityManager } from '@mikro-orm/core'; import { Question } from '../../../src/entities/questions/question.entity'; import { Language } from '../../../src/entities/content/language'; import { Student } from '../../../src/entities/users/student.entity'; -export function makeTestQuestions(em: EntityManager>, students: Array): Array { +export function makeTestQuestions(em: EntityManager, students: Array): Array { const question01 = em.create(Question, { learningObjectLanguage: Language.English, learningObjectVersion: 1, diff --git a/backend/tests/test_assets/users/students.testdata.ts b/backend/tests/test_assets/users/students.testdata.ts index 61e0b590..aff29913 100644 --- a/backend/tests/test_assets/users/students.testdata.ts +++ b/backend/tests/test_assets/users/students.testdata.ts @@ -1,7 +1,7 @@ -import { Connection, EntityManager, IDatabaseDriver } from '@mikro-orm/core'; +import { EntityManager } from '@mikro-orm/core'; import { Student } from '../../../src/entities/users/student.entity'; -export function makeTestStudents(em: EntityManager>): Array { +export function makeTestStudents(em: EntityManager): Array { const student01 = em.create(Student, { username: 'Noordkaap', firstName: 'Stijn', diff --git a/backend/tests/test_assets/users/teachers.testdata.ts b/backend/tests/test_assets/users/teachers.testdata.ts index d8985e44..7e519f7d 100644 --- a/backend/tests/test_assets/users/teachers.testdata.ts +++ b/backend/tests/test_assets/users/teachers.testdata.ts @@ -1,7 +1,7 @@ import { Teacher } from '../../../src/entities/users/teacher.entity'; -import { Connection, EntityManager, IDatabaseDriver } from '@mikro-orm/core'; +import { EntityManager } from '@mikro-orm/core'; -export function makeTestTeachers(em: EntityManager>): Array { +export function makeTestTeachers(em: EntityManager): Array { const teacher01 = em.create(Teacher, { username: 'FooFighters', firstName: 'Dave', diff --git a/eslint.config.ts b/eslint.config.ts index 3596633a..bbc68665 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -34,6 +34,10 @@ export default [ rules: { 'consistent-return': 'off', '@typescript-eslint/consistent-return': 'off', + '@typescript-eslint/consistent-type-assertions': 'error', + '@typescript-eslint/consistent-type-definitions': 'error', + '@typescript-eslint/consistent-type-exports': 'off', + '@typescript-eslint/consistent-type-imports': 'off', '@typescript-eslint/explicit-function-return-type': 'warn', @@ -58,12 +62,24 @@ export default [ } ], + 'no-dupe-class-members': 'off', + '@typescript-eslint/no-dupe-class-members': 'off', + '@typescript-eslint/no-duplicate-enum-values': 'error', + 'no-duplicate-imports': 'off', + '@typescript-eslint/no-duplicate-type-constituents': 'off', + // 'no-empty-function': 'off', '@typescript-eslint/no-empty-function': 'error', 'no-loop-func': 'off', '@typescript-eslint/no-loop-func': 'error', + '@typescript-eslint/no-type-alias': 'off', + + '@typescript-eslint/no-unnecessary-type-arguments': 'error', + '@typescript-eslint/no-unnecessary-type-assertion': 'error', + '@typescript-eslint/no-unnecessary-type-constraint': 'error', + '@typescript-eslint/no-unnecessary-type-parameters': 'off', '@typescript-eslint/no-unsafe-function-type': 'error', 'no-unused-expressions': 'off', @@ -89,7 +105,6 @@ export default [ 'no-await-in-loop': 'warn', 'no-constructor-return': 'error', - 'no-duplicate-imports': 'error', 'no-inner-declarations': 'error', 'no-self-compare': 'error', 'no-template-curly-in-string': 'error', @@ -100,7 +115,6 @@ export default [ 'arrow-body-style': ['warn', 'as-needed'], 'block-scoped-var': 'warn', 'capitalized-comments': 'warn', - 'consistent-return': 'warn', 'consistent-this': 'error', curly: 'error', 'default-case': 'error', From 5b31cec5fed70a8d31bf4d6e8a4d32a4ec23c856 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sun, 23 Mar 2025 11:31:48 +0100 Subject: [PATCH 17/43] refactor(backend): File has too many classes --- backend/src/controllers/learning-objects.ts | 2 +- backend/src/controllers/learning-paths.ts | 3 +- .../content/educational-goal.entity.ts | 10 +++++ .../content/learning-object.entity.ts | 22 ++-------- .../entities/content/return-value.entity.ts | 10 +++++ backend/src/exceptions.ts | 42 ------------------- backend/src/exceptions/badRequestException.ts | 11 +++++ backend/src/exceptions/forbiddenException.ts | 10 +++++ backend/src/exceptions/httpException.ts | 8 ++++ backend/src/exceptions/notFoundException.ts | 10 +++++ .../src/exceptions/unauthorizedException.ts | 10 +++++ backend/src/middleware/auth/auth.ts | 3 +- backend/src/util/envVars.ts | 6 ++- .../pn-werkingnotebooks-example.ts | 4 +- .../content/learning-objects.testdata.ts | 3 +- 15 files changed, 87 insertions(+), 67 deletions(-) create mode 100644 backend/src/entities/content/educational-goal.entity.ts create mode 100644 backend/src/entities/content/return-value.entity.ts delete mode 100644 backend/src/exceptions.ts create mode 100644 backend/src/exceptions/badRequestException.ts create mode 100644 backend/src/exceptions/forbiddenException.ts create mode 100644 backend/src/exceptions/httpException.ts create mode 100644 backend/src/exceptions/notFoundException.ts create mode 100644 backend/src/exceptions/unauthorizedException.ts diff --git a/backend/src/controllers/learning-objects.ts b/backend/src/controllers/learning-objects.ts index 14d200ce..ab850d6a 100644 --- a/backend/src/controllers/learning-objects.ts +++ b/backend/src/controllers/learning-objects.ts @@ -4,7 +4,7 @@ import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifie import learningObjectService from '../services/learning-objects/learning-object-service.js'; import { envVars, getEnvVar } from '../util/envVars.js'; import { Language } from '../entities/content/language.js'; -import { BadRequestException } from '../exceptions.js'; +import { BadRequestException } from '../exceptions/badRequestException.js'; import attachmentService from '../services/learning-objects/attachment-service.js'; import { NotFoundError } from '@mikro-orm/core'; diff --git a/backend/src/controllers/learning-paths.ts b/backend/src/controllers/learning-paths.ts index 37f92d91..90266fb9 100644 --- a/backend/src/controllers/learning-paths.ts +++ b/backend/src/controllers/learning-paths.ts @@ -2,13 +2,14 @@ import { Request, Response } from 'express'; import { themes } from '../data/themes.js'; import { FALLBACK_LANG } from '../config.js'; import learningPathService from '../services/learning-paths/learning-path-service.js'; -import { BadRequestException, NotFoundException } from '../exceptions.js'; +import { BadRequestException } from '../exceptions/badRequestException.js'; import { Language } from '../entities/content/language.js'; import { PersonalizationTarget, personalizedForGroup, personalizedForStudent, } from '../services/learning-paths/learning-path-personalization-util.js'; +import { NotFoundException } from '../exceptions/notFoundException.js'; /** * Fetch learning paths based on query parameters. diff --git a/backend/src/entities/content/educational-goal.entity.ts b/backend/src/entities/content/educational-goal.entity.ts new file mode 100644 index 00000000..fafe1a01 --- /dev/null +++ b/backend/src/entities/content/educational-goal.entity.ts @@ -0,0 +1,10 @@ +import { Embeddable, Property } from '@mikro-orm/core'; + +@Embeddable() +export class EducationalGoal { + @Property({ type: 'string' }) + source!: string; + + @Property({ type: 'string' }) + id!: string; +} diff --git a/backend/src/entities/content/learning-object.entity.ts b/backend/src/entities/content/learning-object.entity.ts index 9eda22ba..a0da9d7b 100644 --- a/backend/src/entities/content/learning-object.entity.ts +++ b/backend/src/entities/content/learning-object.entity.ts @@ -1,28 +1,12 @@ -import { Embeddable, Embedded, Entity, Enum, ManyToMany, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; +import { Embedded, Entity, Enum, ManyToMany, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; import { Language } from './language.js'; import { Attachment } from './attachment.entity.js'; import { Teacher } from '../users/teacher.entity.js'; import { DwengoContentType } from '../../services/learning-objects/processing/content-type.js'; import { v4 } from 'uuid'; import { LearningObjectRepository } from '../../data/content/learning-object-repository.js'; - -@Embeddable() -export class EducationalGoal { - @Property({ type: 'string' }) - source!: string; - - @Property({ type: 'string' }) - id!: string; -} - -@Embeddable() -export class ReturnValue { - @Property({ type: 'string' }) - callbackUrl!: string; - - @Property({ type: 'json' }) - callbackSchema!: string; -} +import { EducationalGoal } from './educational-goal.entity.js'; +import { ReturnValue } from './return-value.entity.js'; @Entity({ repository: () => LearningObjectRepository }) export class LearningObject { diff --git a/backend/src/entities/content/return-value.entity.ts b/backend/src/entities/content/return-value.entity.ts new file mode 100644 index 00000000..d38b0693 --- /dev/null +++ b/backend/src/entities/content/return-value.entity.ts @@ -0,0 +1,10 @@ +import { Embeddable, Property } from '@mikro-orm/core'; + +@Embeddable() +export class ReturnValue { + @Property({ type: 'string' }) + callbackUrl!: string; + + @Property({ type: 'json' }) + callbackSchema!: string; +} diff --git a/backend/src/exceptions.ts b/backend/src/exceptions.ts deleted file mode 100644 index e93a6c93..00000000 --- a/backend/src/exceptions.ts +++ /dev/null @@ -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); - } -} diff --git a/backend/src/exceptions/badRequestException.ts b/backend/src/exceptions/badRequestException.ts new file mode 100644 index 00000000..7249fcc8 --- /dev/null +++ b/backend/src/exceptions/badRequestException.ts @@ -0,0 +1,11 @@ +import { HttpException } from './httpException.js'; + +/** + * Exception for HTTP 400 Bad Request + */ + +export class BadRequestException extends HttpException { + constructor(message: string = 'Bad Request') { + super(400, message); + } +} diff --git a/backend/src/exceptions/forbiddenException.ts b/backend/src/exceptions/forbiddenException.ts new file mode 100644 index 00000000..f452e390 --- /dev/null +++ b/backend/src/exceptions/forbiddenException.ts @@ -0,0 +1,10 @@ +import { HttpException } from './httpException.js'; + +/** + * Exception for HTTP 403 Forbidden + */ +export class ForbiddenException extends HttpException { + constructor(message: string = 'Forbidden') { + super(403, message); + } +} diff --git a/backend/src/exceptions/httpException.ts b/backend/src/exceptions/httpException.ts new file mode 100644 index 00000000..364def5d --- /dev/null +++ b/backend/src/exceptions/httpException.ts @@ -0,0 +1,8 @@ +export class HttpException extends Error { + constructor( + public status: number, + message: string + ) { + super(message); + } +} diff --git a/backend/src/exceptions/notFoundException.ts b/backend/src/exceptions/notFoundException.ts new file mode 100644 index 00000000..b5c6a916 --- /dev/null +++ b/backend/src/exceptions/notFoundException.ts @@ -0,0 +1,10 @@ +import { HttpException } from './httpException.js'; + +/** + * Exception for HTTP 404 Not Found + */ +export class NotFoundException extends HttpException { + constructor(message: string = 'Not Found') { + super(404, message); + } +} diff --git a/backend/src/exceptions/unauthorizedException.ts b/backend/src/exceptions/unauthorizedException.ts new file mode 100644 index 00000000..51ce04d4 --- /dev/null +++ b/backend/src/exceptions/unauthorizedException.ts @@ -0,0 +1,10 @@ +import { HttpException } from './httpException.js'; + +/** + * Exception for HTTP 401 Unauthorized + */ +export class UnauthorizedException extends HttpException { + constructor(message: string = 'Unauthorized') { + super(401, message); + } +} diff --git a/backend/src/middleware/auth/auth.ts b/backend/src/middleware/auth/auth.ts index 345bfa59..6d793590 100644 --- a/backend/src/middleware/auth/auth.ts +++ b/backend/src/middleware/auth/auth.ts @@ -6,7 +6,8 @@ import jwksClient from 'jwks-rsa'; import * as express from 'express'; import { AuthenticatedRequest } from './authenticated-request.js'; import { AuthenticationInfo } from './authentication-info.js'; -import { ForbiddenException, UnauthorizedException } from '../../exceptions.js'; +import { UnauthorizedException } from '../../exceptions/unauthorizedException.js'; +import { ForbiddenException } from '../../exceptions/forbiddenException.js'; const JWKS_CACHE = true; const JWKS_RATE_LIMIT = true; diff --git a/backend/src/util/envVars.ts b/backend/src/util/envVars.ts index aed0bebd..09287788 100644 --- a/backend/src/util/envVars.ts +++ b/backend/src/util/envVars.ts @@ -5,7 +5,11 @@ const STUDENT_IDP_PREFIX = IDP_PREFIX + 'STUDENT_'; const TEACHER_IDP_PREFIX = IDP_PREFIX + 'TEACHER_'; const CORS_PREFIX = PREFIX + 'CORS_'; -interface EnvVar { key: string; required?: boolean; defaultValue?: number | string | boolean } +interface EnvVar { + key: string; + required?: boolean; + defaultValue?: number | string | boolean; +} export const envVars: { [key: string]: EnvVar } = { Port: { key: PREFIX + 'PORT', defaultValue: 3000 }, diff --git a/backend/tests/test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example.ts b/backend/tests/test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example.ts index 34f49e64..835a0ed0 100644 --- a/backend/tests/test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example.ts +++ b/backend/tests/test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example.ts @@ -2,9 +2,11 @@ import { LearningObjectExample } from '../learning-object-example'; import { Language } from '../../../../src/entities/content/language'; import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type'; import { loadTestAsset } from '../../../test-utils/load-test-asset'; -import { EducationalGoal, LearningObject, ReturnValue } from '../../../../src/entities/content/learning-object.entity'; +import { LearningObject } from '../../../../src/entities/content/learning-object.entity'; import { Attachment } from '../../../../src/entities/content/attachment.entity'; import { envVars, getEnvVar } from '../../../../src/util/envVars'; +import { EducationalGoal } from '../../../../src/entities/content/educational-goal.entity'; +import { ReturnValue } from '../../../../src/entities/content/return-value.entity'; const ASSETS_PREFIX = 'learning-objects/pn-werkingnotebooks/'; diff --git a/backend/tests/test_assets/content/learning-objects.testdata.ts b/backend/tests/test_assets/content/learning-objects.testdata.ts index e45bdea0..ba2c230a 100644 --- a/backend/tests/test_assets/content/learning-objects.testdata.ts +++ b/backend/tests/test_assets/content/learning-objects.testdata.ts @@ -1,7 +1,8 @@ import { EntityManager } from '@mikro-orm/core'; -import { LearningObject, ReturnValue } from '../../../src/entities/content/learning-object.entity'; +import { LearningObject } from '../../../src/entities/content/learning-object.entity'; import { Language } from '../../../src/entities/content/language'; import { DwengoContentType } from '../../../src/services/learning-objects/processing/content-type'; +import { ReturnValue } from '../../../src/entities/content/return-value.entity'; export function makeTestLearningObjects(em: EntityManager): Array { const returnValue: ReturnValue = new ReturnValue(); From 8efce6bee0ccb1aec60487da04755450a09a18a9 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sun, 23 Mar 2025 11:53:36 +0100 Subject: [PATCH 18/43] refactor(backend): Methoden en parameters --- eslint.config.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/eslint.config.ts b/eslint.config.ts index bbc68665..bc42bd39 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -38,9 +38,15 @@ export default [ '@typescript-eslint/consistent-type-definitions': 'error', '@typescript-eslint/consistent-type-exports': 'off', '@typescript-eslint/consistent-type-imports': 'off', + 'default-param-last': 'off', + '@typescript-eslint/default-param-last': 'error', '@typescript-eslint/explicit-function-return-type': 'warn', + 'max-params': 'off', + '@typescript-eslint/max-params': ['error', { 'max': 6 }], + '@typescript-eslint/member-ordering': 'warn', + '@typescript-eslint/method-signature-style': 'off', // Don't care about TypeScript strict mode. '@typescript-eslint/naming-convention': [ 'warn', { // Enforce that all variables, functions and properties are camelCase @@ -76,6 +82,8 @@ export default [ '@typescript-eslint/no-type-alias': 'off', + '@typescript-eslint/no-unnecessary-parameter-property-assignment': 'error', + '@typescript-eslint/no-unnecessary-type-arguments': 'error', '@typescript-eslint/no-unnecessary-type-assertion': 'error', '@typescript-eslint/no-unnecessary-type-constraint': 'error', @@ -99,8 +107,13 @@ export default [ 'no-use-before-define': 'off', '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/parameter-properties': 'off', + '@typescript-eslint/prefer-function-type': 'error', + '@typescript-eslint/prefer-readonly-parameter-types': 'off', + '@typescript-eslint/prefer-reduce-type-parameter': 'error', + '@typescript-eslint/promise-function-async': 'warn', 'no-await-in-loop': 'warn', @@ -119,7 +132,6 @@ export default [ curly: 'error', 'default-case': 'error', 'default-case-last': 'error', - 'default-param-last': 'error', 'dot-notation': 'warn', eqeqeq: 'error', 'func-names': 'warn', From dd1000c66278121ce5e12cddccbcce58ce472a61 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sun, 23 Mar 2025 12:04:54 +0100 Subject: [PATCH 19/43] refactor(frontend): Algemene linting fouten --- frontend/src/components/MenuBar.vue | 4 ++-- frontend/src/router/index.ts | 6 +++--- frontend/src/services/auth/auth-config-loader.ts | 3 ++- frontend/src/services/auth/auth-service.ts | 11 ++++++----- frontend/src/services/auth/auth-storage.ts | 4 ++-- frontend/src/services/auth/auth.d.ts | 14 +++++++------- frontend/src/views/HomePage.vue | 2 +- frontend/src/views/LoginPage.vue | 6 +++--- frontend/vite.config.ts | 1 - 9 files changed, 26 insertions(+), 25 deletions(-) diff --git a/frontend/src/components/MenuBar.vue b/frontend/src/components/MenuBar.vue index f942babc..95468351 100644 --- a/frontend/src/components/MenuBar.vue +++ b/frontend/src/components/MenuBar.vue @@ -29,11 +29,11 @@ ]); // Logic to change the language of the website to the selected language - const changeLanguage = (langCode: string) => { + function changeLanguage(langCode: string): void { locale.value = langCode; localStorage.setItem("user-lang", langCode); console.log(langCode); - }; + }