From c67baf216c6c12e166b11a061b11ac99a23f52fd Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Sun, 16 Mar 2025 17:15:09 +0100 Subject: [PATCH 001/173] 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 002/173] 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 003/173] 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 004/173] 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 005/173] 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 006/173] 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 007/173] 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 008/173] 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 c31b4713714670b03636abafcfb70579405c9936 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Fri, 21 Mar 2025 22:51:42 +0100 Subject: [PATCH 009/173] feat: questions via student --- backend/src/controllers/students.ts | 108 ++++++++++-------- backend/src/controllers/teachers.ts | 100 ++++++---------- .../src/data/questions/question-repository.ts | 8 ++ backend/src/routes/students.ts | 18 +-- backend/src/routes/teachers.ts | 2 - backend/src/services/students.ts | 33 ++++-- backend/src/services/teachers.ts | 55 +++------ 7 files changed, 153 insertions(+), 171 deletions(-) diff --git a/backend/src/controllers/students.ts b/backend/src/controllers/students.ts index 6c253cff..cde6b5ef 100644 --- a/backend/src/controllers/students.ts +++ b/backend/src/controllers/students.ts @@ -1,53 +1,43 @@ import { Request, Response } from 'express'; import { createStudent, - deleteStudent, + deleteStudent, getAllStudentIds, getAllStudents, getStudent, getStudentAssignments, getStudentClasses, - getStudentGroups, + getStudentGroups, getStudentQuestions, 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 {MISSING_FIELDS_ERROR, MISSING_USERNAME_ERROR, NAME_NOT_FOUND_ERROR} from './users.js'; import { StudentDTO } from '../interfaces/student.js'; -import { getStudentRepository } from '../data/repositories.js'; -import { UserDTO } from '../interfaces/user.js'; -// TODO: accept arguments (full, ...) -// TODO: endpoints + 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(); + const students: StudentDTO[] | string[] = full ? await getAllStudents() : await getAllStudentIds(); if (!students) { - res.status(404).json({ error: `Student not found.` }); + res.status(404).json({ error: `Students not found.` }); return; } - res.status(201).json(students); + res.json({students}); } export async function getStudentHandler(req: Request, res: Response): Promise { const username = req.params.username; if (!username) { - res.status(400).json({ error: 'Missing required field: username' }); + res.status(400).json(MISSING_USERNAME_ERROR); return; } const user = await getStudent(username); if (!user) { - res.status(404).json({ - error: `User with username '${username}' not found.`, - }); + res.status(404).json(NAME_NOT_FOUND_ERROR(username)); return; } @@ -58,9 +48,7 @@ export async function createStudentHandler(req: Request, res: Response) { const userData = req.body as StudentDTO; if (!userData.username || !userData.firstName || !userData.lastName) { - res.status(400).json({ - error: 'Missing required fields: username, firstName, lastName', - }); + res.status(400).json(MISSING_FIELDS_ERROR); return; } @@ -72,15 +60,13 @@ export async function deleteStudentHandler(req: Request, res: Response) { const username = req.params.username; if (!username) { - res.status(400).json({ error: 'Missing required field: username' }); + res.status(400).json(MISSING_USERNAME_ERROR); return; } const deletedUser = await deleteStudent(username); if (!deletedUser) { - res.status(404).json({ - error: `User with username '${username}' not found.`, - }); + res.status(404).json(NAME_NOT_FOUND_ERROR(username)); return; } @@ -88,25 +74,19 @@ export async function deleteStudentHandler(req: Request, res: Response) { } export async function getStudentClassesHandler(req: Request, res: Response): Promise { - try { - const full = req.query.full === 'true'; - const username = req.params.id; + const full = req.query.full === 'true'; + const username = req.params.username; - const classes = await getStudentClasses(username, full); - - res.json({ - classes: classes, - endpoints: { - self: `${req.baseUrl}/${req.params.id}`, - classes: `${req.baseUrl}/${req.params.id}/invitations`, - questions: `${req.baseUrl}/${req.params.id}/assignments`, - students: `${req.baseUrl}/${req.params.id}/students`, - }, - }); - } catch (error) { - console.error('Error fetching learning objects:', error); - res.status(500).json({ error: 'Internal server error' }); + if (!username) { + res.status(400).json(MISSING_USERNAME_ERROR); + return; } + + const classes = await getStudentClasses(username, full); + + res.json({ + classes, + }); } // TODO @@ -115,32 +95,62 @@ export async function getStudentClassesHandler(req: Request, res: Response): Pro // Have this assignment. export async function getStudentAssignmentsHandler(req: Request, res: Response): Promise { const full = req.query.full === 'true'; - const username = req.params.id; + const username = req.params.username; + + if (!username) { + res.status(400).json(MISSING_USERNAME_ERROR); + return; + } const assignments = getStudentAssignments(username, full); res.json({ - assignments: assignments, + assignments, }); } export async function getStudentGroupsHandler(req: Request, res: Response): Promise { const full = req.query.full === 'true'; - const username = req.params.id; + const username = req.params.username; + + if (!username) { + res.status(400).json(MISSING_USERNAME_ERROR); + return; + } const groups = await getStudentGroups(username, full); res.json({ - groups: groups, + groups, }); } export async function getStudentSubmissionsHandler(req: Request, res: Response): Promise { - const username = req.params.id; + const username = req.params.username; + + if (!username) { + res.status(400).json(MISSING_USERNAME_ERROR); + return; + } const submissions = await getStudentSubmissions(username); res.json({ - submissions: submissions, + submissions, }); } + +export async function getStudentQuestionsHandler(req: Request, res: Response): Promise { + const username = req.params.username; + + if (!username) { + res.status(400).json(MISSING_USERNAME_ERROR); + return; + } + + const questions = await getStudentQuestions(username, full); + + res.json({ + questions, + }) +} diff --git a/backend/src/controllers/teachers.ts b/backend/src/controllers/teachers.ts index 52e5e713..fb4c2a0d 100644 --- a/backend/src/controllers/teachers.ts +++ b/backend/src/controllers/teachers.ts @@ -4,49 +4,40 @@ import { deleteTeacher, getAllTeachers, getClassesByTeacher, - getClassIdsByTeacher, - getQuestionIdsByTeacher, - getQuestionsByTeacher, - getStudentIdsByTeacher, getStudentsByTeacher, getTeacher, } from '../services/teachers.js'; 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 {MISSING_FIELDS_ERROR, MISSING_USERNAME_ERROR, NAME_NOT_FOUND_ERROR} from "./users"; 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(); + const teachers: TeacherDTO[] | string[] = await getAllTeachers(full); if (!teachers) { - res.status(404).json({ error: `Teacher not found.` }); + res.status(404).json({ error: `Teachers not found.` }); return; } - res.status(201).json(teachers); + res.json({teachers}); } export async function getTeacherHandler(req: Request, res: Response): Promise { const username = req.params.username; if (!username) { - res.status(400).json({ error: 'Missing required field: username' }); + res.status(400).json(MISSING_USERNAME_ERROR); return; } const user = await getTeacher(username); if (!user) { - res.status(404).json({ - error: `User with username '${username}' not found.`, - }); + res.status(404).json(NAME_NOT_FOUND_ERROR(username)); return; } @@ -57,9 +48,7 @@ export async function createTeacherHandler(req: Request, res: Response) { const userData = req.body as TeacherDTO; if (!userData.username || !userData.firstName || !userData.lastName) { - res.status(400).json({ - error: 'Missing required fields: username, firstName, lastName', - }); + res.status(400).json(MISSING_FIELDS_ERROR); return; } @@ -71,15 +60,13 @@ export async function deleteTeacherHandler(req: Request, res: Response) { const username = req.params.username; if (!username) { - res.status(400).json({ error: 'Missing required field: username' }); + res.status(400).json(MISSING_USERNAME_ERROR); return; } const deletedUser = await deleteTeacher(username); if (!deletedUser) { - res.status(404).json({ - error: `User with username '${username}' not found.`, - }); + res.status(404).json(NAME_NOT_FOUND_ERROR(username)); return; } @@ -87,58 +74,43 @@ export async function deleteTeacherHandler(req: Request, res: Response) { } export async function getTeacherClassHandler(req: Request, res: Response): Promise { - try { - const username = req.params.username as string; - const full = req.query.full === 'true'; + const username = req.params.username as string; + const full = req.query.full === 'true'; - if (!username) { - res.status(400).json({ error: 'Missing required field: username' }); - return; - } - - const classes: ClassDTO[] | string[] = full ? await getClassesByTeacher(username) : await getClassIdsByTeacher(username); - - res.status(201).json(classes); - } catch (error) { - console.error('Error fetching classes by teacher:', error); - res.status(500).json({ error: 'Internal server error' }); + if (!username) { + res.status(400).json(MISSING_USERNAME_ERROR); + return; } + + const classes: ClassDTO[] | string[] = await getClassesByTeacher(username, full); + + res.status(201).json(classes); } export async function getTeacherStudentHandler(req: Request, res: Response): Promise { - try { - const username = req.params.username as string; - const full = req.query.full === 'true'; + const username = req.params.username as string; + const full = req.query.full === 'true'; - if (!username) { - res.status(400).json({ error: 'Missing required field: username' }); - return; - } - - const students: StudentDTO[] | string[] = full ? await getStudentsByTeacher(username) : await getStudentIdsByTeacher(username); - - res.status(201).json(students); - } catch (error) { - console.error('Error fetching students by teacher:', error); - res.status(500).json({ error: 'Internal server error' }); + if (!username) { + res.status(400).json(MISSING_USERNAME_ERROR); + return; } + + const students: StudentDTO[] | string[] = await getStudentsByTeacher(username, full); + + res.json({students}); } export async function getTeacherQuestionHandler(req: Request, res: Response): Promise { - try { - const username = req.params.username as string; - const full = req.query.full === 'true'; + const username = req.params.username as string; + const full = req.query.full === 'true'; - if (!username) { - res.status(400).json({ error: 'Missing required field: username' }); - return; - } - - const questions: QuestionDTO[] | QuestionId[] = full ? await getQuestionsByTeacher(username) : await getQuestionIdsByTeacher(username); - - res.status(201).json(questions); - } catch (error) { - console.error('Error fetching questions by teacher:', error); - res.status(500).json({ error: 'Internal server error' }); + if (!username) { + res.status(400).json(MISSING_USERNAME_ERROR); + return; } + + const questions: QuestionDTO[] | QuestionId[] = await getQuestionsByTeacher(username, full); + + res.json({questions}); } diff --git a/backend/src/data/questions/question-repository.ts b/backend/src/data/questions/question-repository.ts index 9207e1dd..8789c2e9 100644 --- a/backend/src/data/questions/question-repository.ts +++ b/backend/src/data/questions/question-repository.ts @@ -54,4 +54,12 @@ export class QuestionRepository extends DwengoEntityRepository { orderBy: { timestamp: 'ASC' }, }); } + + public findAllByAuthor(author: Student): Promise { + return this.findAll({ + where: { author }, + orderBy: { timestamp: 'DESC' }, // new to old + }); + } + } diff --git a/backend/src/routes/students.ts b/backend/src/routes/students.ts index 7ed7a666..77775b8f 100644 --- a/backend/src/routes/students.ts +++ b/backend/src/routes/students.ts @@ -6,7 +6,7 @@ import { getStudentAssignmentsHandler, getStudentClassesHandler, getStudentGroupsHandler, - getStudentHandler, + getStudentHandler, getStudentQuestionsHandler, getStudentSubmissionsHandler, } from '../controllers/students.js'; import { getStudentGroups } from '../services/students.js'; @@ -17,30 +17,24 @@ router.get('/', getAllStudentsHandler); router.post('/', createStudentHandler); -router.delete('/', deleteStudentHandler); - router.delete('/:username', deleteStudentHandler); // Information about a student's profile router.get('/:username', getStudentHandler); // The list of classes a student is in -router.get('/:id/classes', getStudentClassesHandler); +router.get('/:username/classes', getStudentClassesHandler); // The list of submissions a student has made -router.get('/:id/submissions', getStudentSubmissionsHandler); +router.get('/:username/submissions', getStudentSubmissionsHandler); // The list of assignments a student has -router.get('/:id/assignments', getStudentAssignmentsHandler); +router.get('/:username/assignments', getStudentAssignmentsHandler); // The list of groups a student is in -router.get('/:id/groups', getStudentGroupsHandler); +router.get('/:username/groups', getStudentGroupsHandler); // A list of questions a user has created -router.get('/:id/questions', (req, res) => { - res.json({ - questions: ['0'], - }); -}); +router.get('/:username/questions', getStudentQuestionsHandler); export default router; diff --git a/backend/src/routes/teachers.ts b/backend/src/routes/teachers.ts index c04e1575..8e7f709d 100644 --- a/backend/src/routes/teachers.ts +++ b/backend/src/routes/teachers.ts @@ -15,8 +15,6 @@ router.get('/', getAllTeachersHandler); router.post('/', createTeacherHandler); -router.delete('/', deleteTeacherHandler); - router.get('/:username', getTeacherHandler); router.delete('/:username', deleteTeacherHandler); diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts index 5099a18d..70bed549 100644 --- a/backend/src/services/students.ts +++ b/backend/src/services/students.ts @@ -1,13 +1,17 @@ -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 { + getClassRepository, + getGroupRepository, + getQuestionRepository, + getStudentRepository, + getSubmissionRepository +} from '../data/repositories.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 {mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId} from "../interfaces/question"; export async function getAllStudents(): Promise { const studentRepository = getStudentRepository(); @@ -88,9 +92,7 @@ export async function getStudentAssignments(username: string, full: boolean): Pr const classRepository = getClassRepository(); const classes = await classRepository.findByStudent(student); - const assignments = (await Promise.all(classes.map(async (cls) => await getAllAssignments(cls.classId!, full)))).flat(); - - return assignments; + return (await Promise.all(classes.map(async (cls) => await getAllAssignments(cls.classId!, full)))).flat(); } export async function getStudentGroups(username: string, full: boolean): Promise { @@ -124,3 +126,20 @@ export async function getStudentSubmissions(username: string): Promise { + const studentRepository = getStudentRepository(); + const student = await studentRepository.findByUsername(username); + + if (!student) { + return []; + } + + const questionRepository = getQuestionRepository(); + const questions = await questionRepository.findAllByAuthor(student); + + if (full) + return questions.map(mapToQuestionDTO) + + return questions.map(mapToQuestionId); +} diff --git a/backend/src/services/teachers.ts b/backend/src/services/teachers.ts index f4dbedfe..5b0d8144 100644 --- a/backend/src/services/teachers.ts +++ b/backend/src/services/teachers.ts @@ -5,23 +5,17 @@ import { getStudentRepository, getTeacherRepository, } from '../data/repositories.js'; -import { Teacher } from '../entities/users/teacher.entity.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 { mapToQuestionDTO, mapToQuestionId, QuestionDTO } from '../interfaces/question.js'; import { mapToTeacher, mapToTeacherDTO, TeacherDTO } from '../interfaces/teacher.js'; -export async function getAllTeachers(): Promise { +export async function getAllTeachers(full: boolean): Promise { const teacherRepository = getTeacherRepository(); const users = await teacherRepository.findAll(); - return users.map(mapToTeacherDTO); -} -export async function getAllTeacherIds(): Promise { - const users = await getAllTeachers(); + if (full) + return users.map(mapToTeacherDTO); return users.map((user) => user.username); } @@ -64,7 +58,7 @@ export async function deleteTeacher(username: string): Promise { +async function fetchClassesByTeacher(username: string): Promise { const teacherRepository = getTeacherRepository(); const teacher = await teacherRepository.findByUsername(username); if (!teacher) { @@ -76,31 +70,24 @@ export async function fetchClassesByTeacher(username: string): Promise { - return await fetchClassesByTeacher(username); -} - -export async function getClassIdsByTeacher(username: string): Promise { +export async function getClassesByTeacher(username: string, full: boolean): Promise { const classes = await fetchClassesByTeacher(username); + + if (full) + return classes; return classes.map((cls) => cls.id); } -export async function fetchStudentsByTeacher(username: string) { - const classes = await getClassIdsByTeacher(username); +export async function getStudentsByTeacher(username: string, full: boolean) { + const classes = await getClassesByTeacher(username, false); - return (await Promise.all(classes.map(async (id) => getClassStudents(id)))).flat(); -} - -export async function getStudentsByTeacher(username: string): Promise { - return await fetchStudentsByTeacher(username); -} - -export async function getStudentIdsByTeacher(username: string): Promise { - const students = await fetchStudentsByTeacher(username); + const students = (await Promise.all(classes.map(async (id) => getClassStudents(id)))).flat(); + if (full) + return students return students.map((student) => student.username); } -export async function fetchTeacherQuestions(username: string): Promise { +export async function getTeacherQuestions(username: string, full: boolean): Promise { const teacherRepository = getTeacherRepository(); const teacher = await teacherRepository.findByUsername(username); if (!teacher) { @@ -115,15 +102,9 @@ export async function fetchTeacherQuestions(username: string): Promise { - return await fetchTeacherQuestions(username); -} - -export async function getQuestionIdsByTeacher(username: string): Promise { - const questions = await fetchTeacherQuestions(username); + if (full) + return questions.map(mapToQuestionDTO); return questions.map(mapToQuestionId); } + From bfb9598fa11fc9344a9e1de5dcd2a73d3094e2f8 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Fri, 21 Mar 2025 22:52:09 +0100 Subject: [PATCH 010/173] feat: gemeenschappelijke errors in aparte file --- backend/src/controllers/users.ts | 92 ++------------------------------ 1 file changed, 4 insertions(+), 88 deletions(-) diff --git a/backend/src/controllers/users.ts b/backend/src/controllers/users.ts index 850c6549..16f0e55d 100644 --- a/backend/src/controllers/users.ts +++ b/backend/src/controllers/users.ts @@ -1,91 +1,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'; +export const MISSING_USERNAME_ERROR = { error: 'Missing required field: username' }; -export async function getAllUsersHandler(req: Request, res: Response, service: UserService): Promise { - try { - const full = req.query.full === 'true'; - - const users: UserDTO[] | string[] = full ? await service.getAllUsers() : await service.getAllUserIds(); - - if (!users) { - res.status(404).json({ error: `Users not found.` }); - return; - } - - res.status(201).json(users); - } catch (error) { - console.error('❌ Error fetching users:', error); - res.status(500).json({ error: 'Internal server error' }); - } +export function NAME_NOT_FOUND_ERROR(username: string){ + return {error: `User with username '${username}' not found.`}; } -export async function getUserHandler(req: Request, res: Response, service: UserService): Promise { - try { - const username = req.params.username as string; - - if (!username) { - res.status(400).json({ error: 'Missing required field: username' }); - return; - } - - const user = await service.getUserByUsername(username); - - if (!user) { - res.status(404).json({ - error: `User with username '${username}' not found.`, - }); - return; - } - - res.status(201).json(user); - } catch (error) { - console.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); - const userData = req.body as UserDTO; - - if (!userData.username || !userData.firstName || !userData.lastName) { - res.status(400).json({ - error: 'Missing required fields: username, firstName, lastName', - }); - return; - } - - const newUser = await service.createUser(userData, UserClass); - res.status(201).json(newUser); - } catch (error) { - console.error('❌ Error creating user:', error); - res.status(500).json({ error: 'Internal server error' }); - } -} - -export async function deleteUserHandler(req: Request, res: Response, service: UserService) { - try { - const username = req.params.username; - - if (!username) { - res.status(400).json({ error: 'Missing required field: username' }); - return; - } - - const deletedUser = await service.deleteUser(username); - if (!deletedUser) { - res.status(404).json({ - error: `User with username '${username}' not found.`, - }); - return; - } - - res.status(200).json(deletedUser); - } catch (error) { - console.error('❌ Error deleting user:', error); - res.status(500).json({ error: 'Internal server error' }); - } -} +export const MISSING_FIELDS_ERROR = { error: 'Missing required fields: username, firstName, lastName'} From 52364d717c559be58828d9da872dde58225a8f8d Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Fri, 21 Mar 2025 23:23:33 +0100 Subject: [PATCH 011/173] feat: teacher en student frontend controllers --- backend/src/routes/router.ts | 2 + frontend/config.ts | 1 + frontend/src/controllers/base-controller.ts | 72 +++++++++++++++++++ frontend/src/controllers/controllers.ts | 16 +++++ .../src/controllers/student-controller.ts | 44 ++++++++++++ .../src/controllers/teacher-controller.ts | 37 ++++++++++ 6 files changed, 172 insertions(+) create mode 100644 frontend/config.ts create mode 100644 frontend/src/controllers/base-controller.ts create mode 100644 frontend/src/controllers/controllers.ts create mode 100644 frontend/src/controllers/student-controller.ts create mode 100644 frontend/src/controllers/teacher-controller.ts diff --git a/backend/src/routes/router.ts b/backend/src/routes/router.ts index 639857a7..e05f9909 100644 --- a/backend/src/routes/router.ts +++ b/backend/src/routes/router.ts @@ -1,5 +1,6 @@ import { Response, Router } from 'express'; import studentRouter from './students.js'; +import teacherRouter from './teachers.js'; import groupRouter from './groups.js'; import assignmentRouter from './assignments.js'; import submissionRouter from './submissions.js'; @@ -22,6 +23,7 @@ router.get('/', (_, res: Response) => { }); router.use('/student', studentRouter /* #swagger.tags = ['Student'] */); +router.use('/teacher', teacherRouter /* #swagger.tags = ['Teacher'] */); router.use('/group', groupRouter /* #swagger.tags = ['Group'] */); router.use('/assignment', assignmentRouter /* #swagger.tags = ['Assignment'] */); router.use('/submission', submissionRouter /* #swagger.tags = ['Submission'] */); diff --git a/frontend/config.ts b/frontend/config.ts new file mode 100644 index 00000000..656687fd --- /dev/null +++ b/frontend/config.ts @@ -0,0 +1 @@ +export const API_BASE = "http://localhost:3000/api"; diff --git a/frontend/src/controllers/base-controller.ts b/frontend/src/controllers/base-controller.ts new file mode 100644 index 00000000..8005a32e --- /dev/null +++ b/frontend/src/controllers/base-controller.ts @@ -0,0 +1,72 @@ +import {API_BASE} from "../../config.ts"; + +export class BaseController { + protected baseUrl: string; + + constructor(basePath: string) { + this.baseUrl = `${API_BASE}/${basePath}`; + } + + protected async get(path: string, queryParams?: Record): Promise { + let url = `${this.baseUrl}${path}`; + if (queryParams) { + const query = new URLSearchParams(); + Object.entries(queryParams).forEach(([key, value]) => { + if (value !== undefined && value !== null) query.append(key, value.toString()); + }); + url += `?${query.toString()}`; + } + + const res = await fetch(url); + if (!res.ok) { + const errorData = await res.json().catch(() => ({})); + throw new Error(errorData?.error || `Error ${res.status}: ${res.statusText}`); + } + + return res.json(); + } + + protected async post(path: string, body: unknown): Promise { + const res = await fetch(`${this.baseUrl}${path}`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + + if (!res.ok) { + const errorData = await res.json().catch(() => ({})); + throw new Error(errorData?.error || `Error ${res.status}: ${res.statusText}`); + } + + return res.json(); + } + + protected async delete(path: string): Promise { + const res = await fetch(`${this.baseUrl}${path}`, { + method: "DELETE", + }); + + if (!res.ok) { + const errorData = await res.json().catch(() => ({})); + throw new Error(errorData?.error || `Error ${res.status}: ${res.statusText}`); + } + + return res.json(); + } + + protected async put(path: string, body: unknown): Promise { + const res = await fetch(`${this.baseUrl}${path}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + + if (!res.ok) { + const errorData = await res.json().catch(() => ({})); + throw new Error(errorData?.error || `Error ${res.status}: ${res.statusText}`); + } + + return res.json(); + } + +} diff --git a/frontend/src/controllers/controllers.ts b/frontend/src/controllers/controllers.ts new file mode 100644 index 00000000..61bb09d6 --- /dev/null +++ b/frontend/src/controllers/controllers.ts @@ -0,0 +1,16 @@ +import {StudentController} from "@/controllers/student-controller.ts"; +import {TeacherController} from "@/controllers/teacher-controller.ts"; + +export function controllerGetter(Factory: new () => T): () => T { + let instance: T | undefined; + + return (): T => { + if (!instance) { + instance = new Factory(); + } + return instance; + }; +} + +export const getStudentController = controllerGetter(StudentController); +export const getTeacherController = controllerGetter(TeacherController); diff --git a/frontend/src/controllers/student-controller.ts b/frontend/src/controllers/student-controller.ts new file mode 100644 index 00000000..e74bd74c --- /dev/null +++ b/frontend/src/controllers/student-controller.ts @@ -0,0 +1,44 @@ +import {BaseController} from "@/controllers/base-controller.ts"; + +export class StudentController extends BaseController { + constructor() { + super("students"); + } + + getAll(full = true) { + return this.get<{ students: any[] }>("/", { full }); + } + + getByUsername(username: string) { + return this.get(`/${username}`); + } + + createStudent(data: any) { + return this.post("/", data); + } + + deleteStudent(username: string) { + return this.delete(`/${username}`); + } + + getClasses(username: string, full = true) { + return this.get<{ classes: any[] }>(`/${username}/classes`, { full }); + } + + getAssignments(username: string, full = true) { + return this.get<{ assignments: any[] }>(`/${username}/assignments`, { full }); + } + + getGroups(username: string, full = true) { + return this.get<{ groups: any[] }>(`/${username}/groups`, { full }); + } + + getSubmissions(username: string) { + return this.get<{ submissions: any[] }>(`/${username}/submissions`); + } + + getQuestions(username: string, full = true) { + return this.get<{ questions: any[] }>(`/${username}/questions`, { full }); + } + +} diff --git a/frontend/src/controllers/teacher-controller.ts b/frontend/src/controllers/teacher-controller.ts new file mode 100644 index 00000000..d3273ac1 --- /dev/null +++ b/frontend/src/controllers/teacher-controller.ts @@ -0,0 +1,37 @@ +import {BaseController} from "@/controllers/base-controller.ts"; + +export class TeacherController extends BaseController { + constructor() { + super("teachers"); + } + + getAll(full = false) { + return this.get<{ teachers: any[] }>("/", { full }); + } + + getByUsername(username: string) { + return this.get(`/${username}`); + } + + createTeacher(data: any) { + return this.post("/", data); + } + + deleteTeacher(username: string) { + return this.delete(`/${username}`); + } + + getClasses(username: string, full = false) { + return this.get(`/${username}/classes`, { full }); + } + + getStudents(username: string, full = false) { + return this.get<{ students: any[] }>(`/${username}/students`, { full }); + } + + getQuestions(username: string, full = false) { + return this.get<{ questions: any[] }>(`/${username}/questions`, { full }); + } + + // getInvitations(id: string) {return this.get<{ invitations: string[] }>(`/${id}/invitations`);} +} From fc5a40ba40e25b7c08fba2535061a8eb8b0980b2 Mon Sep 17 00:00:00 2001 From: Lint Action Date: Fri, 21 Mar 2025 22:26:24 +0000 Subject: [PATCH 012/173] style: fix linting issues met ESLint --- backend/src/data/questions/question-repository.ts | 2 +- backend/src/services/students.ts | 2 +- backend/src/services/teachers.ts | 8 ++++---- frontend/src/controllers/base-controller.ts | 2 +- frontend/src/controllers/teacher-controller.ts | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/src/data/questions/question-repository.ts b/backend/src/data/questions/question-repository.ts index 8789c2e9..78d3a6ef 100644 --- a/backend/src/data/questions/question-repository.ts +++ b/backend/src/data/questions/question-repository.ts @@ -58,7 +58,7 @@ export class QuestionRepository extends DwengoEntityRepository { public findAllByAuthor(author: Student): Promise { return this.findAll({ where: { author }, - orderBy: { timestamp: 'DESC' }, // new to old + orderBy: { timestamp: 'DESC' }, // New to old }); } diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts index 70bed549..f7a7cbcc 100644 --- a/backend/src/services/students.ts +++ b/backend/src/services/students.ts @@ -139,7 +139,7 @@ export async function getStudentQuestions(username: string, full: boolean): Prom const questions = await questionRepository.findAllByAuthor(student); if (full) - return questions.map(mapToQuestionDTO) + {return questions.map(mapToQuestionDTO)} return questions.map(mapToQuestionId); } diff --git a/backend/src/services/teachers.ts b/backend/src/services/teachers.ts index 5b0d8144..5e1ea2cb 100644 --- a/backend/src/services/teachers.ts +++ b/backend/src/services/teachers.ts @@ -15,7 +15,7 @@ export async function getAllTeachers(full: boolean): Promise { const users = await teacherRepository.findAll(); if (full) - return users.map(mapToTeacherDTO); + {return users.map(mapToTeacherDTO);} return users.map((user) => user.username); } @@ -74,7 +74,7 @@ export async function getClassesByTeacher(username: string, full: boolean): Prom const classes = await fetchClassesByTeacher(username); if (full) - return classes; + {return classes;} return classes.map((cls) => cls.id); } @@ -83,7 +83,7 @@ export async function getStudentsByTeacher(username: string, full: boolean) { const students = (await Promise.all(classes.map(async (id) => getClassStudents(id)))).flat(); if (full) - return students + {return students} return students.map((student) => student.username); } @@ -103,7 +103,7 @@ export async function getTeacherQuestions(username: string, full: boolean): Prom const questions = await questionRepository.findAllByLearningObjects(learningObjects); if (full) - return questions.map(mapToQuestionDTO); + {return questions.map(mapToQuestionDTO);} return questions.map(mapToQuestionId); } diff --git a/frontend/src/controllers/base-controller.ts b/frontend/src/controllers/base-controller.ts index 8005a32e..e14dba23 100644 --- a/frontend/src/controllers/base-controller.ts +++ b/frontend/src/controllers/base-controller.ts @@ -12,7 +12,7 @@ export class BaseController { if (queryParams) { const query = new URLSearchParams(); Object.entries(queryParams).forEach(([key, value]) => { - if (value !== undefined && value !== null) query.append(key, value.toString()); + if (value !== undefined && value !== null) {query.append(key, value.toString());} }); url += `?${query.toString()}`; } diff --git a/frontend/src/controllers/teacher-controller.ts b/frontend/src/controllers/teacher-controller.ts index d3273ac1..fea08419 100644 --- a/frontend/src/controllers/teacher-controller.ts +++ b/frontend/src/controllers/teacher-controller.ts @@ -33,5 +33,5 @@ export class TeacherController extends BaseController { return this.get<{ questions: any[] }>(`/${username}/questions`, { full }); } - // getInvitations(id: string) {return this.get<{ invitations: string[] }>(`/${id}/invitations`);} + // GetInvitations(id: string) {return this.get<{ invitations: string[] }>(`/${id}/invitations`);} } From 9b0c0c9889f115730e424ca4a95bcb65ad807f45 Mon Sep 17 00:00:00 2001 From: Lint Action Date: Fri, 21 Mar 2025 22:26:28 +0000 Subject: [PATCH 013/173] style: fix linting issues met Prettier --- backend/src/controllers/students.ts | 13 ++++++----- backend/src/controllers/teachers.ts | 17 ++++---------- backend/src/controllers/users.ts | 6 ++--- .../src/data/questions/question-repository.ts | 1 - backend/src/routes/students.ts | 3 ++- backend/src/services/students.ts | 9 ++++---- backend/src/services/teachers.ts | 23 +++++++++++-------- frontend/src/controllers/base-controller.ts | 7 +++--- frontend/src/controllers/controllers.ts | 4 ++-- .../src/controllers/student-controller.ts | 3 +-- .../src/controllers/teacher-controller.ts | 2 +- 11 files changed, 43 insertions(+), 45 deletions(-) diff --git a/backend/src/controllers/students.ts b/backend/src/controllers/students.ts index cde6b5ef..771aacbe 100644 --- a/backend/src/controllers/students.ts +++ b/backend/src/controllers/students.ts @@ -1,18 +1,19 @@ import { Request, Response } from 'express'; import { createStudent, - deleteStudent, getAllStudentIds, + deleteStudent, + getAllStudentIds, getAllStudents, getStudent, getStudentAssignments, getStudentClasses, - getStudentGroups, getStudentQuestions, + getStudentGroups, + getStudentQuestions, getStudentSubmissions, } from '../services/students.js'; -import {MISSING_FIELDS_ERROR, MISSING_USERNAME_ERROR, NAME_NOT_FOUND_ERROR} from './users.js'; +import { MISSING_FIELDS_ERROR, MISSING_USERNAME_ERROR, NAME_NOT_FOUND_ERROR } from './users.js'; import { StudentDTO } from '../interfaces/student.js'; - export async function getAllStudentsHandler(req: Request, res: Response): Promise { const full = req.query.full === 'true'; @@ -23,7 +24,7 @@ export async function getAllStudentsHandler(req: Request, res: Response): Promis return; } - res.json({students}); + res.json({ students }); } export async function getStudentHandler(req: Request, res: Response): Promise { @@ -152,5 +153,5 @@ export async function getStudentQuestionsHandler(req: Request, res: Response): P res.json({ questions, - }) + }); } diff --git a/backend/src/controllers/teachers.ts b/backend/src/controllers/teachers.ts index fb4c2a0d..f1a557b0 100644 --- a/backend/src/controllers/teachers.ts +++ b/backend/src/controllers/teachers.ts @@ -1,17 +1,10 @@ import { Request, Response } from 'express'; -import { - createTeacher, - deleteTeacher, - getAllTeachers, - getClassesByTeacher, - getStudentsByTeacher, - getTeacher, -} from '../services/teachers.js'; +import { createTeacher, deleteTeacher, getAllTeachers, getClassesByTeacher, getStudentsByTeacher, getTeacher } from '../services/teachers.js'; import { ClassDTO } from '../interfaces/class.js'; import { StudentDTO } from '../interfaces/student.js'; import { QuestionDTO, QuestionId } from '../interfaces/question.js'; import { TeacherDTO } from '../interfaces/teacher.js'; -import {MISSING_FIELDS_ERROR, MISSING_USERNAME_ERROR, NAME_NOT_FOUND_ERROR} from "./users"; +import { MISSING_FIELDS_ERROR, MISSING_USERNAME_ERROR, NAME_NOT_FOUND_ERROR } from './users'; export async function getAllTeachersHandler(req: Request, res: Response): Promise { const full = req.query.full === 'true'; @@ -23,7 +16,7 @@ export async function getAllTeachersHandler(req: Request, res: Response): Promis return; } - res.json({teachers}); + res.json({ teachers }); } export async function getTeacherHandler(req: Request, res: Response): Promise { @@ -98,7 +91,7 @@ export async function getTeacherStudentHandler(req: Request, res: Response): Pro const students: StudentDTO[] | string[] = await getStudentsByTeacher(username, full); - res.json({students}); + res.json({ students }); } export async function getTeacherQuestionHandler(req: Request, res: Response): Promise { @@ -112,5 +105,5 @@ export async function getTeacherQuestionHandler(req: Request, res: Response): Pr const questions: QuestionDTO[] | QuestionId[] = await getQuestionsByTeacher(username, full); - res.json({questions}); + res.json({ questions }); } diff --git a/backend/src/controllers/users.ts b/backend/src/controllers/users.ts index 16f0e55d..d2a6ce7c 100644 --- a/backend/src/controllers/users.ts +++ b/backend/src/controllers/users.ts @@ -1,7 +1,7 @@ export const MISSING_USERNAME_ERROR = { error: 'Missing required field: username' }; -export function NAME_NOT_FOUND_ERROR(username: string){ - return {error: `User with username '${username}' not found.`}; +export function NAME_NOT_FOUND_ERROR(username: string) { + return { error: `User with username '${username}' not found.` }; } -export const MISSING_FIELDS_ERROR = { error: 'Missing required fields: username, firstName, lastName'} +export const MISSING_FIELDS_ERROR = { error: 'Missing required fields: username, firstName, lastName' }; diff --git a/backend/src/data/questions/question-repository.ts b/backend/src/data/questions/question-repository.ts index 78d3a6ef..deba7aad 100644 --- a/backend/src/data/questions/question-repository.ts +++ b/backend/src/data/questions/question-repository.ts @@ -61,5 +61,4 @@ export class QuestionRepository extends DwengoEntityRepository { orderBy: { timestamp: 'DESC' }, // New to old }); } - } diff --git a/backend/src/routes/students.ts b/backend/src/routes/students.ts index 77775b8f..388f51a0 100644 --- a/backend/src/routes/students.ts +++ b/backend/src/routes/students.ts @@ -6,7 +6,8 @@ import { getStudentAssignmentsHandler, getStudentClassesHandler, getStudentGroupsHandler, - getStudentHandler, getStudentQuestionsHandler, + getStudentHandler, + getStudentQuestionsHandler, getStudentSubmissionsHandler, } from '../controllers/students.js'; import { getStudentGroups } from '../services/students.js'; diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts index f7a7cbcc..a3df888c 100644 --- a/backend/src/services/students.ts +++ b/backend/src/services/students.ts @@ -3,7 +3,7 @@ import { getGroupRepository, getQuestionRepository, getStudentRepository, - getSubmissionRepository + getSubmissionRepository, } from '../data/repositories.js'; import { AssignmentDTO } from '../interfaces/assignment.js'; import { ClassDTO, mapToClassDTO } from '../interfaces/class.js'; @@ -11,7 +11,7 @@ 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 {mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId} from "../interfaces/question"; +import { mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId } from '../interfaces/question'; export async function getAllStudents(): Promise { const studentRepository = getStudentRepository(); @@ -138,8 +138,9 @@ export async function getStudentQuestions(username: string, full: boolean): Prom const questionRepository = getQuestionRepository(); const questions = await questionRepository.findAllByAuthor(student); - if (full) - {return questions.map(mapToQuestionDTO)} + if (full) { + return questions.map(mapToQuestionDTO); + } return questions.map(mapToQuestionId); } diff --git a/backend/src/services/teachers.ts b/backend/src/services/teachers.ts index 5e1ea2cb..cfe274c9 100644 --- a/backend/src/services/teachers.ts +++ b/backend/src/services/teachers.ts @@ -14,8 +14,9 @@ export async function getAllTeachers(full: boolean): Promise { const teacherRepository = getTeacherRepository(); const users = await teacherRepository.findAll(); - if (full) - {return users.map(mapToTeacherDTO);} + if (full) { + return users.map(mapToTeacherDTO); + } return users.map((user) => user.username); } @@ -73,17 +74,19 @@ async function fetchClassesByTeacher(username: string): Promise { export async function getClassesByTeacher(username: string, full: boolean): Promise { const classes = await fetchClassesByTeacher(username); - if (full) - {return classes;} + if (full) { + return classes; + } return classes.map((cls) => cls.id); } export async function getStudentsByTeacher(username: string, full: boolean) { const classes = await getClassesByTeacher(username, false); - const students = (await Promise.all(classes.map(async (id) => getClassStudents(id)))).flat(); - if (full) - {return students} + const students = (await Promise.all(classes.map(async (id) => getClassStudents(id)))).flat(); + if (full) { + return students; + } return students.map((student) => student.username); } @@ -102,9 +105,9 @@ export async function getTeacherQuestions(username: string, full: boolean): Prom const questionRepository = getQuestionRepository(); const questions = await questionRepository.findAllByLearningObjects(learningObjects); - if (full) - {return questions.map(mapToQuestionDTO);} + if (full) { + return questions.map(mapToQuestionDTO); + } return questions.map(mapToQuestionId); } - diff --git a/frontend/src/controllers/base-controller.ts b/frontend/src/controllers/base-controller.ts index e14dba23..e422b4be 100644 --- a/frontend/src/controllers/base-controller.ts +++ b/frontend/src/controllers/base-controller.ts @@ -1,4 +1,4 @@ -import {API_BASE} from "../../config.ts"; +import { API_BASE } from "../../config.ts"; export class BaseController { protected baseUrl: string; @@ -12,7 +12,9 @@ export class BaseController { if (queryParams) { const query = new URLSearchParams(); Object.entries(queryParams).forEach(([key, value]) => { - if (value !== undefined && value !== null) {query.append(key, value.toString());} + if (value !== undefined && value !== null) { + query.append(key, value.toString()); + } }); url += `?${query.toString()}`; } @@ -68,5 +70,4 @@ export class BaseController { return res.json(); } - } diff --git a/frontend/src/controllers/controllers.ts b/frontend/src/controllers/controllers.ts index 61bb09d6..9907c745 100644 --- a/frontend/src/controllers/controllers.ts +++ b/frontend/src/controllers/controllers.ts @@ -1,5 +1,5 @@ -import {StudentController} from "@/controllers/student-controller.ts"; -import {TeacherController} from "@/controllers/teacher-controller.ts"; +import { StudentController } from "@/controllers/student-controller.ts"; +import { TeacherController } from "@/controllers/teacher-controller.ts"; export function controllerGetter(Factory: new () => T): () => T { let instance: T | undefined; diff --git a/frontend/src/controllers/student-controller.ts b/frontend/src/controllers/student-controller.ts index e74bd74c..38cfef89 100644 --- a/frontend/src/controllers/student-controller.ts +++ b/frontend/src/controllers/student-controller.ts @@ -1,4 +1,4 @@ -import {BaseController} from "@/controllers/base-controller.ts"; +import { BaseController } from "@/controllers/base-controller.ts"; export class StudentController extends BaseController { constructor() { @@ -40,5 +40,4 @@ export class StudentController extends BaseController { getQuestions(username: string, full = true) { return this.get<{ questions: any[] }>(`/${username}/questions`, { full }); } - } diff --git a/frontend/src/controllers/teacher-controller.ts b/frontend/src/controllers/teacher-controller.ts index fea08419..e4f34027 100644 --- a/frontend/src/controllers/teacher-controller.ts +++ b/frontend/src/controllers/teacher-controller.ts @@ -1,4 +1,4 @@ -import {BaseController} from "@/controllers/base-controller.ts"; +import { BaseController } from "@/controllers/base-controller.ts"; export class TeacherController extends BaseController { constructor() { From 8b0fc4263f3641f45dbfa66ee3464aef4cabbe94 Mon Sep 17 00:00:00 2001 From: Gerald Schmittinger Date: Sat, 22 Mar 2025 16:16:34 +0100 Subject: [PATCH 014/173] feat(frontend): Skelet voor de implementatie van views & services voor leerpaden aangemaakt. --- frontend/src/components/LearningPath.vue | 7 - frontend/src/router/index.ts | 7 + .../src/services/learning-paths/language.ts | 186 ++++++++++++++++++ .../learning-paths/learning-object.ts | 37 ++++ .../learning-paths/learning-path-service.ts | 0 .../services/learning-paths/learning-path.ts | 40 ++++ .../views/learning-paths/LearningPathPage.vue | 20 ++ 7 files changed, 290 insertions(+), 7 deletions(-) delete mode 100644 frontend/src/components/LearningPath.vue create mode 100644 frontend/src/services/learning-paths/language.ts create mode 100644 frontend/src/services/learning-paths/learning-object.ts create mode 100644 frontend/src/services/learning-paths/learning-path-service.ts create mode 100644 frontend/src/services/learning-paths/learning-path.ts create mode 100644 frontend/src/views/learning-paths/LearningPathPage.vue diff --git a/frontend/src/components/LearningPath.vue b/frontend/src/components/LearningPath.vue deleted file mode 100644 index 1a35a59f..00000000 --- a/frontend/src/components/LearningPath.vue +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 5f2c4624..be11c0df 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -13,6 +13,7 @@ import UserDiscussions from "@/views/discussions/UserDiscussions.vue"; import UserClasses from "@/views/classes/UserClasses.vue"; import UserAssignments from "@/views/classes/UserAssignments.vue"; import authState from "@/services/auth/auth-service.ts"; +import LearningPathPage from "@/views/learning-paths/LearningPathPage.vue"; const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), @@ -99,6 +100,12 @@ const router = createRouter({ component: SingleDiscussion, meta: { requiresAuth: true }, }, + { + path: "/learningPath/:hruid/:language", + name: "LearningPath", + component: LearningPathPage, + meta: { requiresAuth: false } + }, { path: "/:catchAll(.*)", name: "NotFound", diff --git a/frontend/src/services/learning-paths/language.ts b/frontend/src/services/learning-paths/language.ts new file mode 100644 index 00000000..d7687331 --- /dev/null +++ b/frontend/src/services/learning-paths/language.ts @@ -0,0 +1,186 @@ +export enum Language { + Afar = 'aa', + Abkhazian = 'ab', + Afrikaans = 'af', + Akan = 'ak', + Albanian = 'sq', + Amharic = 'am', + Arabic = 'ar', + Aragonese = 'an', + Armenian = 'hy', + Assamese = 'as', + Avaric = 'av', + Avestan = 'ae', + Aymara = 'ay', + Azerbaijani = 'az', + Bashkir = 'ba', + Bambara = 'bm', + Basque = 'eu', + Belarusian = 'be', + Bengali = 'bn', + Bihari = 'bh', + Bislama = 'bi', + Bosnian = 'bs', + Breton = 'br', + Bulgarian = 'bg', + Burmese = 'my', + Catalan = 'ca', + Chamorro = 'ch', + Chechen = 'ce', + Chinese = 'zh', + ChurchSlavic = 'cu', + Chuvash = 'cv', + Cornish = 'kw', + Corsican = 'co', + Cree = 'cr', + Czech = 'cs', + Danish = 'da', + Divehi = 'dv', + Dutch = 'nl', + Dzongkha = 'dz', + English = 'en', + Esperanto = 'eo', + Estonian = 'et', + Ewe = 'ee', + Faroese = 'fo', + Fijian = 'fj', + Finnish = 'fi', + French = 'fr', + Frisian = 'fy', + Fulah = 'ff', + Georgian = 'ka', + German = 'de', + Gaelic = 'gd', + Irish = 'ga', + Galician = 'gl', + Manx = 'gv', + Greek = 'el', + Guarani = 'gn', + Gujarati = 'gu', + Haitian = 'ht', + Hausa = 'ha', + Hebrew = 'he', + Herero = 'hz', + Hindi = 'hi', + HiriMotu = 'ho', + Croatian = 'hr', + Hungarian = 'hu', + Igbo = 'ig', + Icelandic = 'is', + Ido = 'io', + SichuanYi = 'ii', + Inuktitut = 'iu', + Interlingue = 'ie', + Interlingua = 'ia', + Indonesian = 'id', + Inupiaq = 'ik', + Italian = 'it', + Javanese = 'jv', + Japanese = 'ja', + Kalaallisut = 'kl', + Kannada = 'kn', + Kashmiri = 'ks', + Kanuri = 'kr', + Kazakh = 'kk', + Khmer = 'km', + Kikuyu = 'ki', + Kinyarwanda = 'rw', + Kirghiz = 'ky', + Komi = 'kv', + Kongo = 'kg', + Korean = 'ko', + Kuanyama = 'kj', + Kurdish = 'ku', + Lao = 'lo', + Latin = 'la', + Latvian = 'lv', + Limburgan = 'li', + Lingala = 'ln', + Lithuanian = 'lt', + Luxembourgish = 'lb', + LubaKatanga = 'lu', + Ganda = 'lg', + Macedonian = 'mk', + Marshallese = 'mh', + Malayalam = 'ml', + Maori = 'mi', + Marathi = 'mr', + Malay = 'ms', + Malagasy = 'mg', + Maltese = 'mt', + Mongolian = 'mn', + Nauru = 'na', + Navajo = 'nv', + SouthNdebele = 'nr', + NorthNdebele = 'nd', + Ndonga = 'ng', + Nepali = 'ne', + NorwegianNynorsk = 'nn', + NorwegianBokmal = 'nb', + Norwegian = 'no', + Chichewa = 'ny', + Occitan = 'oc', + Ojibwa = 'oj', + Oriya = 'or', + Oromo = 'om', + Ossetian = 'os', + Punjabi = 'pa', + Persian = 'fa', + Pali = 'pi', + Polish = 'pl', + Portuguese = 'pt', + Pashto = 'ps', + Quechua = 'qu', + Romansh = 'rm', + Romanian = 'ro', + Rundi = 'rn', + Russian = 'ru', + Sango = 'sg', + Sanskrit = 'sa', + Sinhala = 'si', + Slovak = 'sk', + Slovenian = 'sl', + NorthernSami = 'se', + Samoan = 'sm', + Shona = 'sn', + Sindhi = 'sd', + Somali = 'so', + Sotho = 'st', + Spanish = 'es', + Sardinian = 'sc', + Serbian = 'sr', + Swati = 'ss', + Sundanese = 'su', + Swahili = 'sw', + Swedish = 'sv', + Tahitian = 'ty', + Tamil = 'ta', + Tatar = 'tt', + Telugu = 'te', + Tajik = 'tg', + Tagalog = 'tl', + Thai = 'th', + Tibetan = 'bo', + Tigrinya = 'ti', + Tonga = 'to', + Tswana = 'tn', + Tsonga = 'ts', + Turkmen = 'tk', + Turkish = 'tr', + Twi = 'tw', + Uighur = 'ug', + Ukrainian = 'uk', + Urdu = 'ur', + Uzbek = 'uz', + Venda = 've', + Vietnamese = 'vi', + Volapuk = 'vo', + Welsh = 'cy', + Walloon = 'wa', + Wolof = 'wo', + Xhosa = 'xh', + Yiddish = 'yi', + Yoruba = 'yo', + Zhuang = 'za', + Zulu = 'zu', +} diff --git a/frontend/src/services/learning-paths/learning-object.ts b/frontend/src/services/learning-paths/learning-object.ts new file mode 100644 index 00000000..cde6915e --- /dev/null +++ b/frontend/src/services/learning-paths/learning-object.ts @@ -0,0 +1,37 @@ +import type {Language} from "@/services/learning-paths/language.ts"; + +export interface LearningPathIdentifier { + hruid: string; + language: Language; +} + +export interface EducationalGoal { + source: string; + id: string; +} + +export interface ReturnValue { + callback_url: string; + callback_schema: Record; +} + +export interface LearningObjectMetadata { + _id: string; + uuid: string; + hruid: string; + version: number; + language: Language; + title: string; + description: string; + difficulty: number; + estimated_time: number; + available: boolean; + teacher_exclusive: boolean; + educational_goals: EducationalGoal[]; + keywords: string[]; + target_ages: number[]; + content_type: string; // Markdown, image, etc. + content_location?: string; + skos_concepts?: string[]; + return_value?: ReturnValue; +} diff --git a/frontend/src/services/learning-paths/learning-path-service.ts b/frontend/src/services/learning-paths/learning-path-service.ts new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/services/learning-paths/learning-path.ts b/frontend/src/services/learning-paths/learning-path.ts new file mode 100644 index 00000000..2347b04e --- /dev/null +++ b/frontend/src/services/learning-paths/learning-path.ts @@ -0,0 +1,40 @@ +import type {Language} from "@/services/learning-paths/language.ts"; + +export interface LearningPath { + language: string; + hruid: string; + title: string; + description: string; + image?: string; // Image might be missing, so it's optional + num_nodes: number; + num_nodes_left: number; + nodes: LearningObjectNode[]; + keywords: string; + target_ages: number[]; + min_age: number; + max_age: number; + __order: number; +} + +export interface LearningObjectNode { + _id: string; + learningobject_hruid: string; + version: number; + language: Language; + start_node?: boolean; + transitions: Transition[]; + created_at: string; + updatedAt: string; + done?: boolean; // True if a submission exists for this node by the user for whom the learning path is customized. +} + +export interface Transition { + default: boolean; + _id: string; + next: { + _id: string; + hruid: string; + version: number; + language: string; + }; +} diff --git a/frontend/src/views/learning-paths/LearningPathPage.vue b/frontend/src/views/learning-paths/LearningPathPage.vue new file mode 100644 index 00000000..14e911e2 --- /dev/null +++ b/frontend/src/views/learning-paths/LearningPathPage.vue @@ -0,0 +1,20 @@ + + + + + From c14d6c53da11d3d48d9f9fbe3a038d74f5af71ea Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sat, 22 Mar 2025 17:16:33 +0100 Subject: [PATCH 015/173] 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 016/173] 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 017/173] 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 018/173] 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 019/173] 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 020/173] 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 021/173] 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 3c3fddb7d016ed4a05ac3363de65431f47e18941 Mon Sep 17 00:00:00 2001 From: Gerald Schmittinger Date: Sun, 23 Mar 2025 08:56:34 +0100 Subject: [PATCH 022/173] =?UTF-8?q?feat(frontend):=20LearningObjectService?= =?UTF-8?q?=20en=20LearningPathService=20ge=C3=AFmplementeerd.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/UsingRemoteResource.vue | 39 ++++++ frontend/src/i18n/locale/de.json | 3 +- frontend/src/i18n/locale/en.json | 3 +- frontend/src/i18n/locale/fr.json | 3 +- frontend/src/i18n/locale/nl.json | 3 +- frontend/src/main.ts | 8 ++ .../services/{ => api-client}/api-client.ts | 0 .../services/api-client/api-exceptions.d.ts | 10 ++ .../api-client/endpoints/delete-endpoint.ts | 10 ++ .../api-client/endpoints/get-endpoint.ts | 10 ++ .../api-client/endpoints/post-endpoint.ts | 10 ++ .../api-client/endpoints/rest-endpoint.ts | 30 +++++ .../services/api-client/remote-resource.ts | 70 +++++++++++ .../src/services/auth/auth-config-loader.ts | 2 +- frontend/src/services/auth/auth-service.ts | 2 +- .../language.ts | 0 .../learning-object-service.ts | 13 ++ .../learning-content/learning-object.ts | 33 +++++ .../learning-content/learning-path-service.ts | 13 ++ .../learning-content/learning-path.ts | 118 ++++++++++++++++++ .../learning-paths/learning-object.ts | 37 ------ .../learning-paths/learning-path-service.ts | 0 .../services/learning-paths/learning-path.ts | 40 ------ frontend/src/views/HomePage.vue | 2 +- 24 files changed, 375 insertions(+), 84 deletions(-) create mode 100644 frontend/src/components/UsingRemoteResource.vue rename frontend/src/services/{ => api-client}/api-client.ts (100%) create mode 100644 frontend/src/services/api-client/api-exceptions.d.ts create mode 100644 frontend/src/services/api-client/endpoints/delete-endpoint.ts create mode 100644 frontend/src/services/api-client/endpoints/get-endpoint.ts create mode 100644 frontend/src/services/api-client/endpoints/post-endpoint.ts create mode 100644 frontend/src/services/api-client/endpoints/rest-endpoint.ts create mode 100644 frontend/src/services/api-client/remote-resource.ts rename frontend/src/services/{learning-paths => learning-content}/language.ts (100%) create mode 100644 frontend/src/services/learning-content/learning-object-service.ts create mode 100644 frontend/src/services/learning-content/learning-object.ts create mode 100644 frontend/src/services/learning-content/learning-path-service.ts create mode 100644 frontend/src/services/learning-content/learning-path.ts delete mode 100644 frontend/src/services/learning-paths/learning-object.ts delete mode 100644 frontend/src/services/learning-paths/learning-path-service.ts delete mode 100644 frontend/src/services/learning-paths/learning-path.ts diff --git a/frontend/src/components/UsingRemoteResource.vue b/frontend/src/components/UsingRemoteResource.vue new file mode 100644 index 00000000..6bc19234 --- /dev/null +++ b/frontend/src/components/UsingRemoteResource.vue @@ -0,0 +1,39 @@ + + + + + diff --git a/frontend/src/i18n/locale/de.json b/frontend/src/i18n/locale/de.json index a1a699e5..b9a087fc 100644 --- a/frontend/src/i18n/locale/de.json +++ b/frontend/src/i18n/locale/de.json @@ -1,3 +1,4 @@ { - "welcome": "Willkommen" + "welcome": "Willkommen", + "error_title": "Fehler" } diff --git a/frontend/src/i18n/locale/en.json b/frontend/src/i18n/locale/en.json index c75bfc5d..a04b3fa5 100644 --- a/frontend/src/i18n/locale/en.json +++ b/frontend/src/i18n/locale/en.json @@ -5,5 +5,6 @@ "assignments": "assignments", "classes": "classes", "discussions": "discussions", - "logout": "log out" + "logout": "log out", + "error_title": "Error" } diff --git a/frontend/src/i18n/locale/fr.json b/frontend/src/i18n/locale/fr.json index 86fe964d..1d59c4dd 100644 --- a/frontend/src/i18n/locale/fr.json +++ b/frontend/src/i18n/locale/fr.json @@ -1,3 +1,4 @@ { - "welcome": "Bienvenue" + "welcome": "Bienvenue", + "error_title": "Erreur" } diff --git a/frontend/src/i18n/locale/nl.json b/frontend/src/i18n/locale/nl.json index 97ec9b49..576a95b4 100644 --- a/frontend/src/i18n/locale/nl.json +++ b/frontend/src/i18n/locale/nl.json @@ -5,5 +5,6 @@ "assignments": "opdrachten", "classes": "klassen", "discussions": "discussies", - "logout": "log uit" + "logout": "log uit", + "error_title": "Fout" } diff --git a/frontend/src/main.ts b/frontend/src/main.ts index e4843dae..556395c3 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -10,6 +10,7 @@ import i18n from "./i18n/i18n.ts"; // Components import App from "./App.vue"; import router from "./router"; +import {aliases, mdi} from "vuetify/iconsets/mdi"; const app = createApp(App); @@ -23,6 +24,13 @@ document.head.appendChild(link); const vuetify = createVuetify({ components, directives, + icons: { + defaultSet: "mdi", + aliases, + sets: { + mdi + } + } }); app.use(vuetify); app.use(i18n); diff --git a/frontend/src/services/api-client.ts b/frontend/src/services/api-client/api-client.ts similarity index 100% rename from frontend/src/services/api-client.ts rename to frontend/src/services/api-client/api-client.ts diff --git a/frontend/src/services/api-client/api-exceptions.d.ts b/frontend/src/services/api-client/api-exceptions.d.ts new file mode 100644 index 00000000..28df1bc1 --- /dev/null +++ b/frontend/src/services/api-client/api-exceptions.d.ts @@ -0,0 +1,10 @@ +import type {AxiosResponse} from "axios"; + +export class HttpErrorStatusException extends Error { + public readonly statusCode: number; + + constructor(response: AxiosResponse) { + super(`${response.statusText} (${response.status})`); + this.statusCode = response.status; + } +} diff --git a/frontend/src/services/api-client/endpoints/delete-endpoint.ts b/frontend/src/services/api-client/endpoints/delete-endpoint.ts new file mode 100644 index 00000000..554e1855 --- /dev/null +++ b/frontend/src/services/api-client/endpoints/delete-endpoint.ts @@ -0,0 +1,10 @@ +import {type Params, RestEndpoint} from "@/services/api-client/endpoints/rest-endpoint.ts"; +import {RemoteResource} from "@/services/api-client/remote-resource.ts"; + +export class DeleteEndpoint extends RestEndpoint { + readonly method = "GET"; + + public delete(pathParams: PP, queryParams: QP): RemoteResource { + return super.request(pathParams, queryParams, undefined); + } +} diff --git a/frontend/src/services/api-client/endpoints/get-endpoint.ts b/frontend/src/services/api-client/endpoints/get-endpoint.ts new file mode 100644 index 00000000..1d9a086f --- /dev/null +++ b/frontend/src/services/api-client/endpoints/get-endpoint.ts @@ -0,0 +1,10 @@ +import {type Params, RestEndpoint} from "@/services/api-client/endpoints/rest-endpoint.ts"; +import {RemoteResource} from "@/services/api-client/remote-resource.ts"; + +export class GetEndpoint extends RestEndpoint { + readonly method = "GET"; + + public get(pathParams: PP, queryParams: QP): RemoteResource { + return super.request(pathParams, queryParams, undefined); + } +} diff --git a/frontend/src/services/api-client/endpoints/post-endpoint.ts b/frontend/src/services/api-client/endpoints/post-endpoint.ts new file mode 100644 index 00000000..6fde53e8 --- /dev/null +++ b/frontend/src/services/api-client/endpoints/post-endpoint.ts @@ -0,0 +1,10 @@ +import {type Params, RestEndpoint} from "@/services/api-client/endpoints/rest-endpoint.ts"; +import {RemoteResource} from "@/services/api-client/remote-resource.ts"; + +export class PostEndpoint extends RestEndpoint { + readonly method = "POST"; + + public post(pathParams: PP, queryParams: QP, body: B): RemoteResource { + return super.request(pathParams, queryParams, body); + } +} diff --git a/frontend/src/services/api-client/endpoints/rest-endpoint.ts b/frontend/src/services/api-client/endpoints/rest-endpoint.ts new file mode 100644 index 00000000..3f6bb5cf --- /dev/null +++ b/frontend/src/services/api-client/endpoints/rest-endpoint.ts @@ -0,0 +1,30 @@ +import {RemoteResource} from "@/services/api-client/remote-resource.ts"; +import apiClient from "@/services/api-client/api-client.ts"; +import {HttpErrorStatusException} from "@/services/api-client/api-exceptions"; + +export abstract class RestEndpoint { + public abstract readonly method: "GET" | "POST" | "PUT" | "DELETE"; + constructor(public readonly url: string) { + } + + protected request(pathParams: PP, queryParams: QP, body: B): RemoteResource { + let urlFilledIn = this.url; + urlFilledIn.replace(/:(\w+)([/$])/g, (_, key, after) => + (key in pathParams ? encodeURIComponent(pathParams[key]) : `:${key}`) + after + ); + return new RemoteResource(async () => { + const response = await apiClient.request({ + url: urlFilledIn, + method: this.method, + params: queryParams, + data: body, + }); + if (response.status / 100 !== 2) { + throw new HttpErrorStatusException(response); + } + return response.data; + }); + } +} + +export type Params = {[key: string]: string | number | boolean}; diff --git a/frontend/src/services/api-client/remote-resource.ts b/frontend/src/services/api-client/remote-resource.ts new file mode 100644 index 00000000..b1add8b9 --- /dev/null +++ b/frontend/src/services/api-client/remote-resource.ts @@ -0,0 +1,70 @@ +export class RemoteResource { + static NOT_LOADED: NotLoadedState = {type: "notLoaded"}; + static LOADING: LoadingState = {type: "loading"}; + + private state: NotLoadedState | LoadingState | ErrorState | SuccessState = RemoteResource.NOT_LOADED; + + constructor(private readonly requestFn: () => Promise) { + } + + public async request(): Promise { + this.state = RemoteResource.LOADING; + try { + let resource = await this.requestFn(); + this.state = { + type: "success", + data: resource + }; + return resource; + } catch (e: any) { + this.state = { + type: "error", + errorCode: e.statusCode, + message: e.message, + error: e + }; + } + } + + public startRequestInBackground(): RemoteResource { + this.request().then(); + return this; + } + + public get data(): T | undefined { + if (this.state.type === "success") { + return this.state.data; + } + } + + public map(mappingFn: (content: T) => U): RemoteResource { + return new RemoteResource(async () => { + await this.request(); + if (this.state.type === "success") { + return mappingFn(this.state.data); + } else if (this.state.type === "error") { + throw this.state.error; + } else { + throw new Error("Fetched resource, but afterwards, it was neither in a success nor in an error state. " + + "This should never happen."); + } + }); + } +} + +type NotLoadedState = { + type: "notLoaded" +}; +type LoadingState = { + type: "loading" +}; +type ErrorState = { + type: "error", + errorCode?: number, + message?: string, + error: any +}; +type SuccessState = { + type: "success", + data: T +} diff --git a/frontend/src/services/auth/auth-config-loader.ts b/frontend/src/services/auth/auth-config-loader.ts index ce8a33ca..ef5b63c3 100644 --- a/frontend/src/services/auth/auth-config-loader.ts +++ b/frontend/src/services/auth/auth-config-loader.ts @@ -1,4 +1,4 @@ -import apiClient from "@/services/api-client.ts"; +import apiClient from "@/services/api-client/api-client.ts"; import type { FrontendAuthConfig } from "@/services/auth/auth.d.ts"; /** diff --git a/frontend/src/services/auth/auth-service.ts b/frontend/src/services/auth/auth-service.ts index 2b3d2807..2963d8f4 100644 --- a/frontend/src/services/auth/auth-service.ts +++ b/frontend/src/services/auth/auth-service.ts @@ -8,7 +8,7 @@ import { User, UserManager } from "oidc-client-ts"; import { loadAuthConfig } from "@/services/auth/auth-config-loader.ts"; import authStorage from "./auth-storage.ts"; import { loginRoute } from "@/config.ts"; -import apiClient from "@/services/api-client.ts"; +import apiClient from "@/services/api-client/api-client.ts"; import router from "@/router"; import type { AxiosError } from "axios"; diff --git a/frontend/src/services/learning-paths/language.ts b/frontend/src/services/learning-content/language.ts similarity index 100% rename from frontend/src/services/learning-paths/language.ts rename to frontend/src/services/learning-content/language.ts diff --git a/frontend/src/services/learning-content/learning-object-service.ts b/frontend/src/services/learning-content/learning-object-service.ts new file mode 100644 index 00000000..076a662e --- /dev/null +++ b/frontend/src/services/learning-content/learning-object-service.ts @@ -0,0 +1,13 @@ +import {GetEndpoint} from "@/services/api-client/endpoints/get-endpoint.ts"; +import type {LearningObject} from "@/services/learning-content/learning-object.ts"; +import type {Language} from "@/services/learning-content/language.ts"; +import type {RemoteResource} from "@/services/api-client/remote-resource.ts"; + +const getLearningObjectMetadataEndpoint = new GetEndpoint<{hruid: string}, {language: Language, version: number}, LearningObject>( + "/learningObject/:hruid" +); + +export function getLearningObjectMetadata(hruid: string, language: Language, version: number): RemoteResource { + return getLearningObjectMetadataEndpoint + .get({hruid}, {language, version}); +} diff --git a/frontend/src/services/learning-content/learning-object.ts b/frontend/src/services/learning-content/learning-object.ts new file mode 100644 index 00000000..ff660420 --- /dev/null +++ b/frontend/src/services/learning-content/learning-object.ts @@ -0,0 +1,33 @@ +import type {Language} from "@/services/learning-content/language.ts"; + +export interface EducationalGoal { + source: string; + id: string; +} + +export interface ReturnValue { + callback_url: string; + callback_schema: Record; +} + +export interface LearningObject { + key: string; + _id: string; + uuid: string; + version: number; + title: string; + htmlUrl: string; + language: Language; + difficulty: number; + estimatedTime?: number; + available: boolean; + teacherExclusive: boolean; + educationalGoals: EducationalGoal[]; + keywords: string[]; + description: string; + targetAges: number[]; + contentType: string; + contentLocation?: string; + skosConcepts?: string[]; + returnValue?: ReturnValue; +} diff --git a/frontend/src/services/learning-content/learning-path-service.ts b/frontend/src/services/learning-content/learning-path-service.ts new file mode 100644 index 00000000..be9dffd5 --- /dev/null +++ b/frontend/src/services/learning-content/learning-path-service.ts @@ -0,0 +1,13 @@ +import {GetEndpoint} from "@/services/api-client/endpoints/get-endpoint.ts"; +import {LearningPath, type LearningPathDTO} from "@/services/learning-content/learning-path.ts"; +import type {RemoteResource} from "@/services/api-client/remote-resource.ts"; + +const searchLearningPathsEndpoint = new GetEndpoint<{}, {query: string}, LearningPathDTO[]>( + "/learningObjects/:query" +); + +export function searchLearningPaths(query: string): RemoteResource { + return searchLearningPathsEndpoint + .get({}, {query: query}) + .map(dtos => dtos.map(dto => LearningPath.fromDTO(dto))); +} diff --git a/frontend/src/services/learning-content/learning-path.ts b/frontend/src/services/learning-content/learning-path.ts new file mode 100644 index 00000000..7aefa28e --- /dev/null +++ b/frontend/src/services/learning-content/learning-path.ts @@ -0,0 +1,118 @@ +import type {Language} from "@/services/learning-content/language.ts"; +import type {RemoteResource} from "@/services/api-client/remote-resource.ts"; +import type {LearningObject} from "@/services/learning-content/learning-object.ts"; +import {getLearningObjectMetadata} from "@/services/learning-content/learning-object-service.ts"; + +export interface LearningPathDTO { + language: string; + hruid: string; + title: string; + description: string; + image?: string; // Image might be missing, so it's optional + num_nodes: number; + num_nodes_left: number; + nodes: LearningPathNodeDTO[]; + keywords: string; + target_ages: number[]; + min_age: number; + max_age: number; + __order: number; +} + +interface LearningPathNodeDTO { + _id: string; + learningobject_hruid: string; + version: number; + language: Language; + start_node?: boolean; + transitions: LearningPathTransitionDTO[]; + created_at: string; + updatedAt: string; + done?: boolean; // True if a submission exists for this node by the user for whom the learning path is customized. +} + +interface LearningPathTransitionDTO { + default: boolean; + _id: string; + next: { + _id: string; + hruid: string; + version: number; + language: string; + }; +} + +export class LearningPathNode { + public learningObject: RemoteResource + + constructor( + public readonly learningobjectHruid: string, + public readonly version: number, + public readonly language: Language, + public readonly transitions: {next: LearningPathNode, default: boolean}[], + public readonly createdAt: Date, + public readonly updatedAt: Date + ) { + this.learningObject = getLearningObjectMetadata(learningobjectHruid, language, version); + } + + static fromDTOAndOtherNodes(dto: LearningPathNodeDTO, otherNodes: LearningPathNodeDTO[]): LearningPathNode { + return new LearningPathNode( + dto.learningobject_hruid, + dto.version, + dto.language, + dto.transitions.map(transDto => { + let nextNodeDto = otherNodes.filter(it => + it.learningobject_hruid === transDto.next.hruid + && it.language === transDto.next.language + && it.version === transDto.next.version + ); + if (nextNodeDto.length !== 1) { + throw new Error(`Invalid learning path! There is a transition to node` + + `${transDto.next.hruid}/${transDto.next.language}/${transDto.next.version}, but there are` + + `${nextNodeDto.length} such nodes.`); + } + return { + next: LearningPathNode.fromDTOAndOtherNodes(nextNodeDto[0], otherNodes), + default: transDto.default + } + }), + new Date(dto.created_at), + new Date(dto.updatedAt), + ) + } +} + +export class LearningPath { + constructor( + public readonly language: string, + public readonly hruid: string, + public readonly title: string, + public readonly description: string, + public readonly amountOfNodes: number, + public readonly amountOfNodesLeft: number, + public readonly keywords: string[], + public readonly targetAges: {min: number; max: number}, + public readonly startNode: LearningPathNode, + public readonly image?: string // Image might be missing, so it's optional + ) { + } + + static fromDTO(dto: LearningPathDTO): LearningPath { + let startNodeDto = dto.nodes.filter(it => it.start_node); + if (startNodeDto.length !== 1) { + throw new Error(`Invalid learning path! Expected precisely one start node, but there were ${startNodeDto.length}.`); + } + return new LearningPath( + dto.language, + dto.hruid, + dto.title, + dto.description, + dto.num_nodes, + dto.num_nodes_left, + dto.keywords.split(' '), + {min: dto.min_age, max: dto.max_age}, + LearningPathNode.fromDTOAndOtherNodes(startNodeDto[0], dto.nodes) + ) + } +} diff --git a/frontend/src/services/learning-paths/learning-object.ts b/frontend/src/services/learning-paths/learning-object.ts deleted file mode 100644 index cde6915e..00000000 --- a/frontend/src/services/learning-paths/learning-object.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type {Language} from "@/services/learning-paths/language.ts"; - -export interface LearningPathIdentifier { - hruid: string; - language: Language; -} - -export interface EducationalGoal { - source: string; - id: string; -} - -export interface ReturnValue { - callback_url: string; - callback_schema: Record; -} - -export interface LearningObjectMetadata { - _id: string; - uuid: string; - hruid: string; - version: number; - language: Language; - title: string; - description: string; - difficulty: number; - estimated_time: number; - available: boolean; - teacher_exclusive: boolean; - educational_goals: EducationalGoal[]; - keywords: string[]; - target_ages: number[]; - content_type: string; // Markdown, image, etc. - content_location?: string; - skos_concepts?: string[]; - return_value?: ReturnValue; -} diff --git a/frontend/src/services/learning-paths/learning-path-service.ts b/frontend/src/services/learning-paths/learning-path-service.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/frontend/src/services/learning-paths/learning-path.ts b/frontend/src/services/learning-paths/learning-path.ts deleted file mode 100644 index 2347b04e..00000000 --- a/frontend/src/services/learning-paths/learning-path.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type {Language} from "@/services/learning-paths/language.ts"; - -export interface LearningPath { - language: string; - hruid: string; - title: string; - description: string; - image?: string; // Image might be missing, so it's optional - num_nodes: number; - num_nodes_left: number; - nodes: LearningObjectNode[]; - keywords: string; - target_ages: number[]; - min_age: number; - max_age: number; - __order: number; -} - -export interface LearningObjectNode { - _id: string; - learningobject_hruid: string; - version: number; - language: Language; - start_node?: boolean; - transitions: Transition[]; - created_at: string; - updatedAt: string; - done?: boolean; // True if a submission exists for this node by the user for whom the learning path is customized. -} - -export interface Transition { - default: boolean; - _id: string; - next: { - _id: string; - hruid: string; - version: number; - language: string; - }; -} diff --git a/frontend/src/views/HomePage.vue b/frontend/src/views/HomePage.vue index e9d53770..c17ca147 100644 --- a/frontend/src/views/HomePage.vue +++ b/frontend/src/views/HomePage.vue @@ -1,6 +1,6 @@