From f5b6a5a604f478fedb59acd71b269607836a8608 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Tue, 4 Mar 2025 12:31:12 +0100 Subject: [PATCH 001/106] feat: service en controller laag voor getStudent geimplementeerd --- backend/src/controllers/students.ts | 29 + backend/src/interfaces/students.ts | 12 + backend/src/routes/student.ts | 24 +- backend/src/services/students.ts | 18 + package-lock.json | 3293 ++++----------------------- 5 files changed, 459 insertions(+), 2917 deletions(-) create mode 100644 backend/src/controllers/students.ts create mode 100644 backend/src/interfaces/students.ts create mode 100644 backend/src/services/students.ts diff --git a/backend/src/controllers/students.ts b/backend/src/controllers/students.ts new file mode 100644 index 00000000..b129eb57 --- /dev/null +++ b/backend/src/controllers/students.ts @@ -0,0 +1,29 @@ +import { Request, Response } from 'express'; +import { getStudentById } from "../services/students"; + +export async function getStudent( + req: Request, + res: Response, +): Promise { + try { + const username = req.params.id; + const student = await getStudentById(username); + + if (!student) { + res.status(404).json({ error: "Student not found" }); + return; + } else { + student.endpoints = { + classes: `/student/${req.params.id}/classes`, + questions: `/student/${req.params.id}/submissions`, + invitations: `/student/${req.params.id}/assignments`, + groups: `/student/${req.params.id}/groups`, + } + res.json(student); + } + + } catch (error) { + console.error('Error fetching learning objects:', error); + res.status(500).json({ error: 'Internal server error' }); + } +} \ No newline at end of file diff --git a/backend/src/interfaces/students.ts b/backend/src/interfaces/students.ts new file mode 100644 index 00000000..cb13d3c2 --- /dev/null +++ b/backend/src/interfaces/students.ts @@ -0,0 +1,12 @@ +export interface StudentDTO { + id: string; + username: string; + firstName: string; + lastName: string; + endpoints?: { + classes: string; + questions: string; + invitations: string; + groups: string; + }; +} diff --git a/backend/src/routes/student.ts b/backend/src/routes/student.ts index a11c1fbc..6a6280e6 100644 --- a/backend/src/routes/student.ts +++ b/backend/src/routes/student.ts @@ -1,4 +1,6 @@ import express from 'express' +import { getStudentById } from '../services/students'; +import { getStudent } from '../controllers/students'; const router = express.Router(); // root endpoint used to search objects @@ -12,20 +14,7 @@ router.get('/', (req, res) => { }); // information about a student's profile -router.get('/:id', (req, res) => { - res.json({ - id: req.params.id, - firstName: 'Jimmy', - lastName: 'Faster', - username: 'JimmyFaster2', - endpoints: { - classes: `/student/${req.params.id}/classes`, - questions: `/student/${req.params.id}/submissions`, - invitations: `/student/${req.params.id}/assignments`, - groups: `/student/${req.params.id}/groups`, - }, - }); -}); +router.get('/:id', getStudent); // the list of classes a student is in router.get('/:id/classes', (req, res) => { @@ -56,4 +45,11 @@ router.get('/:id/groups', (req, res) => { }); }) +// a list of questions a user has created +router.get('/:id/questions', (req, res) => { + res.json({ + questions: [ '0' ], + }); +}) + export default router \ No newline at end of file diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts new file mode 100644 index 00000000..b2aa953b --- /dev/null +++ b/backend/src/services/students.ts @@ -0,0 +1,18 @@ +import { getStudentRepository } from "../data/repositories"; +import { StudentDTO } from "../interfaces/students"; + +export async function getStudentById(username: string): Promise { + const studentRepository = getStudentRepository(); + const student = await studentRepository.findByUsername(username); + + if (!student) return null; + else { + return { + id: student.username, + username: student.username, + firstName: student.firstName, + lastName: student.lastName, + } + } +} + diff --git a/package-lock.json b/package-lock.json index a7bf6bb9..88dcd6fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,15 +35,13 @@ "@mikro-orm/core": "^6.4.6", "@mikro-orm/postgresql": "^6.4.6", "@mikro-orm/reflection": "^6.4.6", + "@mikro-orm/sqlite": "6.4.6", "@types/js-yaml": "^4.0.9", "axios": "^1.8.1", - "@mikro-orm/sqlite": "6.4.6", - "dotenv": "^16.4.7", "express": "^5.0.1", - "uuid": "^11.1.0", "js-yaml": "^4.1.0", - "@types/js-yaml": "^4.0.9", + "uuid": "^11.1.0" }, "devDependencies": { "@mikro-orm/cli": "^6.4.6", @@ -56,27 +54,8 @@ "vitest": "^3.0.6" } }, - "backend/node_modules/@mikro-orm/sqlite": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/@mikro-orm/sqlite/-/sqlite-6.4.6.tgz", - "integrity": "sha512-BvoLd6qge2N4P2w9yjPP8+Ya5dxxnZrS6W3B2xm0m8BUesWnaCg2pmGXQpzFjrpYMg40mZ+RJWRTPq4M2Nl4lw==", - "dependencies": { - "@mikro-orm/knex": "6.4.6", - "fs-extra": "11.3.0", - "sqlite3": "5.1.7", - "sqlstring-sqlite": "0.1.1" - }, - "engines": { - "node": ">= 18.12.0" - }, - "peerDependencies": { - "@mikro-orm/core": "^6.0.0" - } - }, "backend/node_modules/globals": { "version": "15.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", - "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", "dev": true, "license": "MIT", "engines": { @@ -119,8 +98,6 @@ }, "node_modules/@ampproject/remapping": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -133,8 +110,6 @@ }, "node_modules/@ampproject/remapping/node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -144,8 +119,6 @@ }, "node_modules/@antfu/utils": { "version": "0.7.10", - "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz", - "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", "dev": true, "license": "MIT", "funding": { @@ -154,8 +127,6 @@ }, "node_modules/@asamuzakjp/css-color": { "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-2.8.3.tgz", - "integrity": "sha512-GIc76d9UI1hCvOATjZPyHFmE5qhRccp3/zGfMPapK3jBi+yocEzp6BBB0UnfRYP9NP4FANqUZYb0hnfs3TM3hw==", "dev": true, "license": "MIT", "dependencies": { @@ -168,8 +139,6 @@ }, "node_modules/@babel/code-frame": { "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "dev": true, "license": "MIT", "dependencies": { @@ -183,8 +152,6 @@ }, "node_modules/@babel/compat-data": { "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", - "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", "dev": true, "license": "MIT", "engines": { @@ -193,8 +160,6 @@ }, "node_modules/@babel/core": { "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz", - "integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==", "dev": true, "license": "MIT", "dependencies": { @@ -224,8 +189,6 @@ }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { @@ -234,8 +197,6 @@ }, "node_modules/@babel/generator": { "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz", - "integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==", "dev": true, "license": "MIT", "dependencies": { @@ -251,8 +212,6 @@ }, "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -262,8 +221,6 @@ }, "node_modules/@babel/helper-annotate-as-pure": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", "dev": true, "license": "MIT", "dependencies": { @@ -275,8 +232,6 @@ }, "node_modules/@babel/helper-compilation-targets": { "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", - "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", "dev": true, "license": "MIT", "dependencies": { @@ -292,8 +247,6 @@ }, "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "license": "ISC", "dependencies": { @@ -302,8 +255,6 @@ }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { @@ -312,8 +263,6 @@ }, "node_modules/@babel/helper-create-class-features-plugin": { "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz", - "integrity": "sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg==", "dev": true, "license": "MIT", "dependencies": { @@ -334,8 +283,6 @@ }, "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { @@ -344,8 +291,6 @@ }, "node_modules/@babel/helper-member-expression-to-functions": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", - "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", "dev": true, "license": "MIT", "dependencies": { @@ -358,8 +303,6 @@ }, "node_modules/@babel/helper-module-imports": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", "dev": true, "license": "MIT", "dependencies": { @@ -372,8 +315,6 @@ }, "node_modules/@babel/helper-module-transforms": { "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", "dev": true, "license": "MIT", "dependencies": { @@ -390,8 +331,6 @@ }, "node_modules/@babel/helper-optimise-call-expression": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", - "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", "dev": true, "license": "MIT", "dependencies": { @@ -403,8 +342,6 @@ }, "node_modules/@babel/helper-plugin-utils": { "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", - "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", "dev": true, "license": "MIT", "engines": { @@ -413,8 +350,6 @@ }, "node_modules/@babel/helper-replace-supers": { "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", - "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", "dev": true, "license": "MIT", "dependencies": { @@ -431,8 +366,6 @@ }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", - "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", "dev": true, "license": "MIT", "dependencies": { @@ -445,8 +378,6 @@ }, "node_modules/@babel/helper-string-parser": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -454,8 +385,6 @@ }, "node_modules/@babel/helper-validator-identifier": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -463,8 +392,6 @@ }, "node_modules/@babel/helper-validator-option": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", "dev": true, "license": "MIT", "engines": { @@ -473,8 +400,6 @@ }, "node_modules/@babel/helpers": { "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.9.tgz", - "integrity": "sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==", "dev": true, "license": "MIT", "dependencies": { @@ -487,8 +412,6 @@ }, "node_modules/@babel/parser": { "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", - "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", "license": "MIT", "dependencies": { "@babel/types": "^7.26.9" @@ -502,8 +425,6 @@ }, "node_modules/@babel/plugin-proposal-decorators": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.25.9.tgz", - "integrity": "sha512-smkNLL/O1ezy9Nhy4CNosc4Va+1wo5w4gzSZeLe6y6dM4mmHfYOCPolXQPHQxonZCF+ZyebxN9vqOolkYrSn5g==", "dev": true, "license": "MIT", "dependencies": { @@ -520,8 +441,6 @@ }, "node_modules/@babel/plugin-syntax-decorators": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.25.9.tgz", - "integrity": "sha512-ryzI0McXUPJnRCvMo4lumIKZUzhYUO/ScI+Mz4YVaTLt04DHNSjEUjKVvbzQjZFLuod/cYEc07mJWhzl6v4DPg==", "dev": true, "license": "MIT", "dependencies": { @@ -536,8 +455,6 @@ }, "node_modules/@babel/plugin-syntax-import-attributes": { "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", - "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", "dev": true, "license": "MIT", "dependencies": { @@ -552,8 +469,6 @@ }, "node_modules/@babel/plugin-syntax-import-meta": { "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, "license": "MIT", "dependencies": { @@ -565,8 +480,6 @@ }, "node_modules/@babel/plugin-syntax-jsx": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", - "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", "dev": true, "license": "MIT", "dependencies": { @@ -581,8 +494,6 @@ }, "node_modules/@babel/plugin-syntax-typescript": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", - "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", "dev": true, "license": "MIT", "dependencies": { @@ -597,8 +508,6 @@ }, "node_modules/@babel/plugin-transform-typescript": { "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.26.8.tgz", - "integrity": "sha512-bME5J9AC8ChwA7aEPJ6zym3w7aObZULHhbNLU0bKUhKsAkylkzUdq+0kdymh9rzi8nlNFl2bmldFBCKNJBUpuw==", "dev": true, "license": "MIT", "dependencies": { @@ -617,8 +526,6 @@ }, "node_modules/@babel/template": { "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", - "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", "dev": true, "license": "MIT", "dependencies": { @@ -632,8 +539,6 @@ }, "node_modules/@babel/traverse": { "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz", - "integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==", "dev": true, "license": "MIT", "dependencies": { @@ -651,8 +556,6 @@ }, "node_modules/@babel/traverse/node_modules/globals": { "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true, "license": "MIT", "engines": { @@ -661,8 +564,6 @@ }, "node_modules/@babel/types": { "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz", - "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.25.9", @@ -674,8 +575,6 @@ }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, "license": "MIT", "dependencies": { @@ -687,8 +586,6 @@ }, "node_modules/@csstools/color-helpers": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.1.tgz", - "integrity": "sha512-MKtmkA0BX87PKaO1NFRTFH+UnkgnmySQOvNxJubsadusqPEC2aJ9MOQiMceZJJ6oitUl/i0L6u0M1IrmAOmgBA==", "dev": true, "funding": [ { @@ -707,8 +604,6 @@ }, "node_modules/@csstools/css-calc": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.1.tgz", - "integrity": "sha512-rL7kaUnTkL9K+Cvo2pnCieqNpTKgQzy5f+N+5Iuko9HAoasP+xgprVh7KN/MaJVvVL1l0EzQq2MoqBHKSrDrag==", "dev": true, "funding": [ { @@ -731,8 +626,6 @@ }, "node_modules/@csstools/css-color-parser": { "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.7.tgz", - "integrity": "sha512-nkMp2mTICw32uE5NN+EsJ4f5N+IGFeCFu4bGpiKgb2Pq/7J/MpyLBeQ5ry4KKtRFZaYs6sTmcMYrSRIyj5DFKA==", "dev": true, "funding": [ { @@ -759,8 +652,6 @@ }, "node_modules/@csstools/css-parser-algorithms": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz", - "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==", "dev": true, "funding": [ { @@ -782,8 +673,6 @@ }, "node_modules/@csstools/css-tokenizer": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz", - "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==", "dev": true, "funding": [ { @@ -800,298 +689,8 @@ "node": ">=18" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", - "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", - "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", - "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", - "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", - "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", - "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", - "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", - "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", - "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", - "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", - "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", - "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", - "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", - "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", - "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", - "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/linux-x64": { "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", - "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", "cpu": [ "x64" ], @@ -1106,154 +705,8 @@ "node": ">=18" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", - "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", - "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", - "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", - "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", - "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", - "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", - "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", - "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", "dev": true, "license": "MIT", "dependencies": { @@ -1271,8 +724,6 @@ }, "node_modules/@eslint-community/regexpp": { "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, "license": "MIT", "engines": { @@ -1281,8 +732,6 @@ }, "node_modules/@eslint/compat": { "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.6.tgz", - "integrity": "sha512-k7HNCqApoDHM6XzT30zGoETj+D+uUcZUb+IVAJmar3u6bvHf7hhHJcWx09QHj4/a2qrKZMWU0E16tvkiAdv06Q==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1299,8 +748,6 @@ }, "node_modules/@eslint/config-array": { "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", - "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1314,8 +761,6 @@ }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", "dependencies": { @@ -1325,8 +770,6 @@ }, "node_modules/@eslint/config-array/node_modules/minimatch": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { @@ -1338,8 +781,6 @@ }, "node_modules/@eslint/core": { "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.11.0.tgz", - "integrity": "sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1351,8 +792,6 @@ }, "node_modules/@eslint/eslintrc": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", - "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", "dev": true, "license": "MIT", "dependencies": { @@ -1375,8 +814,6 @@ }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", "dependencies": { @@ -1386,8 +823,6 @@ }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { @@ -1399,8 +834,6 @@ }, "node_modules/@eslint/js": { "version": "9.20.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.20.0.tgz", - "integrity": "sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ==", "dev": true, "license": "MIT", "engines": { @@ -1409,8 +842,6 @@ }, "node_modules/@eslint/object-schema": { "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1419,8 +850,6 @@ }, "node_modules/@eslint/plugin-kit": { "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.6.tgz", - "integrity": "sha512-+0TjwR1eAUdZtvv/ir1mGX+v0tUoR3VEPB8Up0LLJC+whRW0GgBBtpbOkg/a/U4Dxa6l5a3l9AJ1aWIQVyoWJA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1433,14 +862,11 @@ }, "node_modules/@gar/promisify": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "license": "MIT", "optional": true }, "node_modules/@humanfs/core": { "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1449,8 +875,6 @@ }, "node_modules/@humanfs/node": { "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1463,8 +887,6 @@ }, "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1477,8 +899,6 @@ }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1491,8 +911,6 @@ }, "node_modules/@humanwhocodes/retry": { "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", - "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1507,7 +925,6 @@ "version": "10.0.5", "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-10.0.5.tgz", "integrity": "sha512-F3snDTQs0MdvnnyzTDTVkOYVAZOE/MHwRvF7mn7Jw1yuih4NrFYLNYIymGlLmq4HU2iIdzYsZ7f47bOcwY73XQ==", - "license": "MIT", "dependencies": { "@intlify/message-compiler": "10.0.5", "@intlify/shared": "10.0.5" @@ -1523,7 +940,6 @@ "version": "10.0.5", "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-10.0.5.tgz", "integrity": "sha512-6GT1BJ852gZ0gItNZN2krX5QAmea+cmdjMvsWohArAZ3GmHdnNANEcF9JjPXAMRtQ6Ux5E269ymamg/+WU6tQA==", - "license": "MIT", "dependencies": { "@intlify/shared": "10.0.5", "source-map-js": "^1.0.2" @@ -1539,7 +955,6 @@ "version": "10.0.5", "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-10.0.5.tgz", "integrity": "sha512-bmsP4L2HqBF6i6uaMqJMcFBONVjKt+siGluRq4Ca4C0q7W2eMaVZr8iCgF9dKbcVXutftkC7D6z2SaSMmLiDyA==", - "license": "MIT", "engines": { "node": ">= 16" }, @@ -1549,8 +964,6 @@ }, "node_modules/@isaacs/cliui": { "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, "license": "ISC", "dependencies": { @@ -1565,95 +978,8 @@ "node": ">=12" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/@jercle/yargonaut": { "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@jercle/yargonaut/-/yargonaut-1.1.5.tgz", - "integrity": "sha512-zBp2myVvBHp1UaJsNTyS6q4UDKT7eRiqTS4oNTS6VQMd6mpxYOdbeK4pY279cDCdakGy6hG0J3ejoXZVsPwHqw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1664,8 +990,6 @@ }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "dev": true, "license": "MIT", "dependencies": { @@ -1679,8 +1003,6 @@ }, "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1690,8 +1012,6 @@ }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", "engines": { @@ -1700,8 +1020,6 @@ }, "node_modules/@jridgewell/set-array": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, "license": "MIT", "engines": { @@ -1710,14 +1028,10 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1727,8 +1041,6 @@ }, "node_modules/@mikro-orm/cli": { "version": "6.4.6", - "resolved": "https://registry.npmjs.org/@mikro-orm/cli/-/cli-6.4.6.tgz", - "integrity": "sha512-sTMoDSJrnHZBT+ZAG40OeZwR9zRTYHtaaub9OoMM2CrxfI1KeiNqL/XFB4LaM5SVRAbnoEFpMJwQ8KS+5NcN9w==", "dev": true, "license": "MIT", "dependencies": { @@ -1749,8 +1061,6 @@ }, "node_modules/@mikro-orm/core": { "version": "6.4.6", - "resolved": "https://registry.npmjs.org/@mikro-orm/core/-/core-6.4.6.tgz", - "integrity": "sha512-xVm/ALG/3vTMgh6SrvojJ6jjMa0s2hNzWN0triDB16BaNdLwWE4aAaAe+3CuoMFqJAArSOUISTEjExbzELB1ZA==", "license": "MIT", "dependencies": { "dataloader": "2.2.3", @@ -1770,8 +1080,6 @@ }, "node_modules/@mikro-orm/knex": { "version": "6.4.6", - "resolved": "https://registry.npmjs.org/@mikro-orm/knex/-/knex-6.4.6.tgz", - "integrity": "sha512-o6t67tFH/GuPZCCEtKbTTL8HDXNgB2ITjButCTZLwteL0qI9yE/f7K6K+dEUKW+hAL3KRvc2BQeumvCVWFeISg==", "license": "MIT", "dependencies": { "fs-extra": "11.3.0", @@ -1801,8 +1109,6 @@ }, "node_modules/@mikro-orm/postgresql": { "version": "6.4.6", - "resolved": "https://registry.npmjs.org/@mikro-orm/postgresql/-/postgresql-6.4.6.tgz", - "integrity": "sha512-ZcuGp6n/SPzkHPANksjdLPyeu6jT7WCg3ueNViVrxdsguCi+/grz4I+hbOQDXV8uNHCAUOw6+WP2ndcVEYkZZQ==", "license": "MIT", "dependencies": { "@mikro-orm/knex": "6.4.6", @@ -1820,8 +1126,6 @@ }, "node_modules/@mikro-orm/reflection": { "version": "6.4.6", - "resolved": "https://registry.npmjs.org/@mikro-orm/reflection/-/reflection-6.4.6.tgz", - "integrity": "sha512-7mL7HFVnaOOhDNgLjjndWyeJUtOl2wKn0spSqB8uRjS4XtwNEGVZNkW5YD1t/x7TJ99wUhe+oRDiySciiJSeBQ==", "license": "MIT", "dependencies": { "globby": "11.1.0", @@ -1834,10 +1138,24 @@ "@mikro-orm/core": "^6.0.0" } }, + "node_modules/@mikro-orm/sqlite": { + "version": "6.4.6", + "license": "MIT", + "dependencies": { + "@mikro-orm/knex": "6.4.6", + "fs-extra": "11.3.0", + "sqlite3": "5.1.7", + "sqlstring-sqlite": "0.1.1" + }, + "engines": { + "node": ">= 18.12.0" + }, + "peerDependencies": { + "@mikro-orm/core": "^6.0.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -1849,8 +1167,6 @@ }, "node_modules/@nodelib/fs.stat": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "license": "MIT", "engines": { "node": ">= 8" @@ -1858,8 +1174,6 @@ }, "node_modules/@nodelib/fs.walk": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -1871,8 +1185,7 @@ }, "node_modules/@npmcli/fs": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", - "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "license": "ISC", "optional": true, "dependencies": { "@gar/promisify": "^1.0.1", @@ -1881,9 +1194,7 @@ }, "node_modules/@npmcli/move-file": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", - "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", - "deprecated": "This functionality has been moved to @npmcli/fs", + "license": "MIT", "optional": true, "dependencies": { "mkdirp": "^1.0.4", @@ -1895,15 +1206,11 @@ }, "node_modules/@one-ini/wasm": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", - "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", "dev": true, "license": "MIT" }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, "license": "MIT", "optional": true, @@ -1913,8 +1220,6 @@ }, "node_modules/@pkgr/core": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", - "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", "dev": true, "license": "MIT", "engines": { @@ -1926,8 +1231,6 @@ }, "node_modules/@playwright/test": { "version": "1.50.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.50.1.tgz", - "integrity": "sha512-Jii3aBg+CEDpgnuDxEp/h7BimHcUTDlpEtce89xEumlJ5ef2hqepZ+PWp1DDpYC/VO9fmWVI1IlEaoI5fK9FXQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1942,15 +1245,11 @@ }, "node_modules/@polka/url": { "version": "1.0.0-next.28", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", - "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", "dev": true, "license": "MIT" }, "node_modules/@rollup/pluginutils": { "version": "5.1.4", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", - "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1970,17 +1269,8 @@ } } }, - "node_modules/@rollup/pluginutils/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, - "license": "MIT" - }, "node_modules/@rollup/pluginutils/node_modules/picomatch": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, "license": "MIT", "engines": { @@ -1990,206 +1280,8 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz", - "integrity": "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz", - "integrity": "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz", - "integrity": "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz", - "integrity": "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz", - "integrity": "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz", - "integrity": "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz", - "integrity": "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz", - "integrity": "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz", - "integrity": "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz", - "integrity": "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz", - "integrity": "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz", - "integrity": "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz", - "integrity": "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz", - "integrity": "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz", - "integrity": "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==", "cpu": [ "x64" ], @@ -2202,8 +1294,6 @@ }, "node_modules/@rollup/rollup-linux-x64-musl": { "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz", - "integrity": "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==", "cpu": [ "x64" ], @@ -2214,59 +1304,13 @@ "linux" ] }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz", - "integrity": "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz", - "integrity": "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz", - "integrity": "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", - "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", "dev": true, "license": "MIT" }, "node_modules/@sindresorhus/merge-streams": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", - "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", "dev": true, "license": "MIT", "engines": { @@ -2278,8 +1322,7 @@ }, "node_modules/@tootallnate/once": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "license": "MIT", "optional": true, "engines": { "node": ">= 6" @@ -2287,8 +1330,6 @@ }, "node_modules/@ts-morph/common": { "version": "0.26.1", - "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.26.1.tgz", - "integrity": "sha512-Sn28TGl/4cFpcM+jwsH1wLncYq3FtN/BIpem+HOygfBWPT5pAeS5dB4VFVzV8FbnOKHpDLZmvAl4AjPEev5idA==", "license": "MIT", "dependencies": { "fast-glob": "^3.3.2", @@ -2298,43 +1339,31 @@ }, "node_modules/@tsconfig/node10": { "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node12": { "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node14": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node16": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node22": { "version": "22.0.0", - "resolved": "https://registry.npmjs.org/@tsconfig/node22/-/node22-22.0.0.tgz", - "integrity": "sha512-twLQ77zevtxobBOD4ToAtVmuYrpeYUh3qh+TEp+08IWhpsrIflVHqQ1F1CiPxQGL7doCdBIOOCF+1Tm833faNg==", "dev": true, "license": "MIT" }, "node_modules/@types/body-parser": { "version": "1.19.5", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", - "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", "dev": true, "license": "MIT", "dependencies": { @@ -2344,8 +1373,6 @@ }, "node_modules/@types/connect": { "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "dev": true, "license": "MIT", "dependencies": { @@ -2354,22 +1381,16 @@ }, "node_modules/@types/eslint-config-prettier": { "version": "6.11.3", - "resolved": "https://registry.npmjs.org/@types/eslint-config-prettier/-/eslint-config-prettier-6.11.3.tgz", - "integrity": "sha512-3wXCiM8croUnhg9LdtZUJQwNcQYGWxxdOWDjPe1ykCqJFPVpzAKfs/2dgSoCtAvdPeaponcWPI7mPcGGp9dkKQ==", "dev": true, "license": "MIT" }, "node_modules/@types/estree": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true, "license": "MIT" }, "node_modules/@types/express": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", - "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2381,8 +1402,6 @@ }, "node_modules/@types/express-serve-static-core": { "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", - "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", "dev": true, "license": "MIT", "dependencies": { @@ -2394,21 +1413,16 @@ }, "node_modules/@types/http-errors": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", "dev": true, "license": "MIT" }, "node_modules/@types/js-yaml": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", - "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", - "license": "MIT" + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==" }, "node_modules/@types/jsdom": { "version": "21.1.7", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", - "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==", "dev": true, "license": "MIT", "dependencies": { @@ -2419,22 +1433,16 @@ }, "node_modules/@types/json-schema": { "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, "license": "MIT" }, "node_modules/@types/mime": { "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "dev": true, "license": "MIT" }, "node_modules/@types/node": { "version": "22.13.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.4.tgz", - "integrity": "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==", "dev": true, "license": "MIT", "dependencies": { @@ -2443,22 +1451,16 @@ }, "node_modules/@types/qs": { "version": "6.9.18", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", - "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", "dev": true, "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "dev": true, "license": "MIT" }, "node_modules/@types/send": { "version": "0.17.4", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", - "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", "dev": true, "license": "MIT", "dependencies": { @@ -2468,8 +1470,6 @@ }, "node_modules/@types/serve-static": { "version": "1.15.7", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", - "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", "dev": true, "license": "MIT", "dependencies": { @@ -2480,15 +1480,11 @@ }, "node_modules/@types/tough-cookie": { "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", - "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", "dev": true, "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.24.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.24.1.tgz", - "integrity": "sha512-ll1StnKtBigWIGqvYDVuDmXJHVH4zLVot1yQ4fJtLpL7qacwkxJc1T0bptqw+miBQ/QfUbhl1TcQ4accW5KUyA==", "dev": true, "license": "MIT", "dependencies": { @@ -2517,8 +1513,6 @@ }, "node_modules/@typescript-eslint/parser": { "version": "8.24.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.24.1.tgz", - "integrity": "sha512-Tqoa05bu+t5s8CTZFaGpCH2ub3QeT9YDkXbPd3uQ4SfsLoh1/vv2GEYAioPoxCWJJNsenXlC88tRjwoHNts1oQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2542,8 +1536,6 @@ }, "node_modules/@typescript-eslint/scope-manager": { "version": "8.24.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.24.1.tgz", - "integrity": "sha512-OdQr6BNBzwRjNEXMQyaGyZzgg7wzjYKfX2ZBV3E04hUCBDv3GQCHiz9RpqdUIiVrMgJGkXm3tcEh4vFSHreS2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2560,8 +1552,6 @@ }, "node_modules/@typescript-eslint/type-utils": { "version": "8.24.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.24.1.tgz", - "integrity": "sha512-/Do9fmNgCsQ+K4rCz0STI7lYB4phTtEXqqCAs3gZW0pnK7lWNkvWd5iW545GSmApm4AzmQXmSqXPO565B4WVrw==", "dev": true, "license": "MIT", "dependencies": { @@ -2584,8 +1574,6 @@ }, "node_modules/@typescript-eslint/types": { "version": "8.24.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.24.1.tgz", - "integrity": "sha512-9kqJ+2DkUXiuhoiYIUvIYjGcwle8pcPpdlfkemGvTObzgmYfJ5d0Qm6jwb4NBXP9W1I5tss0VIAnWFumz3mC5A==", "dev": true, "license": "MIT", "engines": { @@ -2598,8 +1586,6 @@ }, "node_modules/@typescript-eslint/typescript-estree": { "version": "8.24.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.24.1.tgz", - "integrity": "sha512-UPyy4MJ/0RE648DSKQe9g0VDSehPINiejjA6ElqnFaFIhI6ZEiZAkUI0D5MCk0bQcTf/LVqZStvQ6K4lPn/BRg==", "dev": true, "license": "MIT", "dependencies": { @@ -2625,8 +1611,6 @@ }, "node_modules/@typescript-eslint/utils": { "version": "8.24.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.24.1.tgz", - "integrity": "sha512-OOcg3PMMQx9EXspId5iktsI3eMaXVwlhC8BvNnX6B5w9a4dVgpkQZuU8Hy67TolKcl+iFWq0XX+jbDGN4xWxjQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2649,8 +1633,6 @@ }, "node_modules/@typescript-eslint/visitor-keys": { "version": "8.24.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.24.1.tgz", - "integrity": "sha512-EwVHlp5l+2vp8CoqJm9KikPZgi3gbdZAtabKT9KPShGeOcJhsv4Zdo3oc8T8I0uKEmYoU4ItyxbptjF08enaxg==", "dev": true, "license": "MIT", "dependencies": { @@ -2667,8 +1649,6 @@ }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2680,8 +1660,6 @@ }, "node_modules/@vitejs/plugin-vue": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz", - "integrity": "sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==", "dev": true, "license": "MIT", "engines": { @@ -2694,8 +1672,6 @@ }, "node_modules/@vitest/eslint-plugin": { "version": "1.1.31", - "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.1.31.tgz", - "integrity": "sha512-xlsLr+e+AXZ/00eVZCtNmMeCJoJaRCoLDiAgLcxgQjSS1EertieB2MUHf8xIqPKs9lECc/UpL+y1xDcpvi02hw==", "dev": true, "license": "MIT", "peerDependencies": { @@ -2715,8 +1691,6 @@ }, "node_modules/@vitest/expect": { "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.6.tgz", - "integrity": "sha512-zBduHf/ja7/QRX4HdP1DSq5XrPgdN+jzLOwaTq/0qZjYfgETNFCKf9nOAp2j3hmom3oTbczuUzrzg9Hafh7hNg==", "dev": true, "license": "MIT", "dependencies": { @@ -2731,8 +1705,6 @@ }, "node_modules/@vitest/mocker": { "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.6.tgz", - "integrity": "sha512-KPztr4/tn7qDGZfqlSPQoF2VgJcKxnDNhmfR3VgZ6Fy1bO8T9Fc1stUiTXtqz0yG24VpD00pZP5f8EOFknjNuQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2756,10 +1728,16 @@ } } }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/@vitest/pretty-format": { "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.6.tgz", - "integrity": "sha512-Zyctv3dbNL+67qtHfRnUE/k8qxduOamRfAL1BurEIQSyOEFffoMvx2pnDSSbKAAVxY0Ej2J/GH2dQKI0W2JyVg==", "dev": true, "license": "MIT", "dependencies": { @@ -2771,8 +1749,6 @@ }, "node_modules/@vitest/runner": { "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.6.tgz", - "integrity": "sha512-JopP4m/jGoaG1+CBqubV/5VMbi7L+NQCJTu1J1Pf6YaUbk7bZtaq5CX7p+8sY64Sjn1UQ1XJparHfcvTTdu9cA==", "dev": true, "license": "MIT", "dependencies": { @@ -2785,8 +1761,6 @@ }, "node_modules/@vitest/snapshot": { "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.6.tgz", - "integrity": "sha512-qKSmxNQwT60kNwwJHMVwavvZsMGXWmngD023OHSgn873pV0lylK7dwBTfYP7e4URy5NiBCHHiQGA9DHkYkqRqg==", "dev": true, "license": "MIT", "dependencies": { @@ -2800,8 +1774,6 @@ }, "node_modules/@vitest/spy": { "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.6.tgz", - "integrity": "sha512-HfOGx/bXtjy24fDlTOpgiAEJbRfFxoX3zIGagCqACkFKKZ/TTOE6gYMKXlqecvxEndKFuNHcHqP081ggZ2yM0Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2813,8 +1785,6 @@ }, "node_modules/@vitest/utils": { "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.6.tgz", - "integrity": "sha512-18ktZpf4GQFTbf9jK543uspU03Q2qya7ZGya5yiZ0Gx0nnnalBvd5ZBislbl2EhLjM8A8rt4OilqKG7QwcGkvQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2828,8 +1798,6 @@ }, "node_modules/@volar/language-core": { "version": "2.4.11", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.11.tgz", - "integrity": "sha512-lN2C1+ByfW9/JRPpqScuZt/4OrUUse57GLI6TbLgTIqBVemdl1wNcZ1qYGEo2+Gw8coYLgCy7SuKqn6IrQcQgg==", "dev": true, "license": "MIT", "dependencies": { @@ -2838,15 +1806,11 @@ }, "node_modules/@volar/source-map": { "version": "2.4.11", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.11.tgz", - "integrity": "sha512-ZQpmafIGvaZMn/8iuvCFGrW3smeqkq/IIh9F1SdSx9aUl0J4Iurzd6/FhmjNO5g2ejF3rT45dKskgXWiofqlZQ==", "dev": true, "license": "MIT" }, "node_modules/@volar/typescript": { "version": "2.4.11", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.11.tgz", - "integrity": "sha512-2DT+Tdh88Spp5PyPbqhyoYavYCPDsqbHLFwcUI9K1NlY1YgUJvujGdrqUp0zWxnW7KWNTr3xSpMuv2WnaTKDAw==", "dev": true, "license": "MIT", "dependencies": { @@ -2857,15 +1821,11 @@ }, "node_modules/@vue/babel-helper-vue-transform-on": { "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.2.5.tgz", - "integrity": "sha512-lOz4t39ZdmU4DJAa2hwPYmKc8EsuGa2U0L9KaZaOJUt0UwQNjNA3AZTq6uEivhOKhhG1Wvy96SvYBoFmCg3uuw==", "dev": true, "license": "MIT" }, "node_modules/@vue/babel-plugin-jsx": { "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.2.5.tgz", - "integrity": "sha512-zTrNmOd4939H9KsRIGmmzn3q2zvv1mjxkYZHgqHZgDrXz5B1Q3WyGEjO2f+JrmKghvl1JIRcvo63LgM1kH5zFg==", "dev": true, "license": "MIT", "dependencies": { @@ -2891,8 +1851,6 @@ }, "node_modules/@vue/babel-plugin-resolve-type": { "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.2.5.tgz", - "integrity": "sha512-U/ibkQrf5sx0XXRnUZD1mo5F7PkpKyTbfXM3a3rC4YnUz6crHEz9Jg09jzzL6QYlXNto/9CePdOg/c87O4Nlfg==", "dev": true, "license": "MIT", "dependencies": { @@ -2908,8 +1866,6 @@ }, "node_modules/@vue/compiler-core": { "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", - "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", "license": "MIT", "dependencies": { "@babel/parser": "^7.25.3", @@ -2919,16 +1875,8 @@ "source-map-js": "^1.2.0" } }, - "node_modules/@vue/compiler-core/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, "node_modules/@vue/compiler-dom": { "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", - "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", "license": "MIT", "dependencies": { "@vue/compiler-core": "3.5.13", @@ -2937,8 +1885,6 @@ }, "node_modules/@vue/compiler-sfc": { "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", - "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", "license": "MIT", "dependencies": { "@babel/parser": "^7.25.3", @@ -2952,16 +1898,8 @@ "source-map-js": "^1.2.0" } }, - "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, "node_modules/@vue/compiler-ssr": { "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", - "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", "license": "MIT", "dependencies": { "@vue/compiler-dom": "3.5.13", @@ -2970,8 +1908,6 @@ }, "node_modules/@vue/compiler-vue2": { "version": "2.7.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", - "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", "dev": true, "license": "MIT", "dependencies": { @@ -2981,14 +1917,10 @@ }, "node_modules/@vue/devtools-api": { "version": "6.6.4", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", - "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", "license": "MIT" }, "node_modules/@vue/devtools-core": { "version": "7.7.2", - "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.7.2.tgz", - "integrity": "sha512-lexREWj1lKi91Tblr38ntSsy6CvI8ba7u+jmwh2yruib/ltLUcsIzEjCnrkh1yYGGIKXbAuYV2tOG10fGDB9OQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3005,8 +1937,6 @@ }, "node_modules/@vue/devtools-core/node_modules/nanoid": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.0.tgz", - "integrity": "sha512-zDAl/llz8Ue/EblwSYwdxGBYfj46IM1dhjVi8dyp9LQffoIGxJEAHj2oeZ4uNcgycSRcQ83CnfcZqEJzVDLcDw==", "dev": true, "funding": [ { @@ -3024,8 +1954,6 @@ }, "node_modules/@vue/devtools-kit": { "version": "7.7.2", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.2.tgz", - "integrity": "sha512-CY0I1JH3Z8PECbn6k3TqM1Bk9ASWxeMtTCvZr7vb+CHi+X/QwQm5F1/fPagraamKMAHVfuuCbdcnNg1A4CYVWQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3040,8 +1968,6 @@ }, "node_modules/@vue/devtools-shared": { "version": "7.7.2", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.2.tgz", - "integrity": "sha512-uBFxnp8gwW2vD6FrJB8JZLUzVb6PNRG0B0jBnHsOH8uKyva2qINY8PTF5Te4QlTbMDqU5K6qtJDr6cNsKWhbOA==", "dev": true, "license": "MIT", "dependencies": { @@ -3050,8 +1976,6 @@ }, "node_modules/@vue/eslint-config-prettier": { "version": "10.2.0", - "resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-10.2.0.tgz", - "integrity": "sha512-GL3YBLwv/+b86yHcNNfPJxOTtVFJ4Mbc9UU3zR+KVoG7SwGTjPT+32fXamscNumElhcpXW3mT0DgzS9w32S7Bw==", "dev": true, "license": "MIT", "dependencies": { @@ -3065,8 +1989,6 @@ }, "node_modules/@vue/eslint-config-typescript": { "version": "14.4.0", - "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-14.4.0.tgz", - "integrity": "sha512-daU+eAekEeVz3CReE4PRW25fe+OJDKwE28jHN6LimDEnuFMbJ6H4WGogEpNof276wVP6UvzOeJQfLFjB5mW29A==", "dev": true, "license": "MIT", "dependencies": { @@ -3091,8 +2013,6 @@ }, "node_modules/@vue/language-core": { "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.2.tgz", - "integrity": "sha512-QotO41kurE5PLf3vrNgGTk3QswO2PdUFjBwNiOi7zMmGhwb25PSTh9hD1MCgKC06AVv+8sZQvlL3Do4TTVHSiQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3116,8 +2036,6 @@ }, "node_modules/@vue/reactivity": { "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", - "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", "license": "MIT", "dependencies": { "@vue/shared": "3.5.13" @@ -3125,8 +2043,6 @@ }, "node_modules/@vue/runtime-core": { "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz", - "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", "license": "MIT", "dependencies": { "@vue/reactivity": "3.5.13", @@ -3135,8 +2051,6 @@ }, "node_modules/@vue/runtime-dom": { "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", - "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", "license": "MIT", "dependencies": { "@vue/reactivity": "3.5.13", @@ -3147,8 +2061,6 @@ }, "node_modules/@vue/server-renderer": { "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz", - "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", "license": "MIT", "dependencies": { "@vue/compiler-ssr": "3.5.13", @@ -3160,14 +2072,10 @@ }, "node_modules/@vue/shared": { "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", - "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", "license": "MIT" }, "node_modules/@vue/test-utils": { "version": "2.4.6", - "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.6.tgz", - "integrity": "sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==", "dev": true, "license": "MIT", "dependencies": { @@ -3177,8 +2085,6 @@ }, "node_modules/@vue/tsconfig": { "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.7.0.tgz", - "integrity": "sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -3196,8 +2102,6 @@ }, "node_modules/abbrev": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.0.tgz", - "integrity": "sha512-+/kfrslGQ7TNV2ecmQwMJj/B65g5KVq1/L3SGVZ3tCYGqlzFuFCGBZJtMP99wH3NpEUyAjn0zPdPUg0D+DwrOA==", "dev": true, "license": "ISC", "engines": { @@ -3206,8 +2110,6 @@ }, "node_modules/accepts": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "dependencies": { "mime-types": "^3.0.0", @@ -3219,8 +2121,6 @@ }, "node_modules/acorn": { "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "license": "MIT", "bin": { @@ -3232,8 +2132,6 @@ }, "node_modules/acorn-jsx": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -3242,8 +2140,6 @@ }, "node_modules/acorn-walk": { "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", "dev": true, "license": "MIT", "dependencies": { @@ -3255,8 +2151,6 @@ }, "node_modules/agent-base": { "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", "dev": true, "license": "MIT", "engines": { @@ -3265,8 +2159,7 @@ }, "node_modules/agentkeepalive": { "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", "optional": true, "dependencies": { "humanize-ms": "^1.2.1" @@ -3277,8 +2170,7 @@ }, "node_modules/aggregate-error": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "license": "MIT", "optional": true, "dependencies": { "clean-stack": "^2.0.0", @@ -3290,8 +2182,6 @@ }, "node_modules/ajv": { "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", "dependencies": { @@ -3306,26 +2196,23 @@ } }, "node_modules/alien-signals": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.4.tgz", - "integrity": "sha512-DJqqQD3XcsaQcQ1s+iE2jDUZmmQpXwHiR6fCAim/w87luaW+vmLY8fMlrdkmRwzaFXhkxf3rqPCR59tKVv1MDw==", + "version": "1.0.3", "dev": true, "license": "MIT" }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "6.1.0", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { @@ -3340,15 +2227,12 @@ }, "node_modules/aproba": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "license": "ISC", "optional": true }, "node_modules/are-we-there-yet": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", - "deprecated": "This package is no longer supported.", + "license": "ISC", "optional": true, "dependencies": { "delegates": "^1.0.0", @@ -3360,21 +2244,15 @@ }, "node_modules/arg": { "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true, "license": "MIT" }, "node_modules/argparse": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, "node_modules/array-union": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "license": "MIT", "engines": { "node": ">=8" @@ -3382,8 +2260,6 @@ }, "node_modules/assertion-error": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "license": "MIT", "engines": { @@ -3392,15 +2268,12 @@ }, "node_modules/asynckit": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, "node_modules/axios": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz", "integrity": "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==", - "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -3409,14 +2282,10 @@ }, "node_modules/balanced-match": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "funding": [ { "type": "github", @@ -3430,36 +2299,27 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/bindings": { "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", "dependencies": { "file-uri-to-path": "1.0.0" } }, "node_modules/birpc": { "version": "0.2.19", - "resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.19.tgz", - "integrity": "sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==", "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" } }, - "node_modules/bmp-js": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", - "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==", - "license": "MIT" - }, "node_modules/bl": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -3468,8 +2328,6 @@ }, "node_modules/body-parser": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.1.0.tgz", - "integrity": "sha512-/hPxh61E+ll0Ujp24Ilm64cykicul1ypfwjVttduAiEdtnJFvLePSrIPk+HMImtNv5270wOGCb1Tns2rybMkoQ==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", @@ -3488,8 +2346,6 @@ }, "node_modules/body-parser/node_modules/qs": { "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -3503,15 +2359,11 @@ }, "node_modules/boolbase": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "dev": true, "license": "ISC" }, "node_modules/brace-expansion": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -3519,8 +2371,6 @@ }, "node_modules/braces": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -3531,8 +2381,6 @@ }, "node_modules/browserslist": { "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", "dev": true, "funding": [ { @@ -3564,8 +2412,6 @@ }, "node_modules/buffer": { "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "funding": [ { "type": "github", @@ -3580,6 +2426,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -3587,8 +2434,6 @@ }, "node_modules/bundle-name": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", "dev": true, "license": "MIT", "dependencies": { @@ -3603,8 +2448,6 @@ }, "node_modules/bytes": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -3612,8 +2455,6 @@ }, "node_modules/cac": { "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true, "license": "MIT", "engines": { @@ -3622,8 +2463,7 @@ }, "node_modules/cacache": { "version": "15.3.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", - "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "license": "ISC", "optional": true, "dependencies": { "@npmcli/fs": "^1.0.0", @@ -3651,8 +2491,7 @@ }, "node_modules/cacache/node_modules/brace-expansion": { "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", "optional": true, "dependencies": { "balanced-match": "^1.0.0", @@ -3661,9 +2500,7 @@ }, "node_modules/cacache/node_modules/glob": { "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", "optional": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -3682,8 +2519,7 @@ }, "node_modules/cacache/node_modules/lru-cache": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", "optional": true, "dependencies": { "yallist": "^4.0.0" @@ -3694,8 +2530,7 @@ }, "node_modules/cacache/node_modules/minimatch": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", "optional": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -3706,8 +2541,7 @@ }, "node_modules/cacache/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "optional": true, "dependencies": { "yallist": "^4.0.0" @@ -3718,14 +2552,11 @@ }, "node_modules/cacache/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", "optional": true }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -3737,8 +2568,6 @@ }, "node_modules/call-bound": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", - "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -3753,8 +2582,6 @@ }, "node_modules/callsites": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, "license": "MIT", "engines": { @@ -3763,8 +2590,6 @@ }, "node_modules/caniuse-lite": { "version": "1.0.30001700", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz", - "integrity": "sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==", "dev": true, "funding": [ { @@ -3784,8 +2609,6 @@ }, "node_modules/chai": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", - "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", "dev": true, "license": "MIT", "dependencies": { @@ -3801,8 +2624,6 @@ }, "node_modules/chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "dependencies": { @@ -3818,8 +2639,6 @@ }, "node_modules/check-error": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, "license": "MIT", "engines": { @@ -3828,16 +2647,14 @@ }, "node_modules/chownr": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/clean-stack": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "license": "MIT", "optional": true, "engines": { "node": ">=6" @@ -3845,8 +2662,6 @@ }, "node_modules/cliui": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "license": "ISC", "dependencies": { @@ -3858,16 +2673,65 @@ "node": ">=12" } }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/code-block-writer": { "version": "13.0.3", - "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", - "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", "license": "MIT" }, "node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3879,15 +2743,12 @@ }, "node_modules/color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "license": "MIT" }, "node_modules/color-support": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", "optional": true, "bin": { "color-support": "bin.js" @@ -3895,14 +2756,10 @@ }, "node_modules/colorette": { "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", "license": "MIT" }, "node_modules/combined-stream": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -3913,8 +2770,6 @@ }, "node_modules/commander": { "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "license": "MIT", "engines": { "node": ">=14" @@ -3922,15 +2777,11 @@ }, "node_modules/concat-map": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "devOptional": true, "license": "MIT" }, "node_modules/config-chain": { "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3940,14 +2791,11 @@ }, "node_modules/console-control-strings": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC", "optional": true }, "node_modules/content-disposition": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" @@ -3958,8 +2806,6 @@ }, "node_modules/content-type": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -3967,15 +2813,11 @@ }, "node_modules/convert-source-map": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, "license": "MIT" }, "node_modules/cookie": { "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -3983,8 +2825,6 @@ }, "node_modules/cookie-signature": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "license": "MIT", "engines": { "node": ">=6.6.0" @@ -3992,8 +2832,6 @@ }, "node_modules/copy-anything": { "version": "3.0.5", - "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", - "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", "dev": true, "license": "MIT", "dependencies": { @@ -4008,15 +2846,11 @@ }, "node_modules/create-require": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true, "license": "MIT" }, "node_modules/cross-spawn": { "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -4030,8 +2864,6 @@ }, "node_modules/cssesc": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true, "license": "MIT", "bin": { @@ -4043,8 +2875,6 @@ }, "node_modules/cssstyle": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.2.1.tgz", - "integrity": "sha512-9+vem03dMXG7gDmZ62uqmRiMRNtinIZ9ZyuF6BdxzfOD+FdN5hretzynkn0ReS2DO2GSw76RWHs0UmJPI2zUjw==", "dev": true, "license": "MIT", "dependencies": { @@ -4057,14 +2887,10 @@ }, "node_modules/csstype": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, "node_modules/data-urls": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", - "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", "dev": true, "license": "MIT", "dependencies": { @@ -4077,21 +2903,15 @@ }, "node_modules/dataloader": { "version": "2.2.3", - "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.2.3.tgz", - "integrity": "sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==", "license": "MIT" }, "node_modules/de-indent": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", - "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", "dev": true, "license": "MIT" }, "node_modules/debug": { "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -4107,15 +2927,12 @@ }, "node_modules/decimal.js": { "version": "10.5.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", - "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==", "dev": true, "license": "MIT" }, "node_modules/decompress-response": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", "dependencies": { "mimic-response": "^3.1.0" }, @@ -4128,8 +2945,6 @@ }, "node_modules/deep-eql": { "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, "license": "MIT", "engines": { @@ -4138,23 +2953,18 @@ }, "node_modules/deep-extend": { "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", "engines": { "node": ">=4.0.0" } }, "node_modules/deep-is": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, "license": "MIT" }, "node_modules/default-browser": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", - "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", "dev": true, "license": "MIT", "dependencies": { @@ -4170,8 +2980,6 @@ }, "node_modules/default-browser-id": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", - "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", "dev": true, "license": "MIT", "engines": { @@ -4183,8 +2991,6 @@ }, "node_modules/define-lazy-prop": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", "dev": true, "license": "MIT", "engines": { @@ -4196,8 +3002,6 @@ }, "node_modules/delayed-stream": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "license": "MIT", "engines": { "node": ">=0.4.0" @@ -4205,14 +3009,11 @@ }, "node_modules/delegates": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT", "optional": true }, "node_modules/depd": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -4220,8 +3021,6 @@ }, "node_modules/destroy": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "license": "MIT", "engines": { "node": ">= 0.8", @@ -4230,16 +3029,13 @@ }, "node_modules/detect-libc": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", "engines": { "node": ">=8" } }, "node_modules/diff": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -4248,8 +3044,6 @@ }, "node_modules/dir-glob": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "license": "MIT", "dependencies": { "path-type": "^4.0.0" @@ -4260,8 +3054,6 @@ }, "node_modules/dotenv": { "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -4272,8 +3064,6 @@ }, "node_modules/dunder-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -4294,15 +3084,11 @@ }, "node_modules/eastasianwidth": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, "license": "MIT" }, "node_modules/editorconfig": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", - "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", "dev": true, "license": "MIT", "dependencies": { @@ -4320,8 +3106,6 @@ }, "node_modules/editorconfig/node_modules/minimatch": { "version": "9.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", - "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", "dev": true, "license": "ISC", "dependencies": { @@ -4336,28 +3120,20 @@ }, "node_modules/ee-first": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, "node_modules/electron-to-chromium": { "version": "1.5.102", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.102.tgz", - "integrity": "sha512-eHhqaja8tE/FNpIiBrvBjFV/SSKpyWHLvxuR9dPTdo+3V9ppdLmFB7ZZQ98qNovcngPLYIz0oOBF9P0FfZef5Q==", "dev": true, "license": "ISC" }, "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "version": "9.2.2", "dev": true, "license": "MIT" }, "node_modules/encodeurl": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -4365,8 +3141,7 @@ }, "node_modules/encoding": { "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "license": "MIT", "optional": true, "dependencies": { "iconv-lite": "^0.6.2" @@ -4374,8 +3149,7 @@ }, "node_modules/encoding/node_modules/iconv-lite": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -4386,16 +3160,13 @@ }, "node_modules/end-of-stream": { "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", "dependencies": { "once": "^1.4.0" } }, "node_modules/entities": { "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -4406,8 +3177,7 @@ }, "node_modules/env-paths": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", "optional": true, "engines": { "node": ">=6" @@ -4415,14 +3185,11 @@ }, "node_modules/err-code": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "license": "MIT", "optional": true }, "node_modules/error-stack-parser-es": { "version": "0.1.5", - "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-0.1.5.tgz", - "integrity": "sha512-xHku1X40RO+fO8yJ8Wh2f2rZWVjqyhb1zgq1yZ8aZRQkv6OOKhKWRUaht3eSCUbAOBaKIgM+ykwFLE+QUxgGeg==", "dev": true, "license": "MIT", "funding": { @@ -4431,8 +3198,6 @@ }, "node_modules/es-define-property": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -4440,8 +3205,6 @@ }, "node_modules/es-errors": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -4449,15 +3212,11 @@ }, "node_modules/es-module-lexer": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", - "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", "dev": true, "license": "MIT" }, "node_modules/es-object-atoms": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -4468,8 +3227,6 @@ }, "node_modules/es-set-tostringtag": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -4483,8 +3240,6 @@ }, "node_modules/esbuild": { "version": "0.25.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", - "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -4524,8 +3279,6 @@ }, "node_modules/escalade": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "license": "MIT", "engines": { "node": ">=6" @@ -4533,14 +3286,10 @@ }, "node_modules/escape-html": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, "node_modules/escape-string-regexp": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", "engines": { @@ -4552,8 +3301,6 @@ }, "node_modules/eslint": { "version": "9.20.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.20.1.tgz", - "integrity": "sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g==", "dev": true, "license": "MIT", "dependencies": { @@ -4612,8 +3359,6 @@ }, "node_modules/eslint-config-prettier": { "version": "10.0.1", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.1.tgz", - "integrity": "sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw==", "dev": true, "license": "MIT", "bin": { @@ -4625,8 +3370,6 @@ }, "node_modules/eslint-plugin-playwright": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-playwright/-/eslint-plugin-playwright-2.2.0.tgz", - "integrity": "sha512-qSQpAw7RcSzE3zPp8FMGkthaCWovHZ/BsXtpmnGax9vQLIovlh1bsZHEa2+j2lv9DWhnyeLM/qZmp7ffQZfQvg==", "dev": true, "license": "MIT", "workspaces": [ @@ -4644,8 +3387,6 @@ }, "node_modules/eslint-plugin-playwright/node_modules/globals": { "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4660,8 +3401,6 @@ }, "node_modules/eslint-plugin-prettier": { "version": "5.2.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.3.tgz", - "integrity": "sha512-qJ+y0FfCp/mQYQ/vWQ3s7eUlFEL4PyKfAJxsnYTJ4YT73nsJBWqmEpFryxV9OeUiqmsTsYJ5Y+KDNaeP31wrRw==", "dev": true, "license": "MIT", "dependencies": { @@ -4691,8 +3430,6 @@ }, "node_modules/eslint-plugin-vue": { "version": "9.32.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.32.0.tgz", - "integrity": "sha512-b/Y05HYmnB/32wqVcjxjHZzNpwxj1onBOvqW89W+V+XNG1dRuaFbNd3vT9CLbr2LXjEoq+3vn8DanWf7XU22Ug==", "dev": true, "license": "MIT", "dependencies": { @@ -4714,8 +3451,6 @@ }, "node_modules/eslint-plugin-vue/node_modules/globals": { "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4730,8 +3465,6 @@ }, "node_modules/eslint-scope": { "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -4747,8 +3480,6 @@ }, "node_modules/eslint-visitor-keys": { "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "license": "Apache-2.0", "engines": { @@ -4760,8 +3491,6 @@ }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", "dependencies": { @@ -4771,8 +3500,6 @@ }, "node_modules/eslint/node_modules/eslint-visitor-keys": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -4784,8 +3511,6 @@ }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { @@ -4797,8 +3522,6 @@ }, "node_modules/esm": { "version": "3.2.25", - "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", - "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", "license": "MIT", "engines": { "node": ">=6" @@ -4806,8 +3529,6 @@ }, "node_modules/espree": { "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -4824,8 +3545,6 @@ }, "node_modules/espree/node_modules/eslint-visitor-keys": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -4837,8 +3556,6 @@ }, "node_modules/esprima": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", @@ -4850,8 +3567,6 @@ }, "node_modules/esquery": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -4863,8 +3578,6 @@ }, "node_modules/esrecurse": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -4876,8 +3589,6 @@ }, "node_modules/estraverse": { "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -4885,19 +3596,11 @@ } }, "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } + "version": "2.0.2", + "license": "MIT" }, "node_modules/esutils": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -4906,8 +3609,6 @@ }, "node_modules/etag": { "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -4915,8 +3616,6 @@ }, "node_modules/execa": { "version": "9.5.2", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.5.2.tgz", - "integrity": "sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==", "dev": true, "license": "MIT", "dependencies": { @@ -4942,16 +3641,13 @@ }, "node_modules/expand-template": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", "engines": { "node": ">=6" } }, "node_modules/expect-type": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", - "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -4960,8 +3656,6 @@ }, "node_modules/express": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.0.1.tgz", - "integrity": "sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==", "license": "MIT", "dependencies": { "accepts": "^2.0.0", @@ -5003,8 +3697,6 @@ }, "node_modules/express/node_modules/debug": { "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "license": "MIT", "dependencies": { "ms": "2.1.2" @@ -5020,28 +3712,20 @@ }, "node_modules/express/node_modules/ms": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "license": "MIT" }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, "license": "MIT" }, "node_modules/fast-diff": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true, "license": "Apache-2.0" }, "node_modules/fast-glob": { "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -5056,8 +3740,6 @@ }, "node_modules/fast-glob/node_modules/glob-parent": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -5068,22 +3750,16 @@ }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, "license": "MIT" }, "node_modules/fastq": { "version": "1.19.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz", - "integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==", "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -5091,8 +3767,6 @@ }, "node_modules/figlet": { "version": "1.8.0", - "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.8.0.tgz", - "integrity": "sha512-chzvGjd+Sp7KUvPHZv6EXV5Ir3Q7kYNpCr4aHrRW79qFtTefmQZNny+W1pW9kf5zeE6dikku2W50W/wAH2xWgw==", "dev": true, "license": "MIT", "bin": { @@ -5104,8 +3778,6 @@ }, "node_modules/figures": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", - "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", "dev": true, "license": "MIT", "dependencies": { @@ -5120,8 +3792,6 @@ }, "node_modules/file-entry-cache": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5133,13 +3803,10 @@ }, "node_modules/file-uri-to-path": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + "license": "MIT" }, "node_modules/fill-range": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -5150,8 +3817,6 @@ }, "node_modules/finalhandler": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.0.0.tgz", - "integrity": "sha512-MX6Zo2adDViYh+GcxxB1dpO43eypOGUOL12rLCOTMQv/DfIbpSJUy4oQIIZhVZkH9e+bZWKMon0XHFEju16tkQ==", "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -5168,8 +3833,6 @@ }, "node_modules/finalhandler/node_modules/debug": { "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -5177,8 +3840,6 @@ }, "node_modules/finalhandler/node_modules/encodeurl": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -5186,14 +3847,10 @@ }, "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, "node_modules/find-up": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", "dependencies": { @@ -5209,8 +3866,6 @@ }, "node_modules/flat-cache": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", "dependencies": { @@ -5223,8 +3878,6 @@ }, "node_modules/flatted": { "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, "license": "ISC" }, @@ -5238,7 +3891,6 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], - "license": "MIT", "engines": { "node": ">=4.0" }, @@ -5250,8 +3902,6 @@ }, "node_modules/foreground-child": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", - "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "dev": true, "license": "ISC", "dependencies": { @@ -5267,8 +3917,6 @@ }, "node_modules/form-data": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -5282,8 +3930,6 @@ }, "node_modules/form-data/node_modules/mime-db": { "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -5291,8 +3937,6 @@ }, "node_modules/form-data/node_modules/mime-types": { "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -5303,8 +3947,6 @@ }, "node_modules/forwarded": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -5312,8 +3954,6 @@ }, "node_modules/fresh": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -5321,13 +3961,10 @@ }, "node_modules/fs-constants": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + "license": "MIT" }, "node_modules/fs-extra": { "version": "11.3.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", - "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -5340,8 +3977,7 @@ }, "node_modules/fs-minipass": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -5351,8 +3987,7 @@ }, "node_modules/fs-minipass/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -5362,34 +3997,15 @@ }, "node_modules/fs-minipass/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "license": "ISC" }, "node_modules/fs.realpath": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC", "optional": true }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5397,9 +4013,7 @@ }, "node_modules/gauge": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", - "deprecated": "This package is no longer supported.", + "license": "ISC", "optional": true, "dependencies": { "aproba": "^1.0.3 || ^2.0.0", @@ -5417,8 +4031,7 @@ }, "node_modules/gauge/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", "optional": true, "engines": { "node": ">=8" @@ -5426,20 +4039,17 @@ }, "node_modules/gauge/node_modules/emoji-regex": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", "optional": true }, "node_modules/gauge/node_modules/signal-exit": { "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC", "optional": true }, "node_modules/gauge/node_modules/string-width": { "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "optional": true, "dependencies": { "emoji-regex": "^8.0.0", @@ -5452,8 +4062,7 @@ }, "node_modules/gauge/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", "optional": true, "dependencies": { "ansi-regex": "^5.0.1" @@ -5464,8 +4073,6 @@ }, "node_modules/gensync": { "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, "license": "MIT", "engines": { @@ -5474,8 +4081,6 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, "license": "ISC", "engines": { @@ -5484,8 +4089,6 @@ }, "node_modules/get-intrinsic": { "version": "1.2.7", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", - "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -5508,8 +4111,6 @@ }, "node_modules/get-package-type": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "license": "MIT", "engines": { "node": ">=8.0.0" @@ -5517,8 +4118,6 @@ }, "node_modules/get-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -5530,8 +4129,6 @@ }, "node_modules/get-stream": { "version": "9.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", - "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", "dev": true, "license": "MIT", "dependencies": { @@ -5547,8 +4144,6 @@ }, "node_modules/get-tsconfig": { "version": "4.10.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", - "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", "dev": true, "license": "MIT", "dependencies": { @@ -5560,19 +4155,14 @@ }, "node_modules/getopts": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz", - "integrity": "sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==", "license": "MIT" }, "node_modules/github-from-package": { "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + "license": "MIT" }, "node_modules/glob": { "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "license": "ISC", "dependencies": { @@ -5592,8 +4182,6 @@ }, "node_modules/glob-parent": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "license": "ISC", "dependencies": { @@ -5605,8 +4193,6 @@ }, "node_modules/globals": { "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, "license": "MIT", "engines": { @@ -5618,8 +4204,6 @@ }, "node_modules/globby": { "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "license": "MIT", "dependencies": { "array-union": "^2.1.0", @@ -5638,8 +4222,6 @@ }, "node_modules/gopd": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -5650,21 +4232,15 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, "node_modules/graphemer": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true, "license": "MIT" }, "node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { @@ -5673,8 +4249,6 @@ }, "node_modules/has-symbols": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -5685,8 +4259,6 @@ }, "node_modules/has-tostringtag": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -5700,14 +4272,11 @@ }, "node_modules/has-unicode": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC", "optional": true }, "node_modules/hasown": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -5718,8 +4287,6 @@ }, "node_modules/he": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true, "license": "MIT", "bin": { @@ -5728,15 +4295,11 @@ }, "node_modules/hookable": { "version": "5.5.3", - "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", - "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", "dev": true, "license": "MIT" }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5748,8 +4311,6 @@ }, "node_modules/html-tags": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", - "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", "dev": true, "license": "MIT", "engines": { @@ -5761,14 +4322,11 @@ }, "node_modules/http-cache-semantics": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "license": "BSD-2-Clause", "optional": true }, "node_modules/http-errors": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "license": "MIT", "dependencies": { "depd": "2.0.0", @@ -5783,8 +4341,6 @@ }, "node_modules/http-proxy-agent": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "license": "MIT", "dependencies": { @@ -5797,8 +4353,6 @@ }, "node_modules/https-proxy-agent": { "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", "dependencies": { @@ -5811,8 +4365,6 @@ }, "node_modules/human-signals": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.0.tgz", - "integrity": "sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -5821,8 +4373,7 @@ }, "node_modules/humanize-ms": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", "optional": true, "dependencies": { "ms": "^2.0.0" @@ -5830,8 +4381,6 @@ }, "node_modules/iconv-lite": { "version": "0.5.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", - "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" @@ -5840,16 +4389,8 @@ "node": ">=0.10.0" } }, - "node_modules/idb-keyval": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz", - "integrity": "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==", - "license": "Apache-2.0" - }, "node_modules/ieee754": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "funding": [ { "type": "github", @@ -5863,12 +4404,11 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "BSD-3-Clause" }, "node_modules/ignore": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "license": "MIT", "engines": { "node": ">= 4" @@ -5876,8 +4416,6 @@ }, "node_modules/import-fresh": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5893,8 +4431,6 @@ }, "node_modules/imurmurhash": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "devOptional": true, "license": "MIT", "engines": { @@ -5903,8 +4439,7 @@ }, "node_modules/indent-string": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", "optional": true, "engines": { "node": ">=8" @@ -5912,15 +4447,12 @@ }, "node_modules/infer-owner": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "license": "ISC", "optional": true }, "node_modules/inflight": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", "optional": true, "dependencies": { "once": "^1.3.0", @@ -5929,20 +4461,14 @@ }, "node_modules/inherits": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, "node_modules/ini": { "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, "node_modules/interpret": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", "license": "MIT", "engines": { "node": ">= 0.10" @@ -5950,8 +4476,7 @@ }, "node_modules/ip-address": { "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "license": "MIT", "optional": true, "dependencies": { "jsbn": "1.1.0", @@ -5963,8 +4488,6 @@ }, "node_modules/ipaddr.js": { "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "license": "MIT", "engines": { "node": ">= 0.10" @@ -5972,8 +4495,6 @@ }, "node_modules/is-core-module": { "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -5987,8 +4508,6 @@ }, "node_modules/is-docker": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", "dev": true, "license": "MIT", "bin": { @@ -6003,8 +4522,6 @@ }, "node_modules/is-extglob": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6012,8 +4529,6 @@ }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "devOptional": true, "license": "MIT", "engines": { @@ -6022,8 +4537,6 @@ }, "node_modules/is-glob": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -6034,8 +4547,6 @@ }, "node_modules/is-inside-container": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", "dev": true, "license": "MIT", "dependencies": { @@ -6053,14 +4564,11 @@ }, "node_modules/is-lambda": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "license": "MIT", "optional": true }, "node_modules/is-number": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "license": "MIT", "engines": { "node": ">=0.12.0" @@ -6068,8 +4576,6 @@ }, "node_modules/is-plain-obj": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "dev": true, "license": "MIT", "engines": { @@ -6081,21 +4587,15 @@ }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true, "license": "MIT" }, "node_modules/is-promise": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, "node_modules/is-stream": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", - "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", "dev": true, "license": "MIT", "engines": { @@ -6107,8 +4607,6 @@ }, "node_modules/is-unicode-supported": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", "dev": true, "license": "MIT", "engines": { @@ -6118,16 +4616,8 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-url": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", - "license": "MIT" - }, "node_modules/is-what": { "version": "4.1.16", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", - "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", "dev": true, "license": "MIT", "engines": { @@ -6139,8 +4629,6 @@ }, "node_modules/is-wsl": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", "dev": true, "license": "MIT", "dependencies": { @@ -6155,15 +4643,11 @@ }, "node_modules/isexe": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "devOptional": true, "license": "ISC" }, "node_modules/jackspeak": { "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -6178,8 +4662,6 @@ }, "node_modules/jiti": { "version": "2.4.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", - "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", "dev": true, "license": "MIT", "bin": { @@ -6188,8 +4670,6 @@ }, "node_modules/js-beautify": { "version": "1.15.3", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.3.tgz", - "integrity": "sha512-rKKGuyTxGNlyN4EQKWzNndzXpi0bOl8Gl8YQAW1as/oMz0XhD6sHJO1hTvoBDOSzKuJb9WkwoAb34FfdkKMv2A==", "dev": true, "license": "MIT", "dependencies": { @@ -6210,8 +4690,6 @@ }, "node_modules/js-cookie": { "version": "3.0.5", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", - "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", "dev": true, "license": "MIT", "engines": { @@ -6220,15 +4698,11 @@ }, "node_modules/js-tokens": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -6239,14 +4713,11 @@ }, "node_modules/jsbn": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "license": "MIT", "optional": true }, "node_modules/jsdom": { "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.0.0.tgz", - "integrity": "sha512-BZYDGVAIriBWTpIxYzrXjv3E/4u8+/pSG5bQdIYCbNCGOvsPkDQfTVLAIXAf9ETdCpduCVTkDe2NNZ8NIwUVzw==", "dev": true, "license": "MIT", "dependencies": { @@ -6286,8 +4757,6 @@ }, "node_modules/jsdom/node_modules/xml-name-validator": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", - "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", "dev": true, "license": "Apache-2.0", "engines": { @@ -6296,8 +4765,6 @@ }, "node_modules/jsesc": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, "license": "MIT", "bin": { @@ -6309,15 +4776,11 @@ }, "node_modules/json-buffer": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, "license": "MIT" }, "node_modules/json-parse-even-better-errors": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", - "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", "dev": true, "license": "MIT", "engines": { @@ -6326,22 +4789,16 @@ }, "node_modules/json-schema-traverse": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, "license": "MIT" }, "node_modules/json5": { "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "license": "MIT", "bin": { @@ -6353,8 +4810,6 @@ }, "node_modules/jsonfile": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "license": "MIT", "dependencies": { "universalify": "^2.0.0" @@ -6365,8 +4820,6 @@ }, "node_modules/keyv": { "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", "dependencies": { @@ -6375,8 +4828,6 @@ }, "node_modules/knex": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/knex/-/knex-3.1.0.tgz", - "integrity": "sha512-GLoII6hR0c4ti243gMs5/1Rb3B+AjwMOfjYm97pu0FOQa7JH56hgBxYf5WK2525ceSbBY1cjeZ9yk99GPMB6Kw==", "license": "MIT", "dependencies": { "colorette": "2.0.19", @@ -6426,8 +4877,6 @@ }, "node_modules/knex/node_modules/debug": { "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "license": "MIT", "dependencies": { "ms": "2.1.2" @@ -6443,14 +4892,10 @@ }, "node_modules/knex/node_modules/ms": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "license": "MIT" }, "node_modules/knex/node_modules/resolve-from": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "license": "MIT", "engines": { "node": ">=8" @@ -6458,15 +4903,11 @@ }, "node_modules/kolorist": { "version": "1.8.0", - "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", - "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", "dev": true, "license": "MIT" }, "node_modules/levn": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6479,8 +4920,6 @@ }, "node_modules/locate-path": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", "dependencies": { @@ -6495,35 +4934,25 @@ }, "node_modules/lodash": { "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true, "license": "MIT" }, "node_modules/loupe": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", - "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", "dev": true, "license": "MIT" }, "node_modules/lru-cache": { "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, "node_modules/magic-string": { "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" @@ -6531,15 +4960,12 @@ }, "node_modules/make-error": { "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true, "license": "ISC" }, "node_modules/make-fetch-happen": { "version": "9.1.0", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", - "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "license": "ISC", "optional": true, "dependencies": { "agentkeepalive": "^4.1.3", @@ -6565,8 +4991,7 @@ }, "node_modules/make-fetch-happen/node_modules/agent-base": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", "optional": true, "dependencies": { "debug": "4" @@ -6577,8 +5002,7 @@ }, "node_modules/make-fetch-happen/node_modules/http-proxy-agent": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "license": "MIT", "optional": true, "dependencies": { "@tootallnate/once": "1", @@ -6591,8 +5015,7 @@ }, "node_modules/make-fetch-happen/node_modules/https-proxy-agent": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", "optional": true, "dependencies": { "agent-base": "6", @@ -6604,8 +5027,7 @@ }, "node_modules/make-fetch-happen/node_modules/lru-cache": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", "optional": true, "dependencies": { "yallist": "^4.0.0" @@ -6616,8 +5038,7 @@ }, "node_modules/make-fetch-happen/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "optional": true, "dependencies": { "yallist": "^4.0.0" @@ -6628,8 +5049,7 @@ }, "node_modules/make-fetch-happen/node_modules/negotiator": { "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", "optional": true, "engines": { "node": ">= 0.6" @@ -6637,14 +5057,11 @@ }, "node_modules/make-fetch-happen/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", "optional": true }, "node_modules/math-intrinsics": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -6652,8 +5069,6 @@ }, "node_modules/media-typer": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -6661,8 +5076,6 @@ }, "node_modules/memorystream": { "version": "0.3.1", - "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", "dev": true, "engines": { "node": ">= 0.10.0" @@ -6670,8 +5083,6 @@ }, "node_modules/merge-descriptors": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", "engines": { "node": ">=18" @@ -6682,8 +5093,6 @@ }, "node_modules/merge2": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "license": "MIT", "engines": { "node": ">= 8" @@ -6691,8 +5100,6 @@ }, "node_modules/methods": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -6700,8 +5107,6 @@ }, "node_modules/micromatch": { "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -6713,8 +5118,6 @@ }, "node_modules/mikro-orm": { "version": "6.4.6", - "resolved": "https://registry.npmjs.org/mikro-orm/-/mikro-orm-6.4.6.tgz", - "integrity": "sha512-Lr3uFK06O/4F/AtQAsuYD6QH7DgmUooSVFVGf1y02IuiKVFKOMJ4iKimkRMyoA+ykKhgYIp8WiaEqbWJVuz4Vw==", "license": "MIT", "engines": { "node": ">= 18.12.0" @@ -6722,8 +5125,6 @@ }, "node_modules/mime-db": { "version": "1.53.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", - "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -6731,8 +5132,6 @@ }, "node_modules/mime-types": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz", - "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==", "license": "MIT", "dependencies": { "mime-db": "^1.53.0" @@ -6743,8 +5142,7 @@ }, "node_modules/mimic-response": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -6754,8 +5152,6 @@ }, "node_modules/minimatch": { "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -6769,8 +5165,6 @@ }, "node_modules/minimist": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6778,8 +5172,6 @@ }, "node_modules/minipass": { "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, "license": "ISC", "engines": { @@ -6788,8 +5180,7 @@ }, "node_modules/minipass-collect": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "license": "ISC", "optional": true, "dependencies": { "minipass": "^3.0.0" @@ -6800,8 +5191,7 @@ }, "node_modules/minipass-collect/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "optional": true, "dependencies": { "yallist": "^4.0.0" @@ -6812,14 +5202,12 @@ }, "node_modules/minipass-collect/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", "optional": true }, "node_modules/minipass-fetch": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", - "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "license": "MIT", "optional": true, "dependencies": { "minipass": "^3.1.0", @@ -6835,8 +5223,7 @@ }, "node_modules/minipass-fetch/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "optional": true, "dependencies": { "yallist": "^4.0.0" @@ -6847,14 +5234,12 @@ }, "node_modules/minipass-fetch/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", "optional": true }, "node_modules/minipass-flush": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "license": "ISC", "optional": true, "dependencies": { "minipass": "^3.0.0" @@ -6865,8 +5250,7 @@ }, "node_modules/minipass-flush/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "optional": true, "dependencies": { "yallist": "^4.0.0" @@ -6877,14 +5261,12 @@ }, "node_modules/minipass-flush/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", "optional": true }, "node_modules/minipass-pipeline": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "license": "ISC", "optional": true, "dependencies": { "minipass": "^3.0.0" @@ -6895,8 +5277,7 @@ }, "node_modules/minipass-pipeline/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "optional": true, "dependencies": { "yallist": "^4.0.0" @@ -6907,14 +5288,12 @@ }, "node_modules/minipass-pipeline/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", "optional": true }, "node_modules/minipass-sized": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "license": "ISC", "optional": true, "dependencies": { "minipass": "^3.0.0" @@ -6925,8 +5304,7 @@ }, "node_modules/minipass-sized/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "optional": true, "dependencies": { "yallist": "^4.0.0" @@ -6937,14 +5315,12 @@ }, "node_modules/minipass-sized/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", "optional": true }, "node_modules/minizlib": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -6955,8 +5331,7 @@ }, "node_modules/minizlib/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -6966,20 +5341,16 @@ }, "node_modules/minizlib/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "license": "ISC" }, "node_modules/mitt": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "dev": true, "license": "MIT" }, "node_modules/mkdirp": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" }, @@ -6989,13 +5360,10 @@ }, "node_modules/mkdirp-classic": { "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + "license": "MIT" }, "node_modules/mrmime": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", - "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", "dev": true, "license": "MIT", "engines": { @@ -7004,21 +5372,15 @@ }, "node_modules/ms": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, "node_modules/muggle-string": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", - "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", "dev": true, "license": "MIT" }, "node_modules/nanoid": { "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "funding": [ { "type": "github", @@ -7035,70 +5397,23 @@ }, "node_modules/napi-build-utils": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==" + "license": "MIT" }, "node_modules/natural-compare": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true, "license": "MIT" }, "node_modules/negotiator": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - }, "node_modules/node-abi": { "version": "3.74.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.74.0.tgz", - "integrity": "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==", + "license": "MIT", "dependencies": { "semver": "^7.3.5" }, @@ -7108,13 +5423,11 @@ }, "node_modules/node-addon-api": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==" + "license": "MIT" }, "node_modules/node-gyp": { "version": "8.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", - "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "license": "MIT", "optional": true, "dependencies": { "env-paths": "^2.2.0", @@ -7137,14 +5450,12 @@ }, "node_modules/node-gyp/node_modules/abbrev": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC", "optional": true }, "node_modules/node-gyp/node_modules/brace-expansion": { "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", "optional": true, "dependencies": { "balanced-match": "^1.0.0", @@ -7153,9 +5464,7 @@ }, "node_modules/node-gyp/node_modules/glob": { "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", "optional": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -7174,8 +5483,7 @@ }, "node_modules/node-gyp/node_modules/minimatch": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", "optional": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -7186,8 +5494,7 @@ }, "node_modules/node-gyp/node_modules/nopt": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", "optional": true, "dependencies": { "abbrev": "1" @@ -7201,15 +5508,11 @@ }, "node_modules/node-releases": { "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "dev": true, "license": "MIT" }, "node_modules/nopt": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", - "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", "dev": true, "license": "ISC", "dependencies": { @@ -7224,8 +5527,6 @@ }, "node_modules/npm-normalize-package-bin": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", - "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", "dev": true, "license": "ISC", "engines": { @@ -7234,8 +5535,6 @@ }, "node_modules/npm-run-all2": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-7.0.2.tgz", - "integrity": "sha512-7tXR+r9hzRNOPNTvXegM+QzCuMjzUIIq66VDunL6j60O4RrExx32XUhlrS7UK4VcdGw5/Wxzb3kfNcFix9JKDA==", "dev": true, "license": "MIT", "dependencies": { @@ -7261,8 +5560,6 @@ }, "node_modules/npm-run-all2/node_modules/ansi-styles": { "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, "license": "MIT", "engines": { @@ -7274,8 +5571,6 @@ }, "node_modules/npm-run-all2/node_modules/isexe": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, "license": "ISC", "engines": { @@ -7284,8 +5579,6 @@ }, "node_modules/npm-run-all2/node_modules/which": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, "license": "ISC", "dependencies": { @@ -7300,8 +5593,6 @@ }, "node_modules/npm-run-path": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", - "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", "dev": true, "license": "MIT", "dependencies": { @@ -7317,8 +5608,6 @@ }, "node_modules/npm-run-path/node_modules/path-key": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, "license": "MIT", "engines": { @@ -7330,9 +5619,7 @@ }, "node_modules/npmlog": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "deprecated": "This package is no longer supported.", + "license": "ISC", "optional": true, "dependencies": { "are-we-there-yet": "^3.0.0", @@ -7346,8 +5633,6 @@ }, "node_modules/nth-check": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -7359,15 +5644,11 @@ }, "node_modules/nwsapi": { "version": "2.2.16", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.16.tgz", - "integrity": "sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==", "dev": true, "license": "MIT" }, "node_modules/object-inspect": { "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -7378,8 +5659,6 @@ }, "node_modules/on-finished": { "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -7390,8 +5669,6 @@ }, "node_modules/once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "license": "ISC", "dependencies": { "wrappy": "1" @@ -7399,8 +5676,6 @@ }, "node_modules/open": { "version": "10.1.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", - "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", "dev": true, "license": "MIT", "dependencies": { @@ -7416,19 +5691,8 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/opencollective-postinstall": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", - "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", - "license": "MIT", - "bin": { - "opencollective-postinstall": "index.js" - } - }, "node_modules/optionator": { "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", "dependencies": { @@ -7445,8 +5709,6 @@ }, "node_modules/p-limit": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7461,8 +5723,6 @@ }, "node_modules/p-locate": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", "dependencies": { @@ -7477,8 +5737,7 @@ }, "node_modules/p-map": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "license": "MIT", "optional": true, "dependencies": { "aggregate-error": "^3.0.0" @@ -7492,15 +5751,11 @@ }, "node_modules/package-json-from-dist": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/parent-module": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "license": "MIT", "dependencies": { @@ -7512,8 +5767,6 @@ }, "node_modules/parent-require": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parent-require/-/parent-require-1.0.0.tgz", - "integrity": "sha512-2MXDNZC4aXdkkap+rBBMv0lUsfJqvX5/2FiYYnfCnorZt3Pk06/IOR5KeaoghgS2w07MLWgjbsnyaq6PdHn2LQ==", "dev": true, "engines": { "node": ">= 0.4.0" @@ -7521,8 +5774,6 @@ }, "node_modules/parse-ms": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", - "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", "dev": true, "license": "MIT", "engines": { @@ -7534,8 +5785,6 @@ }, "node_modules/parse5": { "version": "7.2.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", - "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7547,8 +5796,6 @@ }, "node_modules/parseurl": { "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -7556,14 +5803,10 @@ }, "node_modules/path-browserify": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", "license": "MIT" }, "node_modules/path-exists": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", "engines": { @@ -7572,8 +5815,7 @@ }, "node_modules/path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", "optional": true, "engines": { "node": ">=0.10.0" @@ -7581,8 +5823,6 @@ }, "node_modules/path-key": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", "engines": { @@ -7591,14 +5831,10 @@ }, "node_modules/path-parse": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "license": "MIT" }, "node_modules/path-scurry": { "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -7614,8 +5850,6 @@ }, "node_modules/path-to-regexp": { "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", "license": "MIT", "engines": { "node": ">=16" @@ -7623,8 +5857,6 @@ }, "node_modules/path-type": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "license": "MIT", "engines": { "node": ">=8" @@ -7632,15 +5864,11 @@ }, "node_modules/pathe": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, "node_modules/pathval": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", - "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", "dev": true, "license": "MIT", "engines": { @@ -7649,15 +5877,11 @@ }, "node_modules/perfect-debounce": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", - "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", "dev": true, "license": "MIT" }, "node_modules/pg": { "version": "8.13.2", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.13.2.tgz", - "integrity": "sha512-L5QkPvTjVWWHbLaFjCkOSplpb2uCiRYbg0IJ2okCy5ClYfWlSgDDnvdR6dyw3EWAH2AfS4j8E61QFI7gLfTtlw==", "license": "MIT", "dependencies": { "pg-connection-string": "^2.7.0", @@ -7683,21 +5907,15 @@ }, "node_modules/pg-cloudflare": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz", - "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", "license": "MIT", "optional": true }, "node_modules/pg-connection-string": { "version": "2.6.2", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz", - "integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==", "license": "MIT" }, "node_modules/pg-int8": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", "license": "ISC", "engines": { "node": ">=4.0.0" @@ -7705,8 +5923,6 @@ }, "node_modules/pg-pool": { "version": "3.7.1", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.7.1.tgz", - "integrity": "sha512-xIOsFoh7Vdhojas6q3596mXFsR8nwBQBXX5JiV7p9buEVAGqYL4yFzclON5P9vFrpu1u7Zwl2oriyDa89n0wbw==", "license": "MIT", "peerDependencies": { "pg": ">=8.0" @@ -7714,14 +5930,10 @@ }, "node_modules/pg-protocol": { "version": "1.7.1", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.7.1.tgz", - "integrity": "sha512-gjTHWGYWsEgy9MsY0Gp6ZJxV24IjDqdpTW7Eh0x+WfJLFsm/TJx1MzL6T0D88mBvkpxotCQ6TwW6N+Kko7lhgQ==", "license": "MIT" }, "node_modules/pg-types": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", "license": "MIT", "dependencies": { "pg-int8": "1.0.1", @@ -7736,8 +5948,6 @@ }, "node_modules/pg-types/node_modules/postgres-array": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", "license": "MIT", "engines": { "node": ">=4" @@ -7745,8 +5955,6 @@ }, "node_modules/pg-types/node_modules/postgres-date": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -7754,8 +5962,6 @@ }, "node_modules/pg-types/node_modules/postgres-interval": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", "license": "MIT", "dependencies": { "xtend": "^4.0.0" @@ -7766,14 +5972,10 @@ }, "node_modules/pg/node_modules/pg-connection-string": { "version": "2.7.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.7.0.tgz", - "integrity": "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==", "license": "MIT" }, "node_modules/pgpass": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", - "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", "license": "MIT", "dependencies": { "split2": "^4.1.0" @@ -7781,14 +5983,10 @@ }, "node_modules/picocolors": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "license": "MIT", "engines": { "node": ">=8.6" @@ -7799,8 +5997,6 @@ }, "node_modules/pidtree": { "version": "0.6.0", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", - "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", "dev": true, "license": "MIT", "bin": { @@ -7812,8 +6008,6 @@ }, "node_modules/playwright": { "version": "1.50.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.50.1.tgz", - "integrity": "sha512-G8rwsOQJ63XG6BbKj2w5rHeavFjy5zynBA9zsJMMtBoe/Uf757oG12NXz6e6OirF7RCrTVAKFXbLmn1RbL7Qaw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -7831,8 +6025,6 @@ }, "node_modules/playwright-core": { "version": "1.50.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.50.1.tgz", - "integrity": "sha512-ra9fsNWayuYumt+NiM069M6OkcRb1FZSK8bgi66AtpFoWkg2+y0bJSNmkFrWhMbEBbVKC/EruAHH3g0zmtwGmQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -7842,25 +6034,8 @@ "node": ">=18" } }, - "node_modules/playwright/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/postcss": { "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "funding": [ { "type": "opencollective", @@ -7887,8 +6062,6 @@ }, "node_modules/postcss-selector-parser": { "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "dev": true, "license": "MIT", "dependencies": { @@ -7901,8 +6074,6 @@ }, "node_modules/postgres-array": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.2.tgz", - "integrity": "sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==", "license": "MIT", "engines": { "node": ">=12" @@ -7910,8 +6081,6 @@ }, "node_modules/postgres-bytea": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -7919,8 +6088,6 @@ }, "node_modules/postgres-date": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.1.0.tgz", - "integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==", "license": "MIT", "engines": { "node": ">=12" @@ -7928,8 +6095,6 @@ }, "node_modules/postgres-interval": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-4.0.2.tgz", - "integrity": "sha512-EMsphSQ1YkQqKZL2cuG0zHkmjCCzQqQ71l2GXITqRwjhRleCdv00bDk/ktaSi0LnlaPzAc3535KTrjXsTdtx7A==", "license": "MIT", "engines": { "node": ">=12" @@ -7937,8 +6102,7 @@ }, "node_modules/prebuild-install": { "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "license": "MIT", "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", @@ -7962,8 +6126,6 @@ }, "node_modules/prelude-ls": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", "engines": { @@ -7972,8 +6134,6 @@ }, "node_modules/prettier": { "version": "3.5.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.1.tgz", - "integrity": "sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw==", "dev": true, "license": "MIT", "peer": true, @@ -7989,8 +6149,6 @@ }, "node_modules/prettier-linter-helpers": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", "dev": true, "license": "MIT", "dependencies": { @@ -8002,8 +6160,6 @@ }, "node_modules/pretty-ms": { "version": "9.2.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.2.0.tgz", - "integrity": "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==", "dev": true, "license": "MIT", "dependencies": { @@ -8018,14 +6174,12 @@ }, "node_modules/promise-inflight": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "license": "ISC", "optional": true }, "node_modules/promise-retry": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "license": "MIT", "optional": true, "dependencies": { "err-code": "^2.0.2", @@ -8037,15 +6191,11 @@ }, "node_modules/proto-list": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", "dev": true, "license": "ISC" }, "node_modules/proxy-addr": { "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -8058,13 +6208,11 @@ "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, "node_modules/pump": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -8072,8 +6220,6 @@ }, "node_modules/punycode": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "license": "MIT", "engines": { @@ -8082,8 +6228,6 @@ }, "node_modules/qs": { "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.0.6" @@ -8097,8 +6241,6 @@ }, "node_modules/queue-microtask": { "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "funding": [ { "type": "github", @@ -8117,8 +6259,6 @@ }, "node_modules/range-parser": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -8126,8 +6266,6 @@ }, "node_modules/raw-body": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -8141,8 +6279,6 @@ }, "node_modules/raw-body/node_modules/iconv-lite": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -8153,8 +6289,7 @@ }, "node_modules/rc": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -8167,16 +6302,13 @@ }, "node_modules/rc/node_modules/strip-json-comments": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/read-package-json-fast": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-4.0.0.tgz", - "integrity": "sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==", "dev": true, "license": "ISC", "dependencies": { @@ -8189,8 +6321,7 @@ }, "node_modules/readable-stream": { "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -8202,8 +6333,6 @@ }, "node_modules/rechoir": { "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", "license": "MIT", "dependencies": { "resolve": "^1.20.0" @@ -8214,20 +6343,10 @@ }, "node_modules/reflect-metadata": { "version": "0.2.2", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", - "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", "license": "Apache-2.0" }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "license": "MIT" - }, "node_modules/require-directory": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, "license": "MIT", "engines": { @@ -8236,8 +6355,6 @@ }, "node_modules/resolve": { "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", "license": "MIT", "dependencies": { "is-core-module": "^2.16.0", @@ -8256,8 +6373,6 @@ }, "node_modules/resolve-from": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "license": "MIT", "engines": { @@ -8266,8 +6381,6 @@ }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "dev": true, "license": "MIT", "funding": { @@ -8276,8 +6389,7 @@ }, "node_modules/retry": { "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", "optional": true, "engines": { "node": ">= 4" @@ -8285,8 +6397,6 @@ }, "node_modules/reusify": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -8295,16 +6405,12 @@ }, "node_modules/rfdc": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "dev": true, "license": "MIT" }, "node_modules/rimraf": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", "optional": true, "dependencies": { "glob": "^7.1.3" @@ -8318,8 +6424,7 @@ }, "node_modules/rimraf/node_modules/brace-expansion": { "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", "optional": true, "dependencies": { "balanced-match": "^1.0.0", @@ -8328,9 +6433,7 @@ }, "node_modules/rimraf/node_modules/glob": { "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", "optional": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -8349,8 +6452,7 @@ }, "node_modules/rimraf/node_modules/minimatch": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", "optional": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -8361,8 +6463,6 @@ }, "node_modules/rollup": { "version": "4.34.8", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz", - "integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8400,8 +6500,6 @@ }, "node_modules/router": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.1.0.tgz", - "integrity": "sha512-/m/NSLxeYEgWNtyC+WtNHCF7jbGxOibVWKnn+1Psff4dJGOfoXP+MuC/f2CwSmyiHdOIzYnYFp4W6GxWfekaLA==", "license": "MIT", "dependencies": { "is-promise": "^4.0.0", @@ -8414,15 +6512,11 @@ }, "node_modules/rrweb-cssom": { "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", - "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", "dev": true, "license": "MIT" }, "node_modules/run-applescript": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", - "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", "dev": true, "license": "MIT", "engines": { @@ -8434,8 +6528,6 @@ }, "node_modules/run-parallel": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "funding": [ { "type": "github", @@ -8457,8 +6549,6 @@ }, "node_modules/safe-buffer": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "funding": [ { "type": "github", @@ -8477,14 +6567,10 @@ }, "node_modules/safer-buffer": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, "node_modules/saxes": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", "dev": true, "license": "ISC", "dependencies": { @@ -8496,8 +6582,6 @@ }, "node_modules/semver": { "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -8508,8 +6592,6 @@ }, "node_modules/send": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.1.0.tgz", - "integrity": "sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==", "license": "MIT", "dependencies": { "debug": "^4.3.5", @@ -8531,8 +6613,6 @@ }, "node_modules/send/node_modules/fresh": { "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -8540,8 +6620,6 @@ }, "node_modules/send/node_modules/mime-db": { "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -8549,8 +6627,6 @@ }, "node_modules/send/node_modules/mime-types": { "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -8561,8 +6637,6 @@ }, "node_modules/serve-static": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.1.0.tgz", - "integrity": "sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==", "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", @@ -8576,20 +6650,15 @@ }, "node_modules/set-blocking": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC", "optional": true }, "node_modules/setprototypeof": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, "node_modules/shebang-command": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", "dependencies": { @@ -8601,8 +6670,6 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", "engines": { @@ -8611,8 +6678,6 @@ }, "node_modules/shell-quote": { "version": "1.8.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", - "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", "dev": true, "license": "MIT", "engines": { @@ -8624,8 +6689,6 @@ }, "node_modules/side-channel": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -8643,8 +6706,6 @@ }, "node_modules/side-channel-list": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -8659,8 +6720,6 @@ }, "node_modules/side-channel-map": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -8677,8 +6736,6 @@ }, "node_modules/side-channel-weakmap": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -8696,15 +6753,11 @@ }, "node_modules/siginfo": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true, "license": "ISC" }, "node_modules/signal-exit": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", "engines": { @@ -8716,27 +6769,6 @@ }, "node_modules/simple-concat": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", "funding": [ { "type": "github", @@ -8751,6 +6783,25 @@ "url": "https://feross.org/support" } ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", @@ -8759,8 +6810,6 @@ }, "node_modules/sirv": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", - "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", "dev": true, "license": "MIT", "dependencies": { @@ -8774,8 +6823,6 @@ }, "node_modules/slash": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "license": "MIT", "engines": { "node": ">=8" @@ -8783,8 +6830,7 @@ }, "node_modules/smart-buffer": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", "optional": true, "engines": { "node": ">= 6.0.0", @@ -8793,8 +6839,7 @@ }, "node_modules/socks": { "version": "2.8.4", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", - "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", + "license": "MIT", "optional": true, "dependencies": { "ip-address": "^9.0.5", @@ -8807,8 +6852,7 @@ }, "node_modules/socks-proxy-agent": { "version": "6.2.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", - "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", + "license": "MIT", "optional": true, "dependencies": { "agent-base": "^6.0.2", @@ -8821,8 +6865,7 @@ }, "node_modules/socks-proxy-agent/node_modules/agent-base": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", "optional": true, "dependencies": { "debug": "4" @@ -8833,8 +6876,6 @@ }, "node_modules/source-map-js": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -8842,8 +6883,6 @@ }, "node_modules/speakingurl": { "version": "14.0.1", - "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", - "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -8852,8 +6891,6 @@ }, "node_modules/split2": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", "license": "ISC", "engines": { "node": ">= 10.x" @@ -8861,15 +6898,13 @@ }, "node_modules/sprintf-js": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause", "optional": true }, "node_modules/sqlite3": { "version": "5.1.7", - "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", - "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", "hasInstallScript": true, + "license": "BSD-3-Clause", "dependencies": { "bindings": "^1.5.0", "node-addon-api": "^7.0.0", @@ -8890,8 +6925,6 @@ }, "node_modules/sqlstring": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", - "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -8899,16 +6932,14 @@ }, "node_modules/sqlstring-sqlite": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/sqlstring-sqlite/-/sqlstring-sqlite-0.1.1.tgz", - "integrity": "sha512-9CAYUJ0lEUPYJrswqiqdINNSfq3jqWo/bFJ7tufdoNeSK0Fy+d1kFTxjqO9PIqza0Kri+ZtYMfPVf1aZaFOvrQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/ssri": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", - "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "license": "ISC", "optional": true, "dependencies": { "minipass": "^3.1.1" @@ -8919,8 +6950,7 @@ }, "node_modules/ssri/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "optional": true, "dependencies": { "yallist": "^4.0.0" @@ -8931,21 +6961,16 @@ }, "node_modules/ssri/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", "optional": true }, "node_modules/stackback": { "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true, "license": "MIT" }, "node_modules/statuses": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -8953,23 +6978,35 @@ }, "node_modules/std-env": { "version": "3.8.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", - "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==", "dev": true, "license": "MIT" }, "node_modules/string_decoder": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" } }, "node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { @@ -8981,26 +7018,47 @@ "node": ">=8" } }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" } }, "node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -9010,24 +7068,16 @@ "node": ">=8" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, "engines": { "node": ">=8" } }, "node_modules/strip-bom": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, "license": "MIT", "engines": { @@ -9036,8 +7086,6 @@ }, "node_modules/strip-final-newline": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", - "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", "dev": true, "license": "MIT", "engines": { @@ -9049,8 +7097,6 @@ }, "node_modules/strip-json-comments": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "license": "MIT", "engines": { @@ -9062,8 +7108,6 @@ }, "node_modules/superjson": { "version": "2.2.2", - "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz", - "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==", "dev": true, "license": "MIT", "dependencies": { @@ -9075,8 +7119,6 @@ }, "node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "dependencies": { @@ -9088,8 +7130,6 @@ }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -9100,21 +7140,15 @@ }, "node_modules/svg-tags": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", - "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", "dev": true }, "node_modules/symbol-tree": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true, "license": "MIT" }, "node_modules/synckit": { "version": "0.9.2", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", - "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", "dev": true, "license": "MIT", "dependencies": { @@ -9130,8 +7164,7 @@ }, "node_modules/tar": { "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "license": "ISC", "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -9146,8 +7179,7 @@ }, "node_modules/tar-fs": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz", - "integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==", + "license": "MIT", "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", @@ -9157,13 +7189,11 @@ }, "node_modules/tar-fs/node_modules/chownr": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + "license": "ISC" }, "node_modules/tar-stream": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -9177,54 +7207,24 @@ }, "node_modules/tar/node_modules/minipass": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", "engines": { "node": ">=8" } }, "node_modules/tar/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "license": "ISC" }, "node_modules/tarn": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz", - "integrity": "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==", "license": "MIT", "engines": { "node": ">=8.0.0" } }, - "node_modules/tesseract.js": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-6.0.0.tgz", - "integrity": "sha512-tqYCod1HwJzkeZw1l6XWx+ly2hhisGcBtak9MArhYwDAxL0NgeVhLJcUjqPxZMQtpgtVUzWcpZPryi+hnaQGVw==", - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "bmp-js": "^0.1.0", - "idb-keyval": "^6.2.0", - "is-url": "^1.2.4", - "node-fetch": "^2.6.9", - "opencollective-postinstall": "^2.0.3", - "regenerator-runtime": "^0.13.3", - "tesseract.js-core": "^6.0.0", - "wasm-feature-detect": "^1.2.11", - "zlibjs": "^0.3.1" - } - }, - "node_modules/tesseract.js-core": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-6.0.0.tgz", - "integrity": "sha512-1Qncm/9oKM7xgrQXZXNB+NRh19qiXGhxlrR8EwFbK5SaUbPZnS5OMtP/ghtqfd23hsr1ZvZbZjeuAGcMxd/ooA==", - "license": "Apache-2.0" - }, "node_modules/tildify": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz", - "integrity": "sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==", "license": "MIT", "engines": { "node": ">=8" @@ -9232,22 +7232,16 @@ }, "node_modules/tinybench": { "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "dev": true, "license": "MIT" }, "node_modules/tinyexec": { "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", "dev": true, "license": "MIT" }, "node_modules/tinypool": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", - "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", "dev": true, "license": "MIT", "engines": { @@ -9256,8 +7250,6 @@ }, "node_modules/tinyrainbow": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", "dev": true, "license": "MIT", "engines": { @@ -9266,8 +7258,6 @@ }, "node_modules/tinyspy": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", - "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", "dev": true, "license": "MIT", "engines": { @@ -9275,29 +7265,23 @@ } }, "node_modules/tldts": { - "version": "6.1.78", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.78.tgz", - "integrity": "sha512-fSgYrW0ITH0SR/CqKMXIruYIPpNu5aDgUp22UhYoSrnUQwc7SBqifEBFNce7AAcygUPBo6a/gbtcguWdmko4RQ==", + "version": "6.1.77", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^6.1.78" + "tldts-core": "^6.1.77" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "6.1.78", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.78.tgz", - "integrity": "sha512-jS0svNsB99jR6AJBmfmEWuKIgz91Haya91Z43PATaeHJ24BkMoNRb/jlaD37VYjb0mYf6gRL/HOnvS1zEnYBiw==", + "version": "6.1.77", "dev": true, "license": "MIT" }, "node_modules/to-regex-range": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -9308,8 +7292,6 @@ }, "node_modules/toidentifier": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "license": "MIT", "engines": { "node": ">=0.6" @@ -9317,8 +7299,6 @@ }, "node_modules/totalist": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", "dev": true, "license": "MIT", "engines": { @@ -9327,8 +7307,6 @@ }, "node_modules/tough-cookie": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.1.tgz", - "integrity": "sha512-Ek7HndSVkp10hmHP9V4qZO1u+pn1RU5sI0Fw+jCU3lyvuMZcgqsNgc6CmJJZyByK4Vm/qotGRJlfgAX8q+4JiA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -9340,8 +7318,6 @@ }, "node_modules/tr46": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", - "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", "dev": true, "license": "MIT", "dependencies": { @@ -9353,8 +7329,6 @@ }, "node_modules/ts-api-utils": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz", - "integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==", "dev": true, "license": "MIT", "engines": { @@ -9366,8 +7340,6 @@ }, "node_modules/ts-morph": { "version": "25.0.1", - "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-25.0.1.tgz", - "integrity": "sha512-QJEiTdnz1YjrB3JFhd626gX4rKHDLSjSVMvGGG4v7ONc3RBwa0Eei98G9AT9uNFDMtV54JyuXsFeC+OH0n6bXQ==", "license": "MIT", "dependencies": { "@ts-morph/common": "~0.26.0", @@ -9376,8 +7348,6 @@ }, "node_modules/ts-node": { "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9420,8 +7390,6 @@ }, "node_modules/tsconfig-paths": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", "dev": true, "license": "MIT", "dependencies": { @@ -9435,15 +7403,11 @@ }, "node_modules/tslib": { "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, "license": "0BSD" }, "node_modules/tsx": { "version": "4.19.3", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.3.tgz", - "integrity": "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9460,26 +7424,9 @@ "fsevents": "~2.3.3" } }, - "node_modules/tsx/node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/tunnel-agent": { "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" }, @@ -9489,8 +7436,6 @@ }, "node_modules/type-check": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "license": "MIT", "dependencies": { @@ -9502,8 +7447,6 @@ }, "node_modules/type-fest": { "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -9515,8 +7458,6 @@ }, "node_modules/type-is": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.0.tgz", - "integrity": "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==", "license": "MIT", "dependencies": { "content-type": "^1.0.5", @@ -9529,8 +7470,6 @@ }, "node_modules/typescript": { "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "devOptional": true, "license": "Apache-2.0", "bin": { @@ -9543,8 +7482,6 @@ }, "node_modules/typescript-eslint": { "version": "8.24.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.24.1.tgz", - "integrity": "sha512-cw3rEdzDqBs70TIcb0Gdzbt6h11BSs2pS0yaq7hDWDBtCCSei1pPSUXE9qUdQ/Wm9NgFg8mKtMt1b8fTHIl1jA==", "dev": true, "license": "MIT", "dependencies": { @@ -9566,15 +7503,11 @@ }, "node_modules/undici-types": { "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", "dev": true, "license": "MIT" }, "node_modules/unicorn-magic": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", - "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", "dev": true, "license": "MIT", "engines": { @@ -9586,8 +7519,7 @@ }, "node_modules/unique-filename": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "license": "ISC", "optional": true, "dependencies": { "unique-slug": "^2.0.0" @@ -9595,8 +7527,7 @@ }, "node_modules/unique-slug": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "license": "ISC", "optional": true, "dependencies": { "imurmurhash": "^0.1.4" @@ -9604,8 +7535,6 @@ }, "node_modules/universalify": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "license": "MIT", "engines": { "node": ">= 10.0.0" @@ -9613,8 +7542,6 @@ }, "node_modules/unpipe": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -9622,8 +7549,6 @@ }, "node_modules/update-browserslist-db": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", - "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", "dev": true, "funding": [ { @@ -9653,8 +7578,6 @@ }, "node_modules/uri-js": { "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -9663,14 +7586,10 @@ }, "node_modules/util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, "node_modules/utils-merge": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "license": "MIT", "engines": { "node": ">= 0.4.0" @@ -9690,15 +7609,11 @@ }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true, "license": "MIT" }, "node_modules/vary": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -9706,8 +7621,6 @@ }, "node_modules/vite": { "version": "6.1.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.1.1.tgz", - "integrity": "sha512-4GgM54XrwRfrOp297aIYspIti66k56v16ZnqHvrIM7mG+HjDlAwS7p+Srr7J6fGvEdOJ5JcQ/D9T7HhtdXDTzA==", "dev": true, "license": "MIT", "dependencies": { @@ -9778,8 +7691,6 @@ }, "node_modules/vite-hot-client": { "version": "0.2.4", - "resolved": "https://registry.npmjs.org/vite-hot-client/-/vite-hot-client-0.2.4.tgz", - "integrity": "sha512-a1nzURqO7DDmnXqabFOliz908FRmIppkBKsJthS8rbe8hBEXwEwe4C3Pp33Z1JoFCYfVL4kTOMLKk0ZZxREIeA==", "dev": true, "license": "MIT", "funding": { @@ -9791,8 +7702,6 @@ }, "node_modules/vite-node": { "version": "3.0.6", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.6.tgz", - "integrity": "sha512-s51RzrTkXKJrhNbUzQRsarjmAae7VmMPAsRT7lppVpIg6mK3zGthP9Hgz0YQQKuNcF+Ii7DfYk3Fxz40jRmePw==", "dev": true, "license": "MIT", "dependencies": { @@ -9814,8 +7723,6 @@ }, "node_modules/vite-plugin-inspect": { "version": "0.8.9", - "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-0.8.9.tgz", - "integrity": "sha512-22/8qn+LYonzibb1VeFZmISdVao5kC22jmEKm24vfFE8siEn47EpVcCLYMv6iKOYMJfjSvSJfueOwcFCkUnV3A==", "dev": true, "license": "MIT", "dependencies": { @@ -9846,8 +7753,6 @@ }, "node_modules/vite-plugin-vue-devtools": { "version": "7.7.2", - "resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-7.7.2.tgz", - "integrity": "sha512-5V0UijQWiSBj32blkyPEqIbzc6HO9c1bwnBhx+ay2dzU0FakH+qMdNUT8nF9BvDE+i6I1U8CqCuJiO20vKEdQw==", "dev": true, "license": "MIT", "dependencies": { @@ -9868,8 +7773,6 @@ }, "node_modules/vite-plugin-vue-inspector": { "version": "5.3.1", - "resolved": "https://registry.npmjs.org/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-5.3.1.tgz", - "integrity": "sha512-cBk172kZKTdvGpJuzCCLg8lJ909wopwsu3Ve9FsL1XsnLBiRT9U3MePcqrgGHgCX2ZgkqZmAGR8taxw+TV6s7A==", "dev": true, "license": "MIT", "dependencies": { @@ -9887,282 +7790,8 @@ "vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0" } }, - "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", - "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", - "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", - "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", - "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", - "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", - "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", - "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", - "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", - "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", - "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", - "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", - "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", - "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", - "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", - "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", - "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/vite/node_modules/@esbuild/linux-x64": { "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", - "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", "cpu": [ "x64" ], @@ -10176,146 +7805,8 @@ "node": ">=18" } }, - "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", - "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", - "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", - "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", - "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", - "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", - "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", - "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", - "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/vite/node_modules/esbuild": { "version": "0.24.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", - "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -10355,8 +7846,6 @@ }, "node_modules/vitest": { "version": "3.0.6", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.6.tgz", - "integrity": "sha512-/iL1Sc5VeDZKPDe58oGK4HUFLhw6b5XdY1MYawjuSaDA4sEfYlY9HnS6aCEG26fX+MgUi7MwlduTBHHAI/OvMA==", "dev": true, "license": "MIT", "dependencies": { @@ -10425,15 +7914,11 @@ }, "node_modules/vscode-uri": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", - "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", "dev": true, "license": "MIT" }, "node_modules/vue": { "version": "3.5.13", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", - "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", "license": "MIT", "dependencies": { "@vue/compiler-dom": "3.5.13", @@ -10453,15 +7938,11 @@ }, "node_modules/vue-component-type-helpers": { "version": "2.2.2", - "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.2.2.tgz", - "integrity": "sha512-6lLY+n2xz2kCYshl59mL6gy8OUUTmkscmDFMO8i7Lj+QKwgnIFUZmM1i/iTYObtrczZVdw7UakPqDTGwVSGaRg==", "dev": true, "license": "MIT" }, "node_modules/vue-eslint-parser": { "version": "9.4.3", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", - "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==", "dev": true, "license": "MIT", "dependencies": { @@ -10485,8 +7966,6 @@ }, "node_modules/vue-eslint-parser/node_modules/eslint-scope": { "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -10502,8 +7981,6 @@ }, "node_modules/vue-eslint-parser/node_modules/espree": { "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -10522,7 +7999,6 @@ "version": "10.0.5", "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-10.0.5.tgz", "integrity": "sha512-9/gmDlCblz3i8ypu/afiIc/SUIfTTE1mr0mZhb9pk70xo2csHAM9mp2gdQ3KD2O0AM3Hz/5ypb+FycTj/lHlPQ==", - "license": "MIT", "dependencies": { "@intlify/core-base": "10.0.5", "@intlify/shared": "10.0.5", @@ -10540,8 +8016,6 @@ }, "node_modules/vue-router": { "version": "4.5.0", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.0.tgz", - "integrity": "sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==", "license": "MIT", "dependencies": { "@vue/devtools-api": "^6.6.4" @@ -10555,8 +8029,6 @@ }, "node_modules/vue-tsc": { "version": "2.2.2", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.2.tgz", - "integrity": "sha512-1icPKkxAA5KTAaSwg0wVWdE48EdsH8fgvcbAiqojP4jXKl6LEM3soiW1aG/zrWrFt8Mw1ncG2vG1PvpZpVfehA==", "dev": true, "license": "MIT", "dependencies": { @@ -10571,9 +8043,7 @@ } }, "node_modules/vuetify": { - "version": "3.7.13", - "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.7.13.tgz", - "integrity": "sha512-4+RuQU+zLtXhlN2eZUpKXums9ftzUzhMeiNEJvvJY4XdOzVwUCth2dTnEZkSF6EKdLHk3WhtRk0cIWXZxpBvcw==", + "version": "3.7.12", "license": "MIT", "engines": { "node": "^12.20 || >=14.13" @@ -10602,8 +8072,6 @@ }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", - "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", "dev": true, "license": "MIT", "dependencies": { @@ -10615,24 +8083,14 @@ }, "node_modules/w3c-xmlserializer/node_modules/xml-name-validator": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", - "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18" } }, - "node_modules/wasm-feature-detect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.8.0.tgz", - "integrity": "sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==", - "license": "Apache-2.0" - }, "node_modules/webidl-conversions": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -10641,8 +8099,6 @@ }, "node_modules/whatwg-encoding": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10654,8 +8110,6 @@ }, "node_modules/whatwg-encoding/node_modules/iconv-lite": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "license": "MIT", "dependencies": { @@ -10667,8 +8121,6 @@ }, "node_modules/whatwg-mimetype": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", "dev": true, "license": "MIT", "engines": { @@ -10677,8 +8129,6 @@ }, "node_modules/whatwg-url": { "version": "14.1.1", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.1.tgz", - "integrity": "sha512-mDGf9diDad/giZ/Sm9Xi2YcyzaFpbdLpJPr+E9fSkyQ7KpQD4SdFcugkRQYzhmfI4KeV4Qpnn2sKPdo+kmsgRQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10691,8 +8141,6 @@ }, "node_modules/which": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "devOptional": true, "license": "ISC", "dependencies": { @@ -10707,8 +8155,6 @@ }, "node_modules/why-is-node-running": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "license": "MIT", "dependencies": { @@ -10724,8 +8170,7 @@ }, "node_modules/wide-align": { "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", "optional": true, "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" @@ -10733,8 +8178,7 @@ }, "node_modules/wide-align/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", "optional": true, "engines": { "node": ">=8" @@ -10742,14 +8186,12 @@ }, "node_modules/wide-align/node_modules/emoji-regex": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", "optional": true }, "node_modules/wide-align/node_modules/string-width": { "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "optional": true, "dependencies": { "emoji-regex": "^8.0.0", @@ -10762,8 +8204,7 @@ }, "node_modules/wide-align/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", "optional": true, "dependencies": { "ansi-regex": "^5.0.1" @@ -10774,8 +8215,6 @@ }, "node_modules/word-wrap": { "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "license": "MIT", "engines": { @@ -10783,18 +8222,16 @@ } }, "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "8.1.0", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" @@ -10803,8 +8240,6 @@ "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { @@ -10819,16 +8254,60 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, "node_modules/ws": { "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, "license": "MIT", "engines": { @@ -10849,8 +8328,6 @@ }, "node_modules/xml-name-validator": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", - "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -10859,15 +8336,11 @@ }, "node_modules/xmlchars": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true, "license": "MIT" }, "node_modules/xtend": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "license": "MIT", "engines": { "node": ">=0.4" @@ -10875,8 +8348,6 @@ }, "node_modules/y18n": { "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, "license": "ISC", "engines": { @@ -10885,15 +8356,11 @@ }, "node_modules/yallist": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, "license": "ISC" }, "node_modules/yargs": { "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "license": "MIT", "dependencies": { @@ -10911,18 +8378,51 @@ }, "node_modules/yargs-parser": { "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, "license": "ISC", "engines": { "node": ">=12" } }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yn": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true, "license": "MIT", "engines": { @@ -10931,8 +8431,6 @@ }, "node_modules/yocto-queue": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", "engines": { @@ -10944,8 +8442,6 @@ }, "node_modules/yoctocolors": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", - "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==", "dev": true, "license": "MIT", "engines": { @@ -10954,15 +8450,6 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } - }, - "node_modules/zlibjs": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz", - "integrity": "sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==", - "license": "MIT", - "engines": { - "node": "*" - } } } } From ceef74f1afc8d766f6346874d23c3838dccef5bf Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Tue, 4 Mar 2025 15:50:20 +0100 Subject: [PATCH 002/106] feat: controller en service laag toegevoegd voor student/:id/classes --- backend/src/controllers/students.ts | 53 ++++++++++++++++++-- backend/src/data/classes/class-repository.ts | 7 +++ backend/src/interfaces/classes.ts | 17 +++++++ backend/src/routes/class.ts | 2 +- backend/src/routes/student.ts | 20 ++------ backend/src/services/students.ts | 51 ++++++++++++++++++- 6 files changed, 127 insertions(+), 23 deletions(-) create mode 100644 backend/src/interfaces/classes.ts diff --git a/backend/src/controllers/students.ts b/backend/src/controllers/students.ts index b129eb57..9b4b8f3c 100644 --- a/backend/src/controllers/students.ts +++ b/backend/src/controllers/students.ts @@ -1,13 +1,31 @@ import { Request, Response } from 'express'; -import { getStudentById } from "../services/students"; +import { getStudent, getStudentClasses, getStudentClassIds } from '../services/students'; +import { ClassDTO } from '../interfaces/classes'; -export async function getStudent( +export async function getAllStudentsHandler ( + req: Request, + res: Response, +): Promise { + try { + res.json({ + students: [ + '0', + '1', + ] + }); + } catch (error) { + console.error('Error fetching learning objects:', error); + res.status(500).json({ error: 'Internal server error' }); + } +} + +export async function getStudentHandler( req: Request, res: Response, ): Promise { try { const username = req.params.id; - const student = await getStudentById(username); + const student = await getStudent(username); if (!student) { res.status(404).json({ error: "Student not found" }); @@ -21,7 +39,34 @@ export async function getStudent( } res.json(student); } - + + } catch (error) { + console.error('Error fetching learning objects:', error); + res.status(500).json({ error: 'Internal server error' }); + } +} + +export async function getStudentClassesHandler ( + req: Request, + res: Response, +): Promise { + try { + const full = req.query.full === 'true'; + const username = req.params.id; + + let classes: ClassDTO[] | string[]; + if (full) classes = await getStudentClasses(username); + else classes = await getStudentClassIds(username); + + 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' }); diff --git a/backend/src/data/classes/class-repository.ts b/backend/src/data/classes/class-repository.ts index e3b9f959..a9455323 100644 --- a/backend/src/data/classes/class-repository.ts +++ b/backend/src/data/classes/class-repository.ts @@ -1,5 +1,6 @@ import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; import { Class } from '../../entities/classes/class.entity.js'; +import { Student } from '../../entities/users/student.entity.js'; export class ClassRepository extends DwengoEntityRepository { public findById(id: string): Promise { @@ -8,4 +9,10 @@ export class ClassRepository extends DwengoEntityRepository { public deleteById(id: string): Promise { return this.deleteWhere({ classId: id }); } + public findByStudent(student: Student): Promise { + return this.find( + { students: student }, + { populate: ["students", "teachers"] } // voegt student en teacher objecten toe + ) + } } diff --git a/backend/src/interfaces/classes.ts b/backend/src/interfaces/classes.ts new file mode 100644 index 00000000..78731b59 --- /dev/null +++ b/backend/src/interfaces/classes.ts @@ -0,0 +1,17 @@ +import { ClassJoinRequest } from "../entities/classes/class-join-request.entity"; +import { Student } from "../entities/users/student.entity"; +import { Teacher } from "../entities/users/teacher.entity"; + +export interface ClassDTO { + id: string; + displayName: string; + teachers: string[]; + students: string[]; + joinRequests: string[]; + endpoints?: { + classes: string; + questions: string; + invitations: string; + groups: string; + }; +} diff --git a/backend/src/routes/class.ts b/backend/src/routes/class.ts index e554a7f2..f310367d 100644 --- a/backend/src/routes/class.ts +++ b/backend/src/routes/class.ts @@ -19,7 +19,7 @@ router.get('/:id', (req, res) => { teachers: [ '0' ], students: [ '0' ], joinRequests: [ '0' ], - links: { + endpoints: { self: `${req.baseUrl}/${req.params.id}`, classes: `${req.baseUrl}/${req.params.id}/invitations`, questions: `${req.baseUrl}/${req.params.id}/assignments`, diff --git a/backend/src/routes/student.ts b/backend/src/routes/student.ts index 6a6280e6..ca74d2f7 100644 --- a/backend/src/routes/student.ts +++ b/backend/src/routes/student.ts @@ -1,27 +1,15 @@ import express from 'express' -import { getStudentById } from '../services/students'; -import { getStudent } from '../controllers/students'; +import { getAllStudentsHandler, getStudentClassesHandler, getStudentHandler } from '../controllers/students'; const router = express.Router(); // root endpoint used to search objects -router.get('/', (req, res) => { - res.json({ - students: [ - '0', - '1', - ] - }); -}); +router.get('/', getAllStudentsHandler); // information about a student's profile -router.get('/:id', getStudent); +router.get('/:id', getStudentHandler); // the list of classes a student is in -router.get('/:id/classes', (req, res) => { - res.json({ - classes: [ '0' ], - }); -}) +router.get('/:id/classes', getStudentClassesHandler); // the list of submissions a student has made router.get('/:id/submissions', (req, res) => { diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts index b2aa953b..d38103ed 100644 --- a/backend/src/services/students.ts +++ b/backend/src/services/students.ts @@ -1,7 +1,14 @@ -import { getStudentRepository } from "../data/repositories"; +import { getClassRepository, getStudentRepository } from "../data/repositories"; +import { Class } from "../entities/classes/class.entity"; +import { ClassDTO } from "../interfaces/classes"; import { StudentDTO } from "../interfaces/students"; -export async function getStudentById(username: string): Promise { +export async function getAllStudents(): Promise { + // TODO + return []; +} + +export async function getStudent(username: string): Promise { const studentRepository = getStudentRepository(); const student = await studentRepository.findByUsername(username); @@ -16,3 +23,43 @@ export async function getStudentById(username: string): Promise { + const studentRepository = getStudentRepository(); + const student = await studentRepository.findByUsername(username); + + if (!student) return []; + + const classRepository = getClassRepository(); + // a weird error when running npm run dev occurs when using .findByStudent + // the error says that the function could not be found which is weird + // because typescript does not throw any errors + const classes = await classRepository.find( + { students: student }, + { populate: ["students", "teachers"] } // voegt student en teacher objecten toe + ) + // const classes = await classRepository.findByStudent(student); + + if (!classes) return []; + + return classes; +} + +export async function getStudentClasses(username: string): Promise { + const classes = await fetchStudentClasses(username); + + return classes.map((cls: Class): ClassDTO => { + return { + id: cls.classId, + displayName: cls.displayName, + teachers: cls.teachers.map(teacher => teacher.username), + students: cls.students.map(student => student.username), + joinRequests: [], // TODO + } + }); +} + +export async function getStudentClassIds(username: string): Promise { + const classes = await fetchStudentClasses(username); + return classes.map(cls => cls.classId); // class is a native keyword +} + From e9d9e52f9d3fba770e40b89c62b77ca28e35fc02 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Tue, 4 Mar 2025 16:32:59 +0100 Subject: [PATCH 003/106] feat: class/:id service en controller laag geimplementeerd (BEVAT NOG BUG) --- backend/src/controllers/classes.ts | 29 +++++++++++++++++++++++++++++ backend/src/interfaces/classes.ts | 20 ++++++++++++++------ backend/src/routes/class.ts | 17 ++--------------- backend/src/services/class.ts | 12 ++++++++++++ backend/src/services/students.ts | 15 +++------------ 5 files changed, 60 insertions(+), 33 deletions(-) create mode 100644 backend/src/controllers/classes.ts create mode 100644 backend/src/services/class.ts diff --git a/backend/src/controllers/classes.ts b/backend/src/controllers/classes.ts new file mode 100644 index 00000000..f4c8666d --- /dev/null +++ b/backend/src/controllers/classes.ts @@ -0,0 +1,29 @@ +import { Request, Response } from 'express'; +import { getClass } from '../services/class'; + +export async function getClassHandler( + req: Request, + res: Response, +): Promise { + try { + const classId = req.params.id; + const cls = await getClass(classId); + + if (!cls) { + res.status(404).json({ error: "Student not found" }); + return; + } else { + cls.endpoints = { + self: `${req.baseUrl}/${req.params.id}`, + invitations: `${req.baseUrl}/${req.params.id}/invitations`, + assignments: `${req.baseUrl}/${req.params.id}/assignments`, + students: `${req.baseUrl}/${req.params.id}/students`, + } + + res.json(cls); + } + } catch (error) { + console.error('Error fetching learning objects:', error); + res.status(500).json({ error: 'Internal server error' }); + } +} \ No newline at end of file diff --git a/backend/src/interfaces/classes.ts b/backend/src/interfaces/classes.ts index 78731b59..5c285446 100644 --- a/backend/src/interfaces/classes.ts +++ b/backend/src/interfaces/classes.ts @@ -1,6 +1,4 @@ -import { ClassJoinRequest } from "../entities/classes/class-join-request.entity"; -import { Student } from "../entities/users/student.entity"; -import { Teacher } from "../entities/users/teacher.entity"; +import { Class } from "../entities/classes/class.entity"; export interface ClassDTO { id: string; @@ -9,9 +7,19 @@ export interface ClassDTO { students: string[]; joinRequests: string[]; endpoints?: { - classes: string; - questions: string; + self: string; invitations: string; - groups: string; + assignments: string; + students: string; }; } + +export function mapToClassDTO(cls: Class): ClassDTO { + return { + id: cls.classId, + displayName: cls.displayName, + teachers: cls.teachers.map(teacher => teacher.username), + students: cls.students.map(student => student.username), + joinRequests: [], // TODO + } +}; diff --git a/backend/src/routes/class.ts b/backend/src/routes/class.ts index f310367d..65ca9362 100644 --- a/backend/src/routes/class.ts +++ b/backend/src/routes/class.ts @@ -1,4 +1,5 @@ import express from 'express' +import { getClassHandler } from '../controllers/classes'; const router = express.Router(); // root endpoint used to search objects @@ -12,21 +13,7 @@ router.get('/', (req, res) => { }); // information about an class with id 'id' -router.get('/:id', (req, res) => { - res.json({ - id: req.params.id, - displayName: 'Klas 4B', - teachers: [ '0' ], - students: [ '0' ], - joinRequests: [ '0' ], - 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`, - } - }); -}) +router.get('/:id', getClassHandler); router.get('/:id/invitations', (req, res) => { res.json({ diff --git a/backend/src/services/class.ts b/backend/src/services/class.ts new file mode 100644 index 00000000..3a3e8322 --- /dev/null +++ b/backend/src/services/class.ts @@ -0,0 +1,12 @@ +import { getClassRepository } from "../data/repositories"; +import { ClassDTO, mapToClassDTO } from "../interfaces/classes"; + +export async function getClass(classId: string): Promise { + const classRepository = getClassRepository(); + const cls = await classRepository.findById(classId); + + if (!cls) return null; + else { + return mapToClassDTO(cls); + } +} \ No newline at end of file diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts index d38103ed..4bfd7673 100644 --- a/backend/src/services/students.ts +++ b/backend/src/services/students.ts @@ -1,6 +1,6 @@ import { getClassRepository, getStudentRepository } from "../data/repositories"; import { Class } from "../entities/classes/class.entity"; -import { ClassDTO } from "../interfaces/classes"; +import { ClassDTO, mapToClassDTO } from "../interfaces/classes"; import { StudentDTO } from "../interfaces/students"; export async function getAllStudents(): Promise { @@ -46,20 +46,11 @@ async function fetchStudentClasses(username: string): Promise { export async function getStudentClasses(username: string): Promise { const classes = await fetchStudentClasses(username); - - return classes.map((cls: Class): ClassDTO => { - return { - id: cls.classId, - displayName: cls.displayName, - teachers: cls.teachers.map(teacher => teacher.username), - students: cls.students.map(student => student.username), - joinRequests: [], // TODO - } - }); + return classes.map(mapToClassDTO); } export async function getStudentClassIds(username: string): Promise { const classes = await fetchStudentClasses(username); - return classes.map(cls => cls.classId); // class is a native keyword + return classes.map(cls => cls.classId); // 'class' is a native keyword } From 7a8673f66e51bda21e79026fc211bbfb82b904ee Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Tue, 4 Mar 2025 16:43:27 +0100 Subject: [PATCH 004/106] fix: duplicate lijn in package.json --- backend/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/package.json b/backend/package.json index 29c7ecbc..4911d3f4 100644 --- a/backend/package.json +++ b/backend/package.json @@ -17,7 +17,6 @@ "@mikro-orm/core": "^6.4.6", "@mikro-orm/postgresql": "^6.4.6", "@mikro-orm/reflection": "^6.4.6", - "@types/js-yaml": "^4.0.9", "axios": "^1.8.1", "@mikro-orm/sqlite": "6.4.6", "dotenv": "^16.4.7", From 55387066f093589fbbe98fa516c177e2e726c8ca Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Wed, 5 Mar 2025 15:48:35 +0100 Subject: [PATCH 005/106] fix: @entity(...) decorator gefixt in backend/src/entities --- backend/src/entities/assignments/assignment.entity.ts | 3 ++- backend/src/entities/assignments/group.entity.ts | 3 ++- backend/src/entities/assignments/submission.entity.ts | 3 ++- backend/src/entities/classes/class-join-request.entity.ts | 3 ++- backend/src/entities/classes/class.entity.ts | 3 ++- backend/src/entities/classes/teacher-invitation.entity.ts | 3 ++- backend/src/entities/content/attachment.entity.ts | 3 ++- backend/src/entities/content/learning-object.entity.ts | 3 ++- backend/src/entities/content/learning-path.entity.ts | 3 ++- backend/src/entities/questions/answer.entity.ts | 3 ++- backend/src/entities/questions/question.entity.ts | 3 ++- backend/src/entities/users/teacher.entity.ts | 3 ++- 12 files changed, 24 insertions(+), 12 deletions(-) diff --git a/backend/src/entities/assignments/assignment.entity.ts b/backend/src/entities/assignments/assignment.entity.ts index e883632b..785a8b9c 100644 --- a/backend/src/entities/assignments/assignment.entity.ts +++ b/backend/src/entities/assignments/assignment.entity.ts @@ -9,8 +9,9 @@ import { import { Class } from '../classes/class.entity.js'; import { Group } from './group.entity.js'; import { Language } from '../content/language.js'; +import { AssignmentRepository } from '../../data/assignments/assignment-repository.js'; -@Entity() +@Entity({ repository: () => AssignmentRepository }) export class Assignment { @ManyToOne({ entity: () => Class, primary: true }) within!: Class; diff --git a/backend/src/entities/assignments/group.entity.ts b/backend/src/entities/assignments/group.entity.ts index 80da7d8b..cb4cd6e6 100644 --- a/backend/src/entities/assignments/group.entity.ts +++ b/backend/src/entities/assignments/group.entity.ts @@ -1,8 +1,9 @@ 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'; -@Entity() +@Entity({ repository: () => GroupRepository }) export class Group { @ManyToOne({ entity: () => Assignment, primary: true }) assignment!: Assignment; diff --git a/backend/src/entities/assignments/submission.entity.ts b/backend/src/entities/assignments/submission.entity.ts index 02cbeeae..26269020 100644 --- a/backend/src/entities/assignments/submission.entity.ts +++ b/backend/src/entities/assignments/submission.entity.ts @@ -2,8 +2,9 @@ import { Student } from '../users/student.entity.js'; import { Group } from './group.entity.js'; import { Entity, Enum, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'; import { Language } from '../content/language.js'; +import { SubmissionRepository } from '../../data/assignments/submission-repository.js'; -@Entity() +@Entity({ repository: () => SubmissionRepository }) export class Submission { @PrimaryKey({ type: 'string' }) learningObjectHruid!: string; diff --git a/backend/src/entities/classes/class-join-request.entity.ts b/backend/src/entities/classes/class-join-request.entity.ts index 9a883688..0636e044 100644 --- a/backend/src/entities/classes/class-join-request.entity.ts +++ b/backend/src/entities/classes/class-join-request.entity.ts @@ -1,8 +1,9 @@ import { Entity, Enum, ManyToOne } from '@mikro-orm/core'; import { Student } from '../users/student.entity.js'; import { Class } from './class.entity.js'; +import { ClassJoinRequestRepository } from '../../data/classes/class-join-request-repository.js'; -@Entity() +@Entity({ repository: () => ClassJoinRequestRepository }) export class ClassJoinRequest { @ManyToOne({ entity: () => Student, primary: true }) requester!: Student; diff --git a/backend/src/entities/classes/class.entity.ts b/backend/src/entities/classes/class.entity.ts index 1f5835d2..06d31479 100644 --- a/backend/src/entities/classes/class.entity.ts +++ b/backend/src/entities/classes/class.entity.ts @@ -8,8 +8,9 @@ import { import { v4 } from 'uuid'; import { Teacher } from '../users/teacher.entity.js'; import { Student } from '../users/student.entity.js'; +import { ClassRepository } from '../../data/classes/class-repository.js'; -@Entity() +@Entity({ repository: () => ClassRepository }) export class Class { @PrimaryKey() classId = v4(); diff --git a/backend/src/entities/classes/teacher-invitation.entity.ts b/backend/src/entities/classes/teacher-invitation.entity.ts index 375bf719..42700d3a 100644 --- a/backend/src/entities/classes/teacher-invitation.entity.ts +++ b/backend/src/entities/classes/teacher-invitation.entity.ts @@ -1,11 +1,12 @@ import { Entity, ManyToOne } from '@mikro-orm/core'; import { Teacher } from '../users/teacher.entity.js'; import { Class } from './class.entity.js'; +import { TeacherInvitationRepository } from '../../data/classes/teacher-invitation-repository.js'; /** * Invitation of a teacher into a class (in order to teach it). */ -@Entity() +@Entity({ repository: () => TeacherInvitationRepository }) export class TeacherInvitation { @ManyToOne({ entity: () => Teacher, primary: true }) sender!: Teacher; diff --git a/backend/src/entities/content/attachment.entity.ts b/backend/src/entities/content/attachment.entity.ts index 5a77d4b7..7f14d178 100644 --- a/backend/src/entities/content/attachment.entity.ts +++ b/backend/src/entities/content/attachment.entity.ts @@ -1,7 +1,8 @@ import { Entity, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'; import { LearningObject } from './learning-object.entity.js'; +import { AttachmentRepository } from '../../data/content/attachment-repository.js'; -@Entity() +@Entity({ repository: () => AttachmentRepository }) export class Attachment { @ManyToOne({ entity: () => LearningObject, primary: true }) learningObject!: LearningObject; diff --git a/backend/src/entities/content/learning-object.entity.ts b/backend/src/entities/content/learning-object.entity.ts index aeee268d..179a2f5f 100644 --- a/backend/src/entities/content/learning-object.entity.ts +++ b/backend/src/entities/content/learning-object.entity.ts @@ -11,8 +11,9 @@ import { import { Language } from './language.js'; import { Attachment } from './attachment.entity.js'; import { Teacher } from '../users/teacher.entity.js'; +import { LearningObjectRepository } from '../../data/content/learning-object-repository.js'; -@Entity() +@Entity({ repository: () => LearningObjectRepository }) export class LearningObject { @PrimaryKey({ type: 'string' }) hruid!: string; diff --git a/backend/src/entities/content/learning-path.entity.ts b/backend/src/entities/content/learning-path.entity.ts index f426cdfe..de62aa15 100644 --- a/backend/src/entities/content/learning-path.entity.ts +++ b/backend/src/entities/content/learning-path.entity.ts @@ -10,8 +10,9 @@ import { } from '@mikro-orm/core'; import { Language } from './language.js'; import { Teacher } from '../users/teacher.entity.js'; +import { LearningPathRepository } from '../../data/content/learning-path-repository.js'; -@Entity() +@Entity({ repository: () => LearningPathRepository }) export class LearningPath { @PrimaryKey({ type: 'string' }) hruid!: string; diff --git a/backend/src/entities/questions/answer.entity.ts b/backend/src/entities/questions/answer.entity.ts index d07a78ca..7fb64575 100644 --- a/backend/src/entities/questions/answer.entity.ts +++ b/backend/src/entities/questions/answer.entity.ts @@ -1,8 +1,9 @@ import { Entity, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'; import { Question } from './question.entity.js'; import { Teacher } from '../users/teacher.entity.js'; +import { AnswerRepository } from '../../data/questions/answer-repository.js'; -@Entity() +@Entity({ repository: () => AnswerRepository }) export class Answer { @ManyToOne({ entity: () => Teacher, primary: true }) author!: Teacher; diff --git a/backend/src/entities/questions/question.entity.ts b/backend/src/entities/questions/question.entity.ts index 6c0d07e5..67efdfe7 100644 --- a/backend/src/entities/questions/question.entity.ts +++ b/backend/src/entities/questions/question.entity.ts @@ -1,8 +1,9 @@ import { Entity, Enum, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'; import { Language } from '../content/language.js'; import { Student } from '../users/student.entity.js'; +import { QuestionRepository } from '../../data/questions/question-repository.js'; -@Entity() +@Entity({ repository: () => QuestionRepository }) export class Question { @PrimaryKey({ type: 'string' }) learningObjectHruid!: string; diff --git a/backend/src/entities/users/teacher.entity.ts b/backend/src/entities/users/teacher.entity.ts index 2327527c..d53ca603 100644 --- a/backend/src/entities/users/teacher.entity.ts +++ b/backend/src/entities/users/teacher.entity.ts @@ -1,8 +1,9 @@ import { Collection, Entity, ManyToMany } from '@mikro-orm/core'; import { User } from './user.entity.js'; import { Class } from '../classes/class.entity.js'; +import { TeacherRepository } from '../../data/users/teacher-repository.js'; -@Entity() +@Entity({ repository: () => TeacherRepository }) export class Teacher extends User { @ManyToMany(() => Class) classes!: Collection; From 123fdf0fa11b0311399f50f0fbc7c64c9b91b318 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Wed, 5 Mar 2025 15:57:29 +0100 Subject: [PATCH 006/106] fix: code teruggezet die veroorzaakt werd door bug in backend/src/entities --- backend/src/services/students.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts index 4bfd7673..3a1b601f 100644 --- a/backend/src/services/students.ts +++ b/backend/src/services/students.ts @@ -30,14 +30,7 @@ async function fetchStudentClasses(username: string): Promise { if (!student) return []; const classRepository = getClassRepository(); - // a weird error when running npm run dev occurs when using .findByStudent - // the error says that the function could not be found which is weird - // because typescript does not throw any errors - const classes = await classRepository.find( - { students: student }, - { populate: ["students", "teachers"] } // voegt student en teacher objecten toe - ) - // const classes = await classRepository.findByStudent(student); + const classes = await classRepository.findByStudent(student); if (!classes) return []; From 241fe0103f04761d436e36ba8c626611c60761b7 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Wed, 5 Mar 2025 16:31:27 +0100 Subject: [PATCH 007/106] feat: endpoints voor /, /:id en /:id/students in routes/class.ts zijn geimplementeerd --- backend/src/controllers/classes.ts | 29 +++++++++++++++++- backend/src/controllers/students.ts | 11 ++++--- backend/src/data/classes/class-repository.ts | 5 ++- backend/src/interfaces/students.ts | 11 +++++++ backend/src/routes/class.ts | 19 ++---------- backend/src/services/class.ts | 32 ++++++++++++++++++++ backend/src/services/students.ts | 22 +++++++------- 7 files changed, 95 insertions(+), 34 deletions(-) diff --git a/backend/src/controllers/classes.ts b/backend/src/controllers/classes.ts index f4c8666d..ee932734 100644 --- a/backend/src/controllers/classes.ts +++ b/backend/src/controllers/classes.ts @@ -1,5 +1,18 @@ import { Request, Response } from 'express'; -import { getClass } from '../services/class'; +import { getAllClasses, getClass, getClassStudents } from '../services/class'; +import { ClassDTO } from '../interfaces/classes'; + +export async function getAllClassesHandler( + req: Request, + res: Response, +): Promise { + const full = req.query.full === "true"; + const classes = await getAllClasses(full); + + res.json({ + classes: classes + }); +} export async function getClassHandler( req: Request, @@ -26,4 +39,18 @@ export async function getClassHandler( console.error('Error fetching learning objects:', error); res.status(500).json({ error: 'Internal server error' }); } +} + +export async function getClassStudentsHandler( + req: Request, + res: Response, +): Promise { + const classId = req.params.id; + const full = req.query.full === "true"; + + const students = await getClassStudents(classId, full); + + res.json({ + students: students, + }); } \ No newline at end of file diff --git a/backend/src/controllers/students.ts b/backend/src/controllers/students.ts index 9b4b8f3c..c6973632 100644 --- a/backend/src/controllers/students.ts +++ b/backend/src/controllers/students.ts @@ -1,17 +1,18 @@ import { Request, Response } from 'express'; -import { getStudent, getStudentClasses, getStudentClassIds } from '../services/students'; +import { getAllStudents, getStudent, getStudentClasses, getStudentClassIds } from '../services/students'; import { ClassDTO } from '../interfaces/classes'; +// TODO: accept arguments (full, ...) +// TODO: endpoints export async function getAllStudentsHandler ( req: Request, res: Response, ): Promise { try { + const students = await getAllStudents(); + res.json({ - students: [ - '0', - '1', - ] + students: students }); } catch (error) { console.error('Error fetching learning objects:', error); diff --git a/backend/src/data/classes/class-repository.ts b/backend/src/data/classes/class-repository.ts index a9455323..cd0e44fc 100644 --- a/backend/src/data/classes/class-repository.ts +++ b/backend/src/data/classes/class-repository.ts @@ -4,7 +4,10 @@ import { Student } from '../../entities/users/student.entity.js'; export class ClassRepository extends DwengoEntityRepository { public findById(id: string): Promise { - return this.findOne({ classId: id }); + return this.findOne( + { classId: id }, + { populate: ["students", "teachers"] }, + ); } public deleteById(id: string): Promise { return this.deleteWhere({ classId: id }); diff --git a/backend/src/interfaces/students.ts b/backend/src/interfaces/students.ts index cb13d3c2..e87e707d 100644 --- a/backend/src/interfaces/students.ts +++ b/backend/src/interfaces/students.ts @@ -1,3 +1,5 @@ +import { Student } from "../entities/users/student.entity"; + export interface StudentDTO { id: string; username: string; @@ -10,3 +12,12 @@ export interface StudentDTO { groups: string; }; } + +export function mapToStudentDTO(student: Student): StudentDTO { + return { + id: student.username, + username: student.username, + firstName: student.firstName, + lastName: student.lastName, + }; +} \ No newline at end of file diff --git a/backend/src/routes/class.ts b/backend/src/routes/class.ts index 65ca9362..e58547b2 100644 --- a/backend/src/routes/class.ts +++ b/backend/src/routes/class.ts @@ -1,16 +1,9 @@ import express from 'express' -import { getClassHandler } from '../controllers/classes'; +import { getAllClassesHandler, getClassHandler, getClassStudentsHandler } from '../controllers/classes'; const router = express.Router(); // root endpoint used to search objects -router.get('/', (req, res) => { - res.json({ - classes: [ - '0', - '1', - ] - }); -}); +router.get('/', getAllClassesHandler); // information about an class with id 'id' router.get('/:id', getClassHandler); @@ -31,12 +24,6 @@ router.get('/:id/assignments', (req, res) => { }); }) -router.get('/:id/students', (req, res) => { - res.json({ - students: [ - '0' - ], - }); -}) +router.get('/:id/students', getClassStudentsHandler); export default router \ No newline at end of file diff --git a/backend/src/services/class.ts b/backend/src/services/class.ts index 3a3e8322..89628404 100644 --- a/backend/src/services/class.ts +++ b/backend/src/services/class.ts @@ -1,5 +1,22 @@ import { getClassRepository } from "../data/repositories"; +import { Class } from "../entities/classes/class.entity"; import { ClassDTO, mapToClassDTO } from "../interfaces/classes"; +import { mapToStudentDTO, StudentDTO } from "../interfaces/students"; + +export async function getAllClasses(full: boolean): Promise { + const classRepository = getClassRepository(); + const classes = await classRepository.find({}, { populate: ["students", "teachers"] }); + + if (!classes) { + return []; + } + + if (full) { + return classes.map(mapToClassDTO); + } else { + return classes.map((cls) => cls.classId); + } +} export async function getClass(classId: string): Promise { const classRepository = getClassRepository(); @@ -9,4 +26,19 @@ export async function getClass(classId: string): Promise { else { return mapToClassDTO(cls); } +} + +export async function getClassStudents(classId: string, full: boolean): Promise { + const classRepository = getClassRepository(); + const cls = await classRepository.findById(classId); + + if (!cls) { + return []; + } + + if (full) { + return cls.students.map(mapToStudentDTO); + } else { + return cls.students.map((student) => student.username); + } } \ No newline at end of file diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts index 3a1b601f..a69cab85 100644 --- a/backend/src/services/students.ts +++ b/backend/src/services/students.ts @@ -1,26 +1,26 @@ import { getClassRepository, getStudentRepository } from "../data/repositories"; import { Class } from "../entities/classes/class.entity"; +import { Student } from "../entities/users/student.entity"; import { ClassDTO, mapToClassDTO } from "../interfaces/classes"; -import { StudentDTO } from "../interfaces/students"; +import { StudentDTO, mapToStudentDTO } from "../interfaces/students"; + export async function getAllStudents(): Promise { - // TODO - return []; + const studentRepository = getStudentRepository(); + const students = await studentRepository.find({}); + + return students.map(mapToStudentDTO); } export async function getStudent(username: string): Promise { const studentRepository = getStudentRepository(); const student = await studentRepository.findByUsername(username); - if (!student) return null; - else { - return { - id: student.username, - username: student.username, - firstName: student.firstName, - lastName: student.lastName, - } + if (!student) { + return null; } + + return mapToStudentDTO(student); } async function fetchStudentClasses(username: string): Promise { From cfd0cce2df839f8499cf729c0a948533b293e8dd Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Wed, 5 Mar 2025 17:45:09 +0100 Subject: [PATCH 008/106] feat: gestart met implementeren van assignment service en controller laag --- backend/src/app.ts | 2 -- backend/src/controllers/assignments.ts | 30 ++++++++++++++++++++++++++ backend/src/interfaces/assignments.ts | 25 +++++++++++++++++++++ backend/src/interfaces/groups.ts | 3 +++ backend/src/interfaces/list.ts | 5 +++++ backend/src/routes/assignment.ts | 20 +++++------------ backend/src/routes/class.ts | 12 ++++------- backend/src/services/assignments.ts | 20 +++++++++++++++++ 8 files changed, 92 insertions(+), 25 deletions(-) create mode 100644 backend/src/controllers/assignments.ts create mode 100644 backend/src/interfaces/assignments.ts create mode 100644 backend/src/interfaces/groups.ts create mode 100644 backend/src/interfaces/list.ts create mode 100644 backend/src/services/assignments.ts diff --git a/backend/src/app.ts b/backend/src/app.ts index b21bb9f1..e4da44e7 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -8,7 +8,6 @@ import learningObjectRoutes from './routes/learningObjects.js'; import studentRouter from './routes/student.js'; import groupRouter from './routes/group.js'; -import assignmentRouter from './routes/assignment.js'; import submissionRouter from './routes/submission.js'; import classRouter from './routes/class.js'; import questionRouter from './routes/question.js'; @@ -27,7 +26,6 @@ app.get('/', (_, res: Response) => { app.use('/student', studentRouter); app.use('/group', groupRouter); -app.use('/assignment', assignmentRouter); app.use('/submission', submissionRouter); app.use('/class', classRouter); app.use('/question', questionRouter); diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts new file mode 100644 index 00000000..775a60df --- /dev/null +++ b/backend/src/controllers/assignments.ts @@ -0,0 +1,30 @@ +import { Request, Response } from 'express' +import { getAssignment } from '../services/assignments'; + +// typescript is annoywith with parameter forwarding from class.ts +interface AssignmentParams { + classid: string; + id: string; +} + +export async function getAssignmentHandler( + req: Request, + res: Response, +): Promise { + const id = +req.params.id; + const classid = req.params.classid; + + if (isNaN(id)) { + res.status(400).json({ error: "Assignment id must be a number" }); + return; + } + + const assignment = await getAssignment(classid, id); + + if (!assignment) { + res.status(404).json({ error: "Assignment not found" }); + return; + } + + res.json(assignment); +} \ No newline at end of file diff --git a/backend/src/interfaces/assignments.ts b/backend/src/interfaces/assignments.ts new file mode 100644 index 00000000..a3c3c4cc --- /dev/null +++ b/backend/src/interfaces/assignments.ts @@ -0,0 +1,25 @@ +import { Assignment } from "../entities/assignments/assignment.entity"; +import { Class } from "../entities/classes/class.entity"; +import { GroupDTO } from "./groups"; + +export interface AssignmentDTO { + id: number, + class: string, // id of class 'within' + title: string, + description: string, + learningPath: string, + language: string, + groups?: GroupDTO[], // TODO +} + +export function mapToAssignmentDTO(assignment: Assignment, cls: Class): AssignmentDTO { + return { + id: assignment.id, + class: cls.classId, + title: assignment.title, + description: assignment.description, + learningPath: assignment.learningPathHruid, + language: assignment.learningPathLanguage, + //groups: assignment.groups.map(mapToGroupDTO), + }; +} \ No newline at end of file diff --git a/backend/src/interfaces/groups.ts b/backend/src/interfaces/groups.ts new file mode 100644 index 00000000..6056a906 --- /dev/null +++ b/backend/src/interfaces/groups.ts @@ -0,0 +1,3 @@ +export interface GroupDTO { + groupNumber: number, +} \ No newline at end of file diff --git a/backend/src/interfaces/list.ts b/backend/src/interfaces/list.ts new file mode 100644 index 00000000..0037403d --- /dev/null +++ b/backend/src/interfaces/list.ts @@ -0,0 +1,5 @@ +// TODO: implement something like this but with named endpoints +export interface List { + items: T[], + endpoints?: string[], +}; \ No newline at end of file diff --git a/backend/src/routes/assignment.ts b/backend/src/routes/assignment.ts index fcb6e9da..e9c91160 100644 --- a/backend/src/routes/assignment.ts +++ b/backend/src/routes/assignment.ts @@ -1,5 +1,8 @@ import express from 'express' -const router = express.Router(); +import { getAssignmentHandler } from '../controllers/assignments'; +const router = express.Router({ mergeParams: true }); + + // root endpoint used to search objects router.get('/', (req, res) => { @@ -12,20 +15,7 @@ router.get('/', (req, res) => { }); // information about an assignment with id 'id' -router.get('/:id', (req, res) => { - res.json({ - id: req.params.id, - title: 'Dit is een test assignment', - description: 'Een korte beschrijving', - groups: [ '0' ], - learningPath: '0', - class: '0', - links: { - self: `${req.baseUrl}/${req.params.id}`, - submissions: `${req.baseUrl}/${req.params.id}`, - }, - }); -}) +router.get('/:id', getAssignmentHandler); router.get('/:id/submissions', (req, res) => { res.json({ diff --git a/backend/src/routes/class.ts b/backend/src/routes/class.ts index e58547b2..bc502fb8 100644 --- a/backend/src/routes/class.ts +++ b/backend/src/routes/class.ts @@ -1,5 +1,7 @@ import express from 'express' import { getAllClassesHandler, getClassHandler, getClassStudentsHandler } from '../controllers/classes'; +import assignmentRouter from './assignment.js'; + const router = express.Router(); // root endpoint used to search objects @@ -16,14 +18,8 @@ router.get('/:id/invitations', (req, res) => { }); }) -router.get('/:id/assignments', (req, res) => { - res.json({ - assignments: [ - '0' - ], - }); -}) - router.get('/:id/students', getClassStudentsHandler); +router.use('/:classid/assignments', assignmentRouter); + export default router \ No newline at end of file diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts new file mode 100644 index 00000000..ab76f6a8 --- /dev/null +++ b/backend/src/services/assignments.ts @@ -0,0 +1,20 @@ +import { getAssignmentRepository, getClassRepository } from "../data/repositories"; +import { AssignmentDTO, mapToAssignmentDTO } from "../interfaces/assignments"; + +export async function getAssignment(classid: string, id: number): Promise { + const classRepository = getClassRepository(); + const cls = await classRepository.findById(classid); + + if (!cls) { + return null; + } + + const assignmentRepository = getAssignmentRepository(); + const assignment = await assignmentRepository.findByClassAndId(cls, id); + + if (!assignment) { + return null; + } + + return mapToAssignmentDTO(assignment, cls); +} \ No newline at end of file From 037763a810836e00db5a7f4bf5986fa6395e2f14 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Fri, 7 Mar 2025 10:47:20 +0100 Subject: [PATCH 009/106] fix: verwijder login route --- backend/src/app.ts | 2 -- backend/src/routes/login.ts | 14 -------------- 2 files changed, 16 deletions(-) delete mode 100644 backend/src/routes/login.ts diff --git a/backend/src/app.ts b/backend/src/app.ts index e4da44e7..83b4b998 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -11,7 +11,6 @@ import groupRouter from './routes/group.js'; import submissionRouter from './routes/submission.js'; import classRouter from './routes/class.js'; import questionRouter from './routes/question.js'; -import loginRouter from './routes/login.js'; const app: Express = express(); const port: string | number = getNumericEnvVar(EnvVars.Port); @@ -29,7 +28,6 @@ app.use('/group', groupRouter); app.use('/submission', submissionRouter); app.use('/class', classRouter); app.use('/question', questionRouter); -app.use('/login', loginRouter); app.use('/theme', themeRoutes); app.use('/learningPath', learningPathRoutes); diff --git a/backend/src/routes/login.ts b/backend/src/routes/login.ts deleted file mode 100644 index 550e6d93..00000000 --- a/backend/src/routes/login.ts +++ /dev/null @@ -1,14 +0,0 @@ -import express from 'express' -const router = express.Router(); - -// returns login paths for IDP -router.get('/', (req, res) => { - res.json({ - // dummy variables, needs to be changed - // with IDP endpoints - leerkracht: '/login-leerkracht', - leerling: '/login-leerling', - }); -}) - -export default router \ No newline at end of file From 3b71c80be6c5041078ad97688acfb909b74a521e Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Fri, 7 Mar 2025 10:55:44 +0100 Subject: [PATCH 010/106] fix: consistente naamgeving --- backend/src/app.ts | 24 +++++++++---------- backend/src/controllers/assignments.ts | 4 ++-- .../routes/{assignment.ts => assignments.ts} | 0 backend/src/routes/{class.ts => classes.ts} | 6 ++--- backend/src/routes/{group.ts => groups.ts} | 0 ...learningObjects.ts => learning-objects.ts} | 0 .../{learningPaths.ts => learning-paths.ts} | 0 .../src/routes/{question.ts => questions.ts} | 0 .../src/routes/{student.ts => students.ts} | 0 .../routes/{submission.ts => submissions.ts} | 0 .../src/routes/{teacher.ts => teachers.ts} | 0 11 files changed, 17 insertions(+), 17 deletions(-) rename backend/src/routes/{assignment.ts => assignments.ts} (100%) rename backend/src/routes/{class.ts => classes.ts} (85%) rename backend/src/routes/{group.ts => groups.ts} (100%) rename backend/src/routes/{learningObjects.ts => learning-objects.ts} (100%) rename backend/src/routes/{learningPaths.ts => learning-paths.ts} (100%) rename backend/src/routes/{question.ts => questions.ts} (100%) rename backend/src/routes/{student.ts => students.ts} (100%) rename backend/src/routes/{submission.ts => submissions.ts} (100%) rename backend/src/routes/{teacher.ts => teachers.ts} (100%) diff --git a/backend/src/app.ts b/backend/src/app.ts index 83b4b998..811c6cfc 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -3,14 +3,14 @@ import { initORM } from './orm.js'; import { EnvVars, getNumericEnvVar } from './util/envvars.js'; import themeRoutes from './routes/themes.js'; -import learningPathRoutes from './routes/learningPaths.js'; -import learningObjectRoutes from './routes/learningObjects.js'; +import learningPathRoutes from './routes/learning-paths.js'; +import learningObjectRoutes from './routes/learning-objects.js'; -import studentRouter from './routes/student.js'; -import groupRouter from './routes/group.js'; -import submissionRouter from './routes/submission.js'; -import classRouter from './routes/class.js'; -import questionRouter from './routes/question.js'; +import studentRoutes from './routes/students.js'; +import groupRoutes from './routes/groups.js'; +import submissionRoutes from './routes/submissions.js'; +import classRoutes from './routes/classes.js'; +import questionRoutes from './routes/questions.js'; const app: Express = express(); const port: string | number = getNumericEnvVar(EnvVars.Port); @@ -23,11 +23,11 @@ app.get('/', (_, res: Response) => { }); }); -app.use('/student', studentRouter); -app.use('/group', groupRouter); -app.use('/submission', submissionRouter); -app.use('/class', classRouter); -app.use('/question', questionRouter); +app.use('/student', studentRoutes); +app.use('/group', groupRoutes); +app.use('/submission', submissionRoutes); +app.use('/class', classRoutes); +app.use('/question', questionRoutes); app.use('/theme', themeRoutes); app.use('/learningPath', learningPathRoutes); diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index 775a60df..feabb1e1 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -1,7 +1,7 @@ import { Request, Response } from 'express' import { getAssignment } from '../services/assignments'; -// typescript is annoywith with parameter forwarding from class.ts +// typescript is annoywith with parameter forwarding from classes.ts interface AssignmentParams { classid: string; id: string; @@ -27,4 +27,4 @@ export async function getAssignmentHandler( } res.json(assignment); -} \ No newline at end of file +} diff --git a/backend/src/routes/assignment.ts b/backend/src/routes/assignments.ts similarity index 100% rename from backend/src/routes/assignment.ts rename to backend/src/routes/assignments.ts diff --git a/backend/src/routes/class.ts b/backend/src/routes/classes.ts similarity index 85% rename from backend/src/routes/class.ts rename to backend/src/routes/classes.ts index bc502fb8..420018a4 100644 --- a/backend/src/routes/class.ts +++ b/backend/src/routes/classes.ts @@ -1,6 +1,6 @@ import express from 'express' import { getAllClassesHandler, getClassHandler, getClassStudentsHandler } from '../controllers/classes'; -import assignmentRouter from './assignment.js'; +import assignmentRouter from './assignments.js'; const router = express.Router(); @@ -12,7 +12,7 @@ router.get('/:id', getClassHandler); router.get('/:id/invitations', (req, res) => { res.json({ - invitations: [ + invitations: [ '0' ], }); @@ -22,4 +22,4 @@ router.get('/:id/students', getClassStudentsHandler); router.use('/:classid/assignments', assignmentRouter); -export default router \ No newline at end of file +export default router diff --git a/backend/src/routes/group.ts b/backend/src/routes/groups.ts similarity index 100% rename from backend/src/routes/group.ts rename to backend/src/routes/groups.ts diff --git a/backend/src/routes/learningObjects.ts b/backend/src/routes/learning-objects.ts similarity index 100% rename from backend/src/routes/learningObjects.ts rename to backend/src/routes/learning-objects.ts diff --git a/backend/src/routes/learningPaths.ts b/backend/src/routes/learning-paths.ts similarity index 100% rename from backend/src/routes/learningPaths.ts rename to backend/src/routes/learning-paths.ts diff --git a/backend/src/routes/question.ts b/backend/src/routes/questions.ts similarity index 100% rename from backend/src/routes/question.ts rename to backend/src/routes/questions.ts diff --git a/backend/src/routes/student.ts b/backend/src/routes/students.ts similarity index 100% rename from backend/src/routes/student.ts rename to backend/src/routes/students.ts diff --git a/backend/src/routes/submission.ts b/backend/src/routes/submissions.ts similarity index 100% rename from backend/src/routes/submission.ts rename to backend/src/routes/submissions.ts diff --git a/backend/src/routes/teacher.ts b/backend/src/routes/teachers.ts similarity index 100% rename from backend/src/routes/teacher.ts rename to backend/src/routes/teachers.ts From 662f0ac190d9d99cfaf826a8f50384482b189597 Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Fri, 7 Mar 2025 15:52:52 +0100 Subject: [PATCH 011/106] chore: backend docker setup .dockerignore en .dockerfile van de backend ingesteld --- backend/.dockerignore | 7 +++++++ backend/backend.dockerfile | 14 ++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 backend/.dockerignore create mode 100644 backend/backend.dockerfile diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 00000000..5fbca23a --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,7 @@ +**/node_modules/ +**/dist +.git +npm-debug.log +.coverage +.coverage.* +.env \ No newline at end of file diff --git a/backend/backend.dockerfile b/backend/backend.dockerfile new file mode 100644 index 00000000..632b7e9e --- /dev/null +++ b/backend/backend.dockerfile @@ -0,0 +1,14 @@ +FROM node:22 + +WORKDIR /app + +COPY ./package*.json ./ + +RUN npm install + +COPY . . + +EXPOSE 80 +EXPOSE 443 + +CMD ["npm", "start"] \ No newline at end of file From 174582848755983fad678ec8bad7d72ac85d46ff Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Fri, 7 Mar 2025 15:57:50 +0100 Subject: [PATCH 012/106] chore: backend docker setup .dockerignore en .dockerfile van de backend ingesteld --- backend/backend.dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/backend.dockerfile b/backend/backend.dockerfile index 632b7e9e..708b7520 100644 --- a/backend/backend.dockerfile +++ b/backend/backend.dockerfile @@ -8,7 +8,6 @@ RUN npm install COPY . . -EXPOSE 80 -EXPOSE 443 +EXPOSE 2002 CMD ["npm", "start"] \ No newline at end of file From 987036946ef22dc982b7aa426c0334ed007ef697 Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Fri, 7 Mar 2025 16:04:46 +0100 Subject: [PATCH 013/106] chore: frontend docker setup .dockerignore en .dockerfile van de frontend ingesteld --- frontend/.dockerignore | 7 +++++++ frontend/frontend.dockerfile | 13 +++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 frontend/.dockerignore create mode 100644 frontend/frontend.dockerfile diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 00000000..5fbca23a --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,7 @@ +**/node_modules/ +**/dist +.git +npm-debug.log +.coverage +.coverage.* +.env \ No newline at end of file diff --git a/frontend/frontend.dockerfile b/frontend/frontend.dockerfile new file mode 100644 index 00000000..b8e60e06 --- /dev/null +++ b/frontend/frontend.dockerfile @@ -0,0 +1,13 @@ +# build stage +FROM node:22 as build-stage +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . +RUN npm run build + +# production stage +FROM nginx:stable as production-stage +COPY --from=build-stage /app/dist /usr/share/nginx/html +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file From d6a7cdf9803a0b0b3828cdc5453bab974dee6361 Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Fri, 7 Mar 2025 16:10:18 +0100 Subject: [PATCH 014/106] chore: update docker-compose docker compose uitgebreidt om ook front- en backend docker images te builden --- docker-compose.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 0f8219af..70fc4714 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,13 @@ services: + web: + build: ./frontend + depends_on: + - api + ports: + - "443:443" + - "80:80" + api: + build: ./backend db: image: postgres:latest environment: From 9aff94ba62ffb3c9de0add6a9cc5c2f7501f96be Mon Sep 17 00:00:00 2001 From: Lint Action Date: Fri, 7 Mar 2025 15:17:21 +0000 Subject: [PATCH 015/106] style: fix linting issues met Prettier --- docker-compose.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 70fc4714..23dd9b07 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,10 +2,10 @@ services: web: build: ./frontend depends_on: - - api + - api ports: - - "443:443" - - "80:80" + - '443:443' + - '80:80' api: build: ./backend db: From 71f6b1b3cbc3a0eda25b8b504c3aa14431e422e7 Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Fri, 7 Mar 2025 16:45:42 +0100 Subject: [PATCH 016/106] chore: dockerfiles van naam veranderd --- backend/{backend.dockerfile => Dockerfile} | 0 docker-compose.yml | 2 ++ frontend/{frontend.dockerfile => Dockerfile} | 0 3 files changed, 2 insertions(+) rename backend/{backend.dockerfile => Dockerfile} (100%) rename frontend/{frontend.dockerfile => Dockerfile} (100%) diff --git a/backend/backend.dockerfile b/backend/Dockerfile similarity index 100% rename from backend/backend.dockerfile rename to backend/Dockerfile diff --git a/docker-compose.yml b/docker-compose.yml index 23dd9b07..1a4daac0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,6 +8,8 @@ services: - '80:80' api: build: ./backend + ports: + - '2002:2002' db: image: postgres:latest environment: diff --git a/frontend/frontend.dockerfile b/frontend/Dockerfile similarity index 100% rename from frontend/frontend.dockerfile rename to frontend/Dockerfile From 0995cba88c1eb0ec665a8ff76142f9a8170f55d5 Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Fri, 7 Mar 2025 17:19:30 +0100 Subject: [PATCH 017/106] chore: update docker layout Dockerfiles in root gezet zodat ze ook aan bestanden daarin kunnen --- backend/.dockerignore => .dockerignore | 0 backend/Dockerfile => backend.Dockerfile | 4 ++-- docker-compose.yml | 6 ++++-- frontend/Dockerfile => frontend.Dockerfile | 4 ++-- frontend/.dockerignore | 7 ------- 5 files changed, 8 insertions(+), 13 deletions(-) rename backend/.dockerignore => .dockerignore (100%) rename backend/Dockerfile => backend.Dockerfile (62%) rename frontend/Dockerfile => frontend.Dockerfile (82%) delete mode 100644 frontend/.dockerignore diff --git a/backend/.dockerignore b/.dockerignore similarity index 100% rename from backend/.dockerignore rename to .dockerignore diff --git a/backend/Dockerfile b/backend.Dockerfile similarity index 62% rename from backend/Dockerfile rename to backend.Dockerfile index 708b7520..c8a595fe 100644 --- a/backend/Dockerfile +++ b/backend.Dockerfile @@ -2,11 +2,11 @@ FROM node:22 WORKDIR /app -COPY ./package*.json ./ +COPY ./backend/package*.json ./ RUN npm install -COPY . . +COPY ./backend . EXPOSE 2002 diff --git a/docker-compose.yml b/docker-compose.yml index 1a4daac0..673b3d4d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,13 +1,15 @@ services: web: - build: ./frontend + build: + dockerfile: ./frontend.Dockerfile depends_on: - api ports: - '443:443' - '80:80' api: - build: ./backend + build: + dockerfile: ./backend.Dockerfile ports: - '2002:2002' db: diff --git a/frontend/Dockerfile b/frontend.Dockerfile similarity index 82% rename from frontend/Dockerfile rename to frontend.Dockerfile index b8e60e06..40cb0d4a 100644 --- a/frontend/Dockerfile +++ b/frontend.Dockerfile @@ -1,9 +1,9 @@ # build stage FROM node:22 as build-stage WORKDIR /app -COPY package*.json ./ +COPY ./frontend/package*.json ./ RUN npm install -COPY . . +COPY ./frontend . RUN npm run build # production stage diff --git a/frontend/.dockerignore b/frontend/.dockerignore deleted file mode 100644 index 5fbca23a..00000000 --- a/frontend/.dockerignore +++ /dev/null @@ -1,7 +0,0 @@ -**/node_modules/ -**/dist -.git -npm-debug.log -.coverage -.coverage.* -.env \ No newline at end of file From 8890542f5e7ef30ffb23b8cfdacdf645ad9757d9 Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Fri, 7 Mar 2025 17:29:22 +0100 Subject: [PATCH 018/106] chore: docker container layout aangepast De mappen zitten een laag dieper in de docker container zodat de MenuBar.vue code niet moest aangepast worden --- backend.Dockerfile | 4 +++- frontend.Dockerfile | 10 ++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/backend.Dockerfile b/backend.Dockerfile index c8a595fe..f2967423 100644 --- a/backend.Dockerfile +++ b/backend.Dockerfile @@ -6,7 +6,9 @@ COPY ./backend/package*.json ./ RUN npm install -COPY ./backend . +COPY ./backend ./backend + +WORKDIR /app/backend EXPOSE 2002 diff --git a/frontend.Dockerfile b/frontend.Dockerfile index 40cb0d4a..6491bb5f 100644 --- a/frontend.Dockerfile +++ b/frontend.Dockerfile @@ -1,13 +1,15 @@ # build stage -FROM node:22 as build-stage +FROM node:22 AS build-stage WORKDIR /app COPY ./frontend/package*.json ./ RUN npm install -COPY ./frontend . +COPY ./frontend ./frontend +COPY ./assets ./assets +WORKDIR /app/frontend RUN npm run build # production stage -FROM nginx:stable as production-stage -COPY --from=build-stage /app/dist /usr/share/nginx/html +FROM nginx:stable AS production-stage +COPY --from=build-stage /app/frontend/dist /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file From b8db32161f633a1734a44614c670e4d7697b6e51 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Fri, 7 Mar 2025 20:04:46 +0100 Subject: [PATCH 019/106] fix: consistente naamgeving kebab case --- ...{learningObjects.ts => learning-objects.ts} | 4 ++-- .../{learningPaths.ts => learning-paths.ts} | 2 +- backend/src/controllers/themes.ts | 2 +- .../entities/content/learning-object.entity.ts | 18 +++++++++--------- .../{learningPath.ts => learning-path.ts} | 0 backend/src/routes/learning-objects.ts | 2 +- backend/src/routes/learning-paths.ts | 2 +- ...{learningObjects.ts => learning-objects.ts} | 6 +++--- .../{learningPaths.ts => learning-paths.ts} | 4 ++-- .../src/util/{apiHelper.ts => api-helper.ts} | 0 ...nslationHelper.ts => translation-helper.ts} | 2 +- 11 files changed, 21 insertions(+), 21 deletions(-) rename backend/src/controllers/{learningObjects.ts => learning-objects.ts} (93%) rename backend/src/controllers/{learningPaths.ts => learning-paths.ts} (97%) rename backend/src/interfaces/{learningPath.ts => learning-path.ts} (100%) rename backend/src/services/{learningObjects.ts => learning-objects.ts} (96%) rename backend/src/services/{learningPaths.ts => learning-paths.ts} (93%) rename backend/src/util/{apiHelper.ts => api-helper.ts} (100%) rename backend/src/util/{translationHelper.ts => translation-helper.ts} (93%) diff --git a/backend/src/controllers/learningObjects.ts b/backend/src/controllers/learning-objects.ts similarity index 93% rename from backend/src/controllers/learningObjects.ts rename to backend/src/controllers/learning-objects.ts index 4295326a..c8a51734 100644 --- a/backend/src/controllers/learningObjects.ts +++ b/backend/src/controllers/learning-objects.ts @@ -3,9 +3,9 @@ import { getLearningObjectById, getLearningObjectIdsFromPath, getLearningObjectsFromPath, -} from '../services/learningObjects.js'; +} from '../services/learning-objects.js'; import { FALLBACK_LANG } from '../config.js'; -import { FilteredLearningObject } from '../interfaces/learningPath'; +import { FilteredLearningObject } from '../interfaces/learning-path'; export async function getAllLearningObjects( req: Request, diff --git a/backend/src/controllers/learningPaths.ts b/backend/src/controllers/learning-paths.ts similarity index 97% rename from backend/src/controllers/learningPaths.ts rename to backend/src/controllers/learning-paths.ts index 903451be..fb0fd07c 100644 --- a/backend/src/controllers/learningPaths.ts +++ b/backend/src/controllers/learning-paths.ts @@ -4,7 +4,7 @@ import { FALLBACK_LANG } from '../config.js'; import { fetchLearningPaths, searchLearningPaths, -} from '../services/learningPaths.js'; +} from '../services/learning-paths.js'; /** * Fetch learning paths based on query parameters. */ diff --git a/backend/src/controllers/themes.ts b/backend/src/controllers/themes.ts index 4b59751e..208b27d1 100644 --- a/backend/src/controllers/themes.ts +++ b/backend/src/controllers/themes.ts @@ -1,6 +1,6 @@ import { Request, Response } from 'express'; import { themes } from '../data/themes.js'; -import { loadTranslations } from "../util/translationHelper.js"; +import { loadTranslations } from "../util/translation-helper.js"; import { FALLBACK_LANG } from '../config.js'; interface Translations { diff --git a/backend/src/entities/content/learning-object.entity.ts b/backend/src/entities/content/learning-object.entity.ts index 179a2f5f..78d4aa00 100644 --- a/backend/src/entities/content/learning-object.entity.ts +++ b/backend/src/entities/content/learning-object.entity.ts @@ -13,6 +13,15 @@ import { Attachment } from './attachment.entity.js'; import { Teacher } from '../users/teacher.entity.js'; import { LearningObjectRepository } from '../../data/content/learning-object-repository.js'; +@Embeddable() +export class ReturnValue { + @Property({ type: 'string' }) + callbackUrl!: string; + + @Property({ type: 'json' }) + callbackSchema!: string; +} + @Entity({ repository: () => LearningObjectRepository }) export class LearningObject { @PrimaryKey({ type: 'string' }) @@ -88,15 +97,6 @@ export class EducationalGoal { id!: string; } -@Embeddable() -export class ReturnValue { - @Property({ type: 'string' }) - callbackUrl!: string; - - @Property({ type: 'json' }) - callbackSchema!: string; -} - export enum ContentType { Markdown = 'text/markdown', Image = 'image/image', diff --git a/backend/src/interfaces/learningPath.ts b/backend/src/interfaces/learning-path.ts similarity index 100% rename from backend/src/interfaces/learningPath.ts rename to backend/src/interfaces/learning-path.ts diff --git a/backend/src/routes/learning-objects.ts b/backend/src/routes/learning-objects.ts index 416602b5..3717095a 100644 --- a/backend/src/routes/learning-objects.ts +++ b/backend/src/routes/learning-objects.ts @@ -2,7 +2,7 @@ import express from 'express'; import { getAllLearningObjects, getLearningObject, -} from '../controllers/learningObjects.js'; +} from '../controllers/learning-objects.js'; const router = express.Router(); diff --git a/backend/src/routes/learning-paths.ts b/backend/src/routes/learning-paths.ts index ce580745..efe17312 100644 --- a/backend/src/routes/learning-paths.ts +++ b/backend/src/routes/learning-paths.ts @@ -1,5 +1,5 @@ import express from 'express'; -import { getLearningPaths } from '../controllers/learningPaths.js'; +import { getLearningPaths } from '../controllers/learning-paths.js'; const router = express.Router(); diff --git a/backend/src/services/learningObjects.ts b/backend/src/services/learning-objects.ts similarity index 96% rename from backend/src/services/learningObjects.ts rename to backend/src/services/learning-objects.ts index d1d34ad2..65ad11a6 100644 --- a/backend/src/services/learningObjects.ts +++ b/backend/src/services/learning-objects.ts @@ -1,12 +1,12 @@ import { DWENGO_API_BASE } from '../config.js'; -import { fetchWithLogging } from '../util/apiHelper.js'; +import { fetchWithLogging } from '../util/api-helper.js'; import { FilteredLearningObject, LearningObjectMetadata, LearningObjectNode, LearningPathResponse, -} from '../interfaces/learningPath.js'; -import { fetchLearningPaths } from './learningPaths.js'; +} from '../interfaces/learning-path.js'; +import { fetchLearningPaths } from './learning-paths.js'; function filterData( data: LearningObjectMetadata, diff --git a/backend/src/services/learningPaths.ts b/backend/src/services/learning-paths.ts similarity index 93% rename from backend/src/services/learningPaths.ts rename to backend/src/services/learning-paths.ts index 2a9f15a3..3353b58d 100644 --- a/backend/src/services/learningPaths.ts +++ b/backend/src/services/learning-paths.ts @@ -1,9 +1,9 @@ -import { fetchWithLogging } from '../util/apiHelper.js'; +import { fetchWithLogging } from '../util/api-helper.js'; import { DWENGO_API_BASE } from '../config.js'; import { LearningPath, LearningPathResponse, -} from '../interfaces/learningPath.js'; +} from '../interfaces/learning-path.js'; export async function fetchLearningPaths( hruids: string[], diff --git a/backend/src/util/apiHelper.ts b/backend/src/util/api-helper.ts similarity index 100% rename from backend/src/util/apiHelper.ts rename to backend/src/util/api-helper.ts diff --git a/backend/src/util/translationHelper.ts b/backend/src/util/translation-helper.ts similarity index 93% rename from backend/src/util/translationHelper.ts rename to backend/src/util/translation-helper.ts index f4443531..d6d842ff 100644 --- a/backend/src/util/translationHelper.ts +++ b/backend/src/util/translation-helper.ts @@ -1,7 +1,7 @@ import fs from 'fs'; import path from 'path'; import yaml from 'js-yaml'; -import {FALLBACK_LANG} from "../../config"; +import { FALLBACK_LANG } from "../config.js"; export function loadTranslations(language: string): T { try { From 6b87722469e2afd1671336e4c988690fcdf6ce57 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Fri, 7 Mar 2025 20:05:16 +0100 Subject: [PATCH 020/106] feat: teacher get, post en delete route --- backend/src/app.ts | 4 + backend/src/controllers/teachers.ts | 79 ++++++++++++++++++++ backend/src/data/users/teacher-repository.ts | 3 + backend/src/interfaces/teacher.ts | 36 +++++++++ backend/src/routes/teachers.ts | 30 ++------ backend/src/services/teachers.ts | 38 ++++++++++ 6 files changed, 166 insertions(+), 24 deletions(-) create mode 100644 backend/src/controllers/teachers.ts create mode 100644 backend/src/interfaces/teacher.ts create mode 100644 backend/src/services/teachers.ts diff --git a/backend/src/app.ts b/backend/src/app.ts index 811c6cfc..f28604c0 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -7,6 +7,7 @@ import learningPathRoutes from './routes/learning-paths.js'; import learningObjectRoutes from './routes/learning-objects.js'; import studentRoutes from './routes/students.js'; +import teacherRoutes from './routes/teachers.js' import groupRoutes from './routes/groups.js'; import submissionRoutes from './routes/submissions.js'; import classRoutes from './routes/classes.js'; @@ -16,6 +17,8 @@ const app: Express = express(); const port: string | number = getNumericEnvVar(EnvVars.Port); +app.use(express.json()); + // TODO Replace with Express routes app.get('/', (_, res: Response) => { res.json({ @@ -24,6 +27,7 @@ app.get('/', (_, res: Response) => { }); app.use('/student', studentRoutes); +app.use('/teacher', teacherRoutes); app.use('/group', groupRoutes); app.use('/submission', submissionRoutes); app.use('/class', classRoutes); diff --git a/backend/src/controllers/teachers.ts b/backend/src/controllers/teachers.ts new file mode 100644 index 00000000..d4baef2f --- /dev/null +++ b/backend/src/controllers/teachers.ts @@ -0,0 +1,79 @@ +import { Request, Response } from 'express'; +import { + createTeacher, deleteTeacher, + fetchAllTeachers, fetchTeacherByUsername +} from '../services/teachers.js'; +import {TeacherDTO} from "../interfaces/teacher"; + +export async function getTeacherHandler(req: Request, res: Response): Promise { + try { + const full = req.query.full === 'true'; + const username = req.query.username as string; + + if (username){ + const teacher = await fetchTeacherByUsername(username); + if (!teacher){ + res.status(404).json({ error: `Teacher with username '${username}' not found.` }); + return; + } + res.json(teacher); + return; + } + + let teachers: TeacherDTO[] | string[] = await fetchAllTeachers(); + + if (!full) + teachers = teachers.map((teacher) => teacher.username) + + res.json(teachers); + } catch (error) { + console.error("❌ Error fetching teachers:", error); + res.status(500).json({ error: "Internal server error" }); + } +} + +export async function createTeacherHandler( + req: Request, + res: Response +): Promise { + try { + const teacherData = req.body as TeacherDTO; + + if (!teacherData.username || !teacherData.firstName || !teacherData.lastName) { + res.status(400).json({ error: 'Missing required fields: username, firstName, lastName' }); + return; + } + + const newTeacher = await createTeacher(teacherData); + res.status(201).json(newTeacher); + } catch (error) { + console.error('Error creating teacher:', error); + res.status(500).json({ error: 'Internal server error' }); + } +} + +export async function deleteTeacherHandler( + req: Request, + res: Response +): Promise { + try { + const username = req.params.username as string; + + if (!username) { + res.status(400).json({ error: 'Missing required field: username' }); + return; + } + + const deletedTeacher = await deleteTeacher(username); + + if (!deletedTeacher) { + res.status(400).json({ error: `Did not find teacher with username ${username}` }); + return; + } + + res.status(201).json(deletedTeacher); + } catch (error) { + console.error('Error deleting teacher:', error); + res.status(500).json({ error: 'Internal server error' }); + } +} diff --git a/backend/src/data/users/teacher-repository.ts b/backend/src/data/users/teacher-repository.ts index 704ef409..43602ff7 100644 --- a/backend/src/data/users/teacher-repository.ts +++ b/backend/src/data/users/teacher-repository.ts @@ -5,6 +5,9 @@ export class TeacherRepository extends DwengoEntityRepository { public findByUsername(username: string): Promise { return this.findOne({ username: username }); } + public addTeacher(teacher: Teacher): Promise { + return this.save(teacher); + } public deleteByUsername(username: string): Promise { return this.deleteWhere({ username: username }); } diff --git a/backend/src/interfaces/teacher.ts b/backend/src/interfaces/teacher.ts new file mode 100644 index 00000000..15c89520 --- /dev/null +++ b/backend/src/interfaces/teacher.ts @@ -0,0 +1,36 @@ +import { Teacher } from "../entities/users/teacher.entity.js"; + +/** + * Teacher Data Transfer Object + */ +export interface TeacherDTO { + username: string; + firstName: string; + lastName: string; + endpoints?: { + self: string; + classes: string; + questions: string; + invitations: string; + }; +} + +/** + * Maps a Teacher entity to a TeacherDTO + */ +export function mapToTeacherDTO(teacher: Teacher): TeacherDTO { + return { + username: teacher.username, + firstName: teacher.firstName, + lastName: teacher.lastName, + }; +} + +export function mapToTeacher(teacherData: TeacherDTO): Teacher { + const teacher = new Teacher(); + teacher.username = teacherData.username; + teacher.firstName = teacherData.firstName; + teacher.lastName = teacherData.lastName; + + return teacher; +} diff --git a/backend/src/routes/teachers.ts b/backend/src/routes/teachers.ts index 37b3b04b..dab98dea 100644 --- a/backend/src/routes/teachers.ts +++ b/backend/src/routes/teachers.ts @@ -1,31 +1,13 @@ import express from 'express' +import {createTeacherHandler, deleteTeacherHandler, getTeacherHandler} from "../controllers/teachers.js"; const router = express.Router(); // root endpoint used to search objects -router.get('/', (req, res) => { - res.json({ - teachers: [ - '0', - '1', - ] - }); -}); +router.get('/', getTeacherHandler); -// information about a teacher -router.get('/:id', (req, res) => { - res.json({ - id: req.params.id, - firstName: 'John', - lastName: 'Doe', - username: 'JohnDoe1', - links: { - self: `${req.baseUrl}/${req.params.id}`, - classes: `${req.baseUrl}/${req.params.id}/classes`, - questions: `${req.baseUrl}/${req.params.id}/questions`, - invitations: `${req.baseUrl}/${req.params.id}/invitations`, - }, - }); -}) +router.post('/', createTeacherHandler); + +router.delete('/:username', deleteTeacherHandler); // the questions students asked a teacher router.get('/:id/questions', (req, res) => { @@ -55,4 +37,4 @@ router.get('/:id/classes', (req, res) => { }); -export default router \ No newline at end of file +export default router diff --git a/backend/src/services/teachers.ts b/backend/src/services/teachers.ts new file mode 100644 index 00000000..5572d4b0 --- /dev/null +++ b/backend/src/services/teachers.ts @@ -0,0 +1,38 @@ +import {getTeacherRepository} from "../data/repositories.js"; +import {mapToTeacher, mapToTeacherDTO, TeacherDTO} from "../interfaces/teacher.js"; +import { Teacher } from "../entities/users/teacher.entity"; + + +export async function fetchAllTeachers(): Promise { + const teacherRepository = getTeacherRepository(); + const teachers = await teacherRepository.find({}); + + return teachers.map(mapToTeacherDTO); +} + +export async function createTeacher(teacherData: TeacherDTO): Promise { + const teacherRepository = getTeacherRepository(); + const newTeacher = mapToTeacher(teacherData); + + await teacherRepository.addTeacher(newTeacher); + return newTeacher; +} + +export async function fetchTeacherByUsername(username: string): Promise { + const teacherRepository = getTeacherRepository(); + const teacher = await teacherRepository.findByUsername(username); + + return teacher ? mapToTeacherDTO(teacher) : null; +} + +export async function deleteTeacher(username: string): Promise { + const teacherRepository = getTeacherRepository(); + const teacher = await teacherRepository.findByUsername(username); + + if (!teacher) + return null; + + await teacherRepository.deleteByUsername(username); + return teacher; +} + From ab8ece2a76d600188c5684ab127f8c259cbbf849 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Fri, 7 Mar 2025 20:00:54 +0100 Subject: [PATCH 021/106] docs(backend): Setup swagger-autogen --- backend/package.json | 2 + backend/src/app.ts | 30 +- backend/src/swagger.ts | 7 + backend/tsconfig.json | 3 +- docs/api/generate.ts | 28 ++ docs/api/swagger.json | 712 +++++++++++++++++++++++++++++++++++++++++ docs/package.json | 14 + package-lock.json | 148 ++++++++- package.json | 4 +- 9 files changed, 929 insertions(+), 19 deletions(-) create mode 100644 backend/src/swagger.ts create mode 100644 docs/api/generate.ts create mode 100644 docs/api/swagger.json create mode 100644 docs/package.json diff --git a/backend/package.json b/backend/package.json index 478b26e6..b4dc9c1e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -25,6 +25,7 @@ "js-yaml": "^4.1.0", "loki-logger-ts": "^1.0.2", "response-time": "^2.3.3", + "swagger-ui-express": "^5.0.1", "uuid": "^11.1.0", "winston": "^3.17.0", "winston-loki": "^6.1.3" @@ -34,6 +35,7 @@ "@types/express": "^5.0.0", "@types/node": "^22.13.4", "@types/response-time": "^2.3.8", + "@types/swagger-ui-express": "^4.1.8", "globals": "^15.15.0", "ts-node": "^10.9.2", "tsx": "^4.19.3", diff --git a/backend/src/app.ts b/backend/src/app.ts index 5769e360..f76d72d6 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -16,6 +16,8 @@ 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 swaggerMiddleware from "./swagger"; +import swaggerUi from "swagger-ui-express"; const logger: Logger = getLogger(); @@ -23,7 +25,6 @@ const app: Express = express(); const port: string | number = getNumericEnvVar(EnvVars.Port); app.use(express.json()); -app.use(responseTime(responseTimeLogger)); // TODO Replace with Express routes app.get('/', (_, res: Response) => { @@ -33,17 +34,24 @@ app.get('/', (_, res: Response) => { }); }); -app.use('/student', studentRouter); -app.use('/group', groupRouter); -app.use('/assignment', assignmentRouter); -app.use('/submission', submissionRouter); -app.use('/class', classRouter); -app.use('/question', questionRouter); -app.use('/login', loginRouter); +// Routes +app.use('/student', studentRouter /* #swagger.tags = ['Student'] */); +app.use('/group', groupRouter /* #swagger.tags = ['Group'] */); +app.use('/assignment', assignmentRouter /* #swagger.tags = ['Assignment'] */); +app.use('/submission', submissionRouter /* #swagger.tags = ['Submission'] */); +app.use('/class', classRouter /* #swagger.tags = ['Class'] */); +app.use('/question', questionRouter /* #swagger.tags = ['Question'] */); +app.use('/login', loginRouter /* #swagger.tags = ['Login'] */); -app.use('/theme', themeRoutes); -app.use('/learningPath', learningPathRoutes); -app.use('/learningObject', learningObjectRoutes); +app.use('/theme', themeRoutes /* #swagger.tags = ['Theme'] */); +app.use('/learningPath', learningPathRoutes /* #swagger.tags = ['Learning Path'] */); +app.use('/learningObject', learningObjectRoutes /* #swagger.tags = ['Learning Object'] */); + +// Add response time loggin +app.use(responseTime(responseTimeLogger)); + +// Swagger UI for API documentation +app.use('/api-docs', swaggerUi.serve, swaggerMiddleware); async function startServer() { await initORM(); diff --git a/backend/src/swagger.ts b/backend/src/swagger.ts new file mode 100644 index 00000000..97b08496 --- /dev/null +++ b/backend/src/swagger.ts @@ -0,0 +1,7 @@ +import { RequestHandler } from 'express'; +import swaggerUi from 'swagger-ui-express'; +import swaggerDocument from '../../docs/api/swagger.json'; + +const swaggerMiddleware: RequestHandler = swaggerUi.setup(swaggerDocument); + +export default swaggerMiddleware; diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 86267d25..2dd3998d 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -3,6 +3,7 @@ "include": ["src/**/*.ts"], "compilerOptions": { "rootDir": "./src", - "outDir": "./dist" + "outDir": "./dist", + "resolveJsonModule": true } } diff --git a/docs/api/generate.ts b/docs/api/generate.ts new file mode 100644 index 00000000..737619bb --- /dev/null +++ b/docs/api/generate.ts @@ -0,0 +1,28 @@ +import swaggerAutogen from "swagger-autogen"; + +const doc = { + info: { + version: '0.1.0', + title: 'Dwengo-1 Backend API', + description: 'Dwengo-1 Backend API using Express, based on VZW Dwengo', + license: { + name: 'MIT', + url: 'https://github.com/SELab-2/Dwengo-1/blob/336496ab6352ee3f8bf47490c90b5cf81526cef6/LICENSE' + } + }, + servers: [ + { + url: 'http://localhost:3000/', + }, + { + url: 'https://sel2-1.ugent.be/api' + } + ] +}; + +const outputFile = './swagger.json'; +const routes = [ + '../../backend/src/app.ts' +]; + +swaggerAutogen({ openapi: '3.1.0' })(outputFile, routes, doc); diff --git a/docs/api/swagger.json b/docs/api/swagger.json new file mode 100644 index 00000000..4f231cbd --- /dev/null +++ b/docs/api/swagger.json @@ -0,0 +1,712 @@ +{ + "openapi": "3.1.0", + "info": { + "version": "0.1.0", + "title": "Dwengo-1 Backend API", + "description": "Dwengo-1 Backend API using Express, based on VZW Dwengo", + "license": { + "name": "MIT", + "url": "https://github.com/SELab-2/Dwengo-1/blob/336496ab6352ee3f8bf47490c90b5cf81526cef6/LICENSE" + } + }, + "servers": [ + { + "url": "http://localhost:3000/" + }, + { + "url": "https://sel2-1.ugent.be/api" + } + ], + "paths": { + "/": { + "get": { + "description": "", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/student/": { + "get": { + "tags": [ + "Student" + ], + "description": "", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/student/{id}": { + "get": { + "tags": [ + "Student" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/student/{id}/classes": { + "get": { + "tags": [ + "Student" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/student/{id}/submissions": { + "get": { + "tags": [ + "Student" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/student/{id}/assignments": { + "get": { + "tags": [ + "Student" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/student/{id}/groups": { + "get": { + "tags": [ + "Student" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/group/": { + "get": { + "tags": [ + "Group" + ], + "description": "", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/group/{id}": { + "get": { + "tags": [ + "Group" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/group/{id}/question": { + "get": { + "tags": [ + "Group" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/assignment/": { + "get": { + "tags": [ + "Assignment" + ], + "description": "", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/assignment/{id}": { + "get": { + "tags": [ + "Assignment" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/assignment/{id}/submissions": { + "get": { + "tags": [ + "Assignment" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/assignment/{id}/groups": { + "get": { + "tags": [ + "Assignment" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/assignment/{id}/questions": { + "get": { + "tags": [ + "Assignment" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/submission/": { + "get": { + "tags": [ + "Submission" + ], + "description": "", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/submission/{id}": { + "get": { + "tags": [ + "Submission" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/class/": { + "get": { + "tags": [ + "Class" + ], + "description": "", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/class/{id}": { + "get": { + "tags": [ + "Class" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/class/{id}/invitations": { + "get": { + "tags": [ + "Class" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/class/{id}/assignments": { + "get": { + "tags": [ + "Class" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/class/{id}/students": { + "get": { + "tags": [ + "Class" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/question/": { + "get": { + "tags": [ + "Question" + ], + "description": "", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/question/{id}": { + "get": { + "tags": [ + "Question" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/question/{id}/answers": { + "get": { + "tags": [ + "Question" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/login/": { + "get": { + "tags": [ + "Login" + ], + "description": "", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/theme/": { + "get": { + "tags": [ + "Theme" + ], + "description": "", + "parameters": [ + { + "name": "language", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/theme/{theme}": { + "get": { + "tags": [ + "Theme" + ], + "description": "", + "parameters": [ + { + "name": "theme", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/learningPath/": { + "get": { + "tags": [ + "Learning Path" + ], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "theme", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "search", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "language", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/learningObject/": { + "get": { + "tags": [ + "Learning Object" + ], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "language", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/learningObject/{hruid}": { + "get": { + "tags": [ + "Learning Object" + ], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "language", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "500": { + "description": "Internal Server Error" + } + } + } + } + } +} \ No newline at end of file diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 00000000..3e3cb619 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,14 @@ +{ + "name": "dwengo-1-docs", + "version": "0.0.1", + "description": "Documentation for Dwengo-1", + "private": true, + "scripts": { + "build": "npm run architecture && npm run swagger", + "architecture": "python3 -m venv .venv && source .venv/bin/activate && pip install -r docs/requirements.txt && python docs/architecture/schema.py", + "swagger": "tsx api/generate.ts" + }, + "devDependencies": { + "swagger-autogen": "^2.23.7" + } +} diff --git a/package-lock.json b/package-lock.json index 92bf5a8e..3a99b9ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "license": "MIT", "workspaces": [ "backend", - "frontend" + "frontend", + "docs" ], "devDependencies": { "@eslint/compat": "^1.2.6", @@ -39,6 +40,7 @@ "js-yaml": "^4.1.0", "loki-logger-ts": "^1.0.2", "response-time": "^2.3.3", + "swagger-ui-express": "^5.0.1", "uuid": "^11.1.0", "winston": "^3.17.0", "winston-loki": "^6.1.3" @@ -48,6 +50,7 @@ "@types/express": "^5.0.0", "@types/node": "^22.13.4", "@types/response-time": "^2.3.8", + "@types/swagger-ui-express": "^4.1.8", "globals": "^15.15.0", "ts-node": "^10.9.2", "tsx": "^4.19.3", @@ -82,6 +85,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "docs": { + "name": "dwengo-1-docs", + "version": "0.0.1", + "devDependencies": { + "swagger-autogen": "^2.23.7" + } + }, "frontend": { "name": "dwengo-1-frontend", "version": "0.0.1", @@ -1556,6 +1566,13 @@ "linux" ] }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", "dev": true, @@ -1739,6 +1756,17 @@ "@types/send": "*" } }, + "node_modules/@types/swagger-ui-express": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz", + "integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/serve-static": "*" + } + }, "node_modules/@types/tough-cookie": { "version": "4.0.5", "dev": true, @@ -3300,6 +3328,16 @@ "dev": true, "license": "MIT" }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/default-browser": { "version": "5.2.1", "dev": true, @@ -3415,6 +3453,10 @@ "resolved": "backend", "link": true }, + "node_modules/dwengo-1-docs": { + "resolved": "docs", + "link": true + }, "node_modules/dwengo-1-frontend": { "resolved": "frontend", "link": true @@ -4355,8 +4397,8 @@ }, "node_modules/fs.realpath": { "version": "1.0.0", - "license": "ISC", - "optional": true + "devOptional": true, + "license": "ISC" }, "node_modules/function-bind": { "version": "1.1.2", @@ -4806,8 +4848,8 @@ }, "node_modules/inflight": { "version": "1.0.6", + "devOptional": true, "license": "ISC", - "optional": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -6230,8 +6272,8 @@ }, "node_modules/path-is-absolute": { "version": "1.0.1", + "devOptional": true, "license": "MIT", - "optional": true, "engines": { "node": ">=0.10.0" } @@ -7649,6 +7691,102 @@ "version": "1.0.0", "dev": true }, + "node_modules/swagger-autogen": { + "version": "2.23.7", + "resolved": "https://registry.npmjs.org/swagger-autogen/-/swagger-autogen-2.23.7.tgz", + "integrity": "sha512-vr7uRmuV0DCxWc0wokLJAwX3GwQFJ0jwN+AWk0hKxre2EZwusnkGSGdVFd82u7fQLgwSTnbWkxUL7HXuz5LTZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^7.4.1", + "deepmerge": "^4.2.2", + "glob": "^7.1.7", + "json5": "^2.2.3" + } + }, + "node_modules/swagger-autogen/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/swagger-autogen/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/swagger-autogen/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/swagger-autogen/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/swagger-ui-dist": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.20.0.tgz", + "integrity": "sha512-V5pozVTZxivdoQq/SQWxj3A4cOu5opk9MEbcZANX3Pj8X8xCrD1QCtBT7744Pz9msOvt0nnmy9JvM/9PGonCdg==", + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, + "node_modules/swagger-ui-express": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", + "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", + "license": "MIT", + "dependencies": { + "swagger-ui-dist": ">=5.0.0" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0 || >=5.0.0-beta" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "dev": true, diff --git a/package.json b/package.json index db7f5ba3..09703f20 100644 --- a/package.json +++ b/package.json @@ -8,13 +8,13 @@ "build": "npm run build --ws", "format": "npm run format --ws", "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" }, "workspaces": [ "backend", - "frontend" + "frontend", + "docs" ], "repository": { "type": "git", From 9c9e7c4870954c03274f2f503faeef19f50d97dc Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Fri, 7 Mar 2025 23:09:51 +0100 Subject: [PATCH 022/106] feat: teacher-class en teacher-students route --- backend/src/controllers/classes.ts | 9 ++- backend/src/controllers/teachers.ts | 61 ++++++++++++++++++-- backend/src/data/classes/class-repository.ts | 8 +++ backend/src/routes/teachers.ts | 11 +++- backend/src/services/class.ts | 24 ++++---- backend/src/services/teachers.ts | 54 ++++++++++++++++- 6 files changed, 146 insertions(+), 21 deletions(-) diff --git a/backend/src/controllers/classes.ts b/backend/src/controllers/classes.ts index ee932734..c8095162 100644 --- a/backend/src/controllers/classes.ts +++ b/backend/src/controllers/classes.ts @@ -1,5 +1,5 @@ import { Request, Response } from 'express'; -import { getAllClasses, getClass, getClassStudents } from '../services/class'; +import {getAllClasses, getClass, getClassStudents, getClassStudentsIds} from '../services/class'; import { ClassDTO } from '../interfaces/classes'; export async function getAllClassesHandler( @@ -48,9 +48,12 @@ export async function getClassStudentsHandler( const classId = req.params.id; const full = req.query.full === "true"; - const students = await getClassStudents(classId, full); + let students; + + if (full) students = await getClassStudents(classId); + else students = await getClassStudentsIds(classId); res.json({ students: students, }); -} \ No newline at end of file +} diff --git a/backend/src/controllers/teachers.ts b/backend/src/controllers/teachers.ts index d4baef2f..6f363c20 100644 --- a/backend/src/controllers/teachers.ts +++ b/backend/src/controllers/teachers.ts @@ -1,9 +1,16 @@ import { Request, Response } from 'express'; import { - createTeacher, deleteTeacher, - fetchAllTeachers, fetchTeacherByUsername + createTeacher, + deleteTeacher, + fetchTeacherByUsername, + getClassesByTeacher, + getClassIdsByTeacher, + getAllTeachers, + getAllTeachersIds, getStudentsByTeacher, getStudentIdsByTeacher } from '../services/teachers.js'; import {TeacherDTO} from "../interfaces/teacher"; +import {ClassDTO} from "../interfaces/classes"; +import {StudentDTO} from "../interfaces/students"; export async function getTeacherHandler(req: Request, res: Response): Promise { try { @@ -20,10 +27,10 @@ export async function getTeacherHandler(req: Request, res: Response): Promise teacher.username) + if (full) teachers = await getAllTeachers(); + else teachers = await getAllTeachersIds(); res.json(teachers); } catch (error) { @@ -77,3 +84,47 @@ export async function deleteTeacherHandler( res.status(500).json({ error: 'Internal server error' }); } } + +export async function getTeacherClassHandler(req: Request, res: Response): Promise { + try { + 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; + } + + let classes: ClassDTO[] | string[]; + + if (full) classes = await getClassesByTeacher(username); + else classes = 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' }); + } +} + +export async function getTeacherStudentHandler(req: Request, res: Response): Promise { + try { + 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; + } + + let students: StudentDTO[] | string[]; + + if (full) students = await getStudentsByTeacher(username); + else students = 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' }); + } +} diff --git a/backend/src/data/classes/class-repository.ts b/backend/src/data/classes/class-repository.ts index cd0e44fc..9fd50d75 100644 --- a/backend/src/data/classes/class-repository.ts +++ b/backend/src/data/classes/class-repository.ts @@ -1,6 +1,7 @@ import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; import { Class } from '../../entities/classes/class.entity.js'; 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 { @@ -18,4 +19,11 @@ export class ClassRepository extends DwengoEntityRepository { { populate: ["students", "teachers"] } // voegt student en teacher objecten toe ) } + + public findByTeacher(teacher: Teacher): Promise { + return this.find( + { teachers: teacher }, + { populate: ["students", "teachers"] } + ); + } } diff --git a/backend/src/routes/teachers.ts b/backend/src/routes/teachers.ts index dab98dea..338ac0b8 100644 --- a/backend/src/routes/teachers.ts +++ b/backend/src/routes/teachers.ts @@ -1,5 +1,10 @@ import express from 'express' -import {createTeacherHandler, deleteTeacherHandler, getTeacherHandler} from "../controllers/teachers.js"; +import { + createTeacherHandler, + deleteTeacherHandler, + getTeacherClassHandler, + getTeacherHandler, getTeacherStudentHandler +} from "../controllers/teachers.js"; const router = express.Router(); // root endpoint used to search objects @@ -9,6 +14,10 @@ router.post('/', createTeacherHandler); router.delete('/:username', deleteTeacherHandler); +router.get('/:username/classes', getTeacherClassHandler); + +router.get('/:username/students', getTeacherStudentHandler); + // the questions students asked a teacher router.get('/:id/questions', (req, res) => { res.json({ diff --git a/backend/src/services/class.ts b/backend/src/services/class.ts index 89628404..56d939ae 100644 --- a/backend/src/services/class.ts +++ b/backend/src/services/class.ts @@ -10,7 +10,7 @@ export async function getAllClasses(full: boolean): Promise { } } -export async function getClassStudents(classId: string, full: boolean): Promise { +async function fetchClassStudents(classId: string, full: boolean): Promise { const classRepository = getClassRepository(); const cls = await classRepository.findById(classId); - if (!cls) { + if (!cls) return []; - } - if (full) { - return cls.students.map(mapToStudentDTO); - } else { - return cls.students.map((student) => student.username); - } -} \ No newline at end of file + return cls.students.map(mapToStudentDTO); +} + +export async function getClassStudents(classId: string): Promise { + return await fetchClassStudents(classId); +} + +export async function getClassStudentsIds(classId: string): Promise { + return await fetchClassStudents(classId).map((student) => student.username); +} + diff --git a/backend/src/services/teachers.ts b/backend/src/services/teachers.ts index 5572d4b0..39ee3997 100644 --- a/backend/src/services/teachers.ts +++ b/backend/src/services/teachers.ts @@ -1,15 +1,26 @@ -import {getTeacherRepository} from "../data/repositories.js"; +import {getClassRepository, getTeacherRepository} from "../data/repositories.js"; import {mapToTeacher, mapToTeacherDTO, TeacherDTO} from "../interfaces/teacher.js"; import { Teacher } from "../entities/users/teacher.entity"; +import {ClassDTO, mapToClassDTO} from "../interfaces/classes"; +import {getClassStudents, getClassStudentsIds} from "./class"; +import {StudentDTO} from "../interfaces/students"; -export async function fetchAllTeachers(): Promise { +async function fetchAllTeachers(): Promise { const teacherRepository = getTeacherRepository(); const teachers = await teacherRepository.find({}); return teachers.map(mapToTeacherDTO); } +export async function getAllTeachers(): Promise { + return await fetchAllTeachers(); +} + +export async function getAllTeachersIds(): Promise { + return await fetchAllTeachers().map((teacher) => teacher.username) +} + export async function createTeacher(teacherData: TeacherDTO): Promise { const teacherRepository = getTeacherRepository(); const newTeacher = mapToTeacher(teacherData); @@ -36,3 +47,42 @@ export async function deleteTeacher(username: string): Promise { + const teacherRepository = getTeacherRepository(); + const classRepository = getClassRepository(); + + const teacher = await teacherRepository.findByUsername(username); + if (!teacher) { + return []; + } + + const classes = await classRepository.findByTeacher(teacher); + return classes.map(mapToClassDTO); +} + +export async function getClassesByTeacher(username: string): Promise { + return await fetchClassesByTeacher(username) +} + +export async function getClassIdsByTeacher(): Promise { + return await fetchClassesByTeacher(username).map((cls) => cls.id); +} + +async function fetchStudentsByTeacher(username: string) { + const classes = await getClassIdsByTeacher(); + + return Promise.all( + classes.map( async (id) => getClassStudents(id)) + ); +} + +export async function getStudentsByTeacher(username: string): Promise { + return await fetchStudentsByTeacher(username); +} + +export async function getStudentIdsByTeacher(): Promise { + return await fetchStudentsByTeacher(username).map((student) => student.username); +} + + + From 4968d7cb077b885ee90ba0896262e684762c0ae0 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Sat, 8 Mar 2025 09:36:03 +0100 Subject: [PATCH 023/106] fix: interface bestanden enkelvoud --- backend/src/controllers/assignments.ts | 2 +- backend/src/controllers/classes.ts | 2 +- backend/src/controllers/students.ts | 6 +++--- backend/src/controllers/teachers.ts | 4 ++-- backend/src/interfaces/{assignments.ts => assignment.ts} | 4 ++-- backend/src/interfaces/{classes.ts => class.ts} | 0 backend/src/interfaces/{groups.ts => group.ts} | 0 backend/src/interfaces/{students.ts => student.ts} | 0 backend/src/services/assignments.ts | 6 +++--- backend/src/services/class.ts | 4 ++-- backend/src/services/students.ts | 8 ++++---- backend/src/services/teachers.ts | 4 ++-- 12 files changed, 20 insertions(+), 20 deletions(-) rename backend/src/interfaces/{assignments.ts => assignment.ts} (95%) rename backend/src/interfaces/{classes.ts => class.ts} (100%) rename backend/src/interfaces/{groups.ts => group.ts} (100%) rename backend/src/interfaces/{students.ts => student.ts} (100%) diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index feabb1e1..1a79c8b1 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -1,7 +1,7 @@ import { Request, Response } from 'express' import { getAssignment } from '../services/assignments'; -// typescript is annoywith with parameter forwarding from classes.ts +// typescript is annoywith with parameter forwarding from class.ts interface AssignmentParams { classid: string; id: string; diff --git a/backend/src/controllers/classes.ts b/backend/src/controllers/classes.ts index c8095162..65391781 100644 --- a/backend/src/controllers/classes.ts +++ b/backend/src/controllers/classes.ts @@ -1,6 +1,6 @@ import { Request, Response } from 'express'; import {getAllClasses, getClass, getClassStudents, getClassStudentsIds} from '../services/class'; -import { ClassDTO } from '../interfaces/classes'; +import { ClassDTO } from '../interfaces/class'; export async function getAllClassesHandler( req: Request, diff --git a/backend/src/controllers/students.ts b/backend/src/controllers/students.ts index c6973632..64822b4d 100644 --- a/backend/src/controllers/students.ts +++ b/backend/src/controllers/students.ts @@ -1,6 +1,6 @@ import { Request, Response } from 'express'; import { getAllStudents, getStudent, getStudentClasses, getStudentClassIds } from '../services/students'; -import { ClassDTO } from '../interfaces/classes'; +import { ClassDTO } from '../interfaces/class'; // TODO: accept arguments (full, ...) // TODO: endpoints @@ -27,7 +27,7 @@ export async function getStudentHandler( try { const username = req.params.id; const student = await getStudent(username); - + if (!student) { res.status(404).json({ error: "Student not found" }); return; @@ -72,4 +72,4 @@ export async function getStudentClassesHandler ( console.error('Error fetching learning objects:', error); res.status(500).json({ error: 'Internal server error' }); } -} \ No newline at end of file +} diff --git a/backend/src/controllers/teachers.ts b/backend/src/controllers/teachers.ts index 6f363c20..2ef63b65 100644 --- a/backend/src/controllers/teachers.ts +++ b/backend/src/controllers/teachers.ts @@ -9,8 +9,8 @@ import { getAllTeachersIds, getStudentsByTeacher, getStudentIdsByTeacher } from '../services/teachers.js'; import {TeacherDTO} from "../interfaces/teacher"; -import {ClassDTO} from "../interfaces/classes"; -import {StudentDTO} from "../interfaces/students"; +import {ClassDTO} from "../interfaces/class"; +import {StudentDTO} from "../interfaces/student"; export async function getTeacherHandler(req: Request, res: Response): Promise { try { diff --git a/backend/src/interfaces/assignments.ts b/backend/src/interfaces/assignment.ts similarity index 95% rename from backend/src/interfaces/assignments.ts rename to backend/src/interfaces/assignment.ts index a3c3c4cc..59242920 100644 --- a/backend/src/interfaces/assignments.ts +++ b/backend/src/interfaces/assignment.ts @@ -1,6 +1,6 @@ import { Assignment } from "../entities/assignments/assignment.entity"; import { Class } from "../entities/classes/class.entity"; -import { GroupDTO } from "./groups"; +import { GroupDTO } from "./group"; export interface AssignmentDTO { id: number, @@ -22,4 +22,4 @@ export function mapToAssignmentDTO(assignment: Assignment, cls: Class): Assignme language: assignment.learningPathLanguage, //groups: assignment.groups.map(mapToGroupDTO), }; -} \ No newline at end of file +} diff --git a/backend/src/interfaces/classes.ts b/backend/src/interfaces/class.ts similarity index 100% rename from backend/src/interfaces/classes.ts rename to backend/src/interfaces/class.ts diff --git a/backend/src/interfaces/groups.ts b/backend/src/interfaces/group.ts similarity index 100% rename from backend/src/interfaces/groups.ts rename to backend/src/interfaces/group.ts diff --git a/backend/src/interfaces/students.ts b/backend/src/interfaces/student.ts similarity index 100% rename from backend/src/interfaces/students.ts rename to backend/src/interfaces/student.ts diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts index ab76f6a8..48c6b883 100644 --- a/backend/src/services/assignments.ts +++ b/backend/src/services/assignments.ts @@ -1,5 +1,5 @@ import { getAssignmentRepository, getClassRepository } from "../data/repositories"; -import { AssignmentDTO, mapToAssignmentDTO } from "../interfaces/assignments"; +import { AssignmentDTO, mapToAssignmentDTO } from "../interfaces/assignment"; export async function getAssignment(classid: string, id: number): Promise { const classRepository = getClassRepository(); @@ -8,7 +8,7 @@ export async function getAssignment(classid: string, id: number): Promise { const classRepository = getClassRepository(); diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts index a69cab85..5def5119 100644 --- a/backend/src/services/students.ts +++ b/backend/src/services/students.ts @@ -1,8 +1,8 @@ import { getClassRepository, getStudentRepository } from "../data/repositories"; import { Class } from "../entities/classes/class.entity"; import { Student } from "../entities/users/student.entity"; -import { ClassDTO, mapToClassDTO } from "../interfaces/classes"; -import { StudentDTO, mapToStudentDTO } from "../interfaces/students"; +import { ClassDTO, mapToClassDTO } from "../interfaces/class"; +import { StudentDTO, mapToStudentDTO } from "../interfaces/student"; export async function getAllStudents(): Promise { @@ -16,8 +16,8 @@ export async function getStudent(username: string): Promise { const studentRepository = getStudentRepository(); const student = await studentRepository.findByUsername(username); - if (!student) { - return null; + if (!student) { + return null; } return mapToStudentDTO(student); diff --git a/backend/src/services/teachers.ts b/backend/src/services/teachers.ts index 39ee3997..8382e4b3 100644 --- a/backend/src/services/teachers.ts +++ b/backend/src/services/teachers.ts @@ -1,9 +1,9 @@ import {getClassRepository, getTeacherRepository} from "../data/repositories.js"; import {mapToTeacher, mapToTeacherDTO, TeacherDTO} from "../interfaces/teacher.js"; import { Teacher } from "../entities/users/teacher.entity"; -import {ClassDTO, mapToClassDTO} from "../interfaces/classes"; +import {ClassDTO, mapToClassDTO} from "../interfaces/class"; import {getClassStudents, getClassStudentsIds} from "./class"; -import {StudentDTO} from "../interfaces/students"; +import {StudentDTO} from "../interfaces/student"; async function fetchAllTeachers(): Promise { From 16b73b9e182bc5b6778fa28bc94ef70de3ac397a Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Sat, 8 Mar 2025 10:19:22 +0100 Subject: [PATCH 024/106] feat: question-teacher route --- backend/src/controllers/teachers.ts | 31 ++++++++++-- .../content/learning-object-repository.ts | 8 ++++ .../src/data/questions/question-repository.ts | 14 ++++++ backend/src/interfaces/question.ts | 41 ++++++++++++++++ backend/src/routes/teachers.ts | 19 +------- backend/src/services/teachers.ts | 47 ++++++++++++++++++- 6 files changed, 138 insertions(+), 22 deletions(-) create mode 100644 backend/src/interfaces/question.ts diff --git a/backend/src/controllers/teachers.ts b/backend/src/controllers/teachers.ts index 2ef63b65..d3b6a4db 100644 --- a/backend/src/controllers/teachers.ts +++ b/backend/src/controllers/teachers.ts @@ -2,15 +2,16 @@ import { Request, Response } from 'express'; import { createTeacher, deleteTeacher, - fetchTeacherByUsername, + getTeacherByUsername, getClassesByTeacher, getClassIdsByTeacher, getAllTeachers, - getAllTeachersIds, getStudentsByTeacher, getStudentIdsByTeacher + getAllTeachersIds, getStudentsByTeacher, getStudentIdsByTeacher, getQuestionsByTeacher, getQuestionIdsByTeacher } from '../services/teachers.js'; import {TeacherDTO} from "../interfaces/teacher"; import {ClassDTO} from "../interfaces/class"; import {StudentDTO} from "../interfaces/student"; +import {QuestionDTO, QuestionId} from "../interfaces/question"; export async function getTeacherHandler(req: Request, res: Response): Promise { try { @@ -18,7 +19,7 @@ export async function getTeacherHandler(req: Request, res: Response): Promise { + try { + 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; + } + + let questions: QuestionDTO[] | QuestionId[]; + + if (full) questions = await getQuestionsByTeacher(username); + else questions = 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' }); + } +} + + diff --git a/backend/src/data/content/learning-object-repository.ts b/backend/src/data/content/learning-object-repository.ts index 5d30b956..d8b07943 100644 --- a/backend/src/data/content/learning-object-repository.ts +++ b/backend/src/data/content/learning-object-repository.ts @@ -1,6 +1,7 @@ import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; import { LearningObject } from '../../entities/content/learning-object.entity.js'; import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js'; +import {Teacher} from "../../entities/users/teacher.entity"; export class LearningObjectRepository extends DwengoEntityRepository { public findByIdentifier( @@ -13,4 +14,11 @@ export class LearningObjectRepository extends DwengoEntityRepository { + return this.find( + { admins: teacher }, + { populate: ['admins'] } // Make sure to load admin relations + ); + } } diff --git a/backend/src/data/questions/question-repository.ts b/backend/src/data/questions/question-repository.ts index 517305f1..05430ddd 100644 --- a/backend/src/data/questions/question-repository.ts +++ b/backend/src/data/questions/question-repository.ts @@ -2,6 +2,7 @@ import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; import { Question } from '../../entities/questions/question.entity.js'; import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js'; import { Student } from '../../entities/users/student.entity.js'; +import {LearningObject} from "../../entities/content/learning-object.entity"; export class QuestionRepository extends DwengoEntityRepository { public createQuestion(question: { @@ -42,4 +43,17 @@ export class QuestionRepository extends DwengoEntityRepository { sequenceNumber: sequenceNumber, }); } + + public async findAllByLearningObjects(learningObjects: LearningObject[]): Promise { + const objectIdentifiers = learningObjects.map(lo => ({ + learningObjectHruid: lo.hruid, + learningObjectLanguage: lo.language, + learningObjectVersion: lo.version + })); + + return this.findAll({ + where: { $or: objectIdentifiers }, + orderBy: { timestamp: 'ASC' }, + }); + } } diff --git a/backend/src/interfaces/question.ts b/backend/src/interfaces/question.ts new file mode 100644 index 00000000..1a8c914a --- /dev/null +++ b/backend/src/interfaces/question.ts @@ -0,0 +1,41 @@ +import {Question} from "../entities/questions/question.entity"; +import {Enum, PrimaryKey} from "@mikro-orm/core"; +import {Language} from "../entities/content/language"; + +export interface QuestionDTO { + learningObjectHruid: string; + learningObjectLanguage: string; + learningObjectVersion: string; + sequenceNumber: number; + authorUsername: string; + timestamp: string; + content: string; + endpoints?: { + classes: string; + questions: string; + invitations: string; + groups: string; + }; +} + +/** + * Convert a Question entity to a DTO format. + */ +export function mapToQuestionDTO(question: Question): QuestionDTO { + return { + learningObjectHruid: question.learningObjectHruid, + learningObjectLanguage: question.learningObjectLanguage, + learningObjectVersion: question.learningObjectVersion, + sequenceNumber: question.sequenceNumber, + authorUsername: question.author.username, + timestamp: question.timestamp.toISOString(), + content: question.content, + }; +} + +export interface QuestionId { + learningObjectHruid: string, + learningObjectLanguage: Language, + learningObjectVersion: string, + sequenceNumber: number +} diff --git a/backend/src/routes/teachers.ts b/backend/src/routes/teachers.ts index 338ac0b8..2a29897b 100644 --- a/backend/src/routes/teachers.ts +++ b/backend/src/routes/teachers.ts @@ -3,7 +3,7 @@ import { createTeacherHandler, deleteTeacherHandler, getTeacherClassHandler, - getTeacherHandler, getTeacherStudentHandler + getTeacherHandler, getTeacherQuestionHandler, getTeacherStudentHandler } from "../controllers/teachers.js"; const router = express.Router(); @@ -18,14 +18,7 @@ router.get('/:username/classes', getTeacherClassHandler); router.get('/:username/students', getTeacherStudentHandler); -// the questions students asked a teacher -router.get('/:id/questions', (req, res) => { - res.json({ - questions: [ - '0' - ], - }); -}); +router.get('/:username/questions', getTeacherQuestionHandler); // invitations to other classes a teacher received router.get('/:id/invitations', (req, res) => { @@ -36,14 +29,6 @@ router.get('/:id/invitations', (req, res) => { }); }); -// a list with ids of classes a teacher is in -router.get('/:id/classes', (req, res) => { - res.json({ - classes: [ - '0' - ], - }); -}); export default router diff --git a/backend/src/services/teachers.ts b/backend/src/services/teachers.ts index 8382e4b3..4b8affc5 100644 --- a/backend/src/services/teachers.ts +++ b/backend/src/services/teachers.ts @@ -1,9 +1,15 @@ -import {getClassRepository, getTeacherRepository} from "../data/repositories.js"; +import { + getClassRepository, + getLearningObjectRepository, + getQuestionRepository, + getTeacherRepository +} from "../data/repositories.js"; import {mapToTeacher, mapToTeacherDTO, TeacherDTO} from "../interfaces/teacher.js"; import { Teacher } from "../entities/users/teacher.entity"; import {ClassDTO, mapToClassDTO} from "../interfaces/class"; import {getClassStudents, getClassStudentsIds} from "./class"; import {StudentDTO} from "../interfaces/student"; +import {mapToQuestionDTO, QuestionDTO, QuestionId} from "../interfaces/question"; async function fetchAllTeachers(): Promise { @@ -29,7 +35,7 @@ export async function createTeacher(teacherData: TeacherDTO): Promise { return newTeacher; } -export async function fetchTeacherByUsername(username: string): Promise { +export async function getTeacherByUsername(username: string): Promise { const teacherRepository = getTeacherRepository(); const teacher = await teacherRepository.findByUsername(username); @@ -84,5 +90,42 @@ export async function getStudentIdsByTeacher(): Promise { return await fetchStudentsByTeacher(username).map((student) => student.username); } +async function fetchTeacherQuestions(username: string): Promise { + const learningObjectRepository = getLearningObjectRepository(); + const questionRepository = getQuestionRepository(); + + const teacher = getTeacherByUsername(username); + if (!teacher) { + throw new Error(`Teacher with username '${username}' not found.`); + } + + // Find all learning objects that this teacher manages + const learningObjects = await learningObjectRepository.findAllByTeacher(teacher); + + // Fetch all questions related to these learning objects + const questions = await questionRepository.findAllByLearningObjects(learningObjects); + + return questions.map(mapToQuestionDTO); +} + +export async function getQuestionsByTeacher(username: string): Promise { + return await fetchTeacherQuestions(username); +} + +export async function getQuestionIdsByTeacher(username: string): Promise { + const questions = await fetchTeacherQuestions(username); + + return questions.map((question) => ({ + learningObjectHruid: question.learningObjectHruid, + learningObjectLanguage: question.learningObjectLanguage, + learningObjectVersion: question.learningObjectVersion, + sequenceNumber: question.sequenceNumber + })); +} + + + + + From baf43e91dec816792911f993ea3107fa6c217ae5 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sat, 8 Mar 2025 20:16:57 +0100 Subject: [PATCH 025/106] feat: teacher invitation databank api verbinding aangemaakt, bug in data repo waar teacher invitation repo niet juist werd teruggegeven gefixt --- backend/src/controllers/classes.ts | 16 +++++++++++- backend/src/data/repositories.ts | 2 +- backend/src/interfaces/teacher-invitation.ts | 25 ++++++++++++++++++ backend/src/interfaces/teacher.ts | 23 +++++++++++++++++ backend/src/routes/class.ts | 10 ++------ backend/src/services/class.ts | 27 +++++++++++++++++++- 6 files changed, 92 insertions(+), 11 deletions(-) create mode 100644 backend/src/interfaces/teacher-invitation.ts create mode 100644 backend/src/interfaces/teacher.ts diff --git a/backend/src/controllers/classes.ts b/backend/src/controllers/classes.ts index ee932734..d7580d62 100644 --- a/backend/src/controllers/classes.ts +++ b/backend/src/controllers/classes.ts @@ -1,5 +1,5 @@ import { Request, Response } from 'express'; -import { getAllClasses, getClass, getClassStudents } from '../services/class'; +import { getAllClasses, getClass, getClassStudents, getClassTeacherInvitations } from '../services/class'; import { ClassDTO } from '../interfaces/classes'; export async function getAllClassesHandler( @@ -53,4 +53,18 @@ export async function getClassStudentsHandler( res.json({ students: students, }); +} + +export async function getTeacherInvitationsHandler( + req: Request, + res: Response, +): Promise { + const classId = req.params.id; + const full = req.query.full === "true"; // TODO: not implemented yet + + const invitations = await getClassTeacherInvitations(classId, full); + + res.json({ + invitations: invitations, + }); } \ No newline at end of file diff --git a/backend/src/data/repositories.ts b/backend/src/data/repositories.ts index 843eb1ac..35ad26cd 100644 --- a/backend/src/data/repositories.ts +++ b/backend/src/data/repositories.ts @@ -80,7 +80,7 @@ export const getClassJoinRequestRepository = repositoryGetter< export const getTeacherInvitationRepository = repositoryGetter< TeacherInvitation, TeacherInvitationRepository ->(TeacherInvitationRepository); +>(TeacherInvitation); /* Assignments */ export const getAssignmentRepository = repositoryGetter< diff --git a/backend/src/interfaces/teacher-invitation.ts b/backend/src/interfaces/teacher-invitation.ts new file mode 100644 index 00000000..c563968b --- /dev/null +++ b/backend/src/interfaces/teacher-invitation.ts @@ -0,0 +1,25 @@ +import { TeacherInvitation } from "../entities/classes/teacher-invitation.entity"; +import { ClassDTO, mapToClassDTO } from "./classes"; +import { mapToTeacherDTO, TeacherDTO } from "./teacher"; + +export interface TeacherInvitationDTO { + sender: string | TeacherDTO, + receiver: string | TeacherDTO, + class: string | ClassDTO, +} + +export function mapToTeacherInvitationDTO(invitation: TeacherInvitation): TeacherInvitationDTO { + return { + sender: mapToTeacherDTO(invitation.sender), + receiver: mapToTeacherDTO(invitation.receiver), + class: mapToClassDTO(invitation.class), + }; +} + +export function mapToTeacherInvitationDTOIds(invitation: TeacherInvitation): TeacherInvitationDTO { + return { + sender: invitation.sender.username, + receiver: invitation.receiver.username, + class: invitation.class.classId, + }; +} \ No newline at end of file diff --git a/backend/src/interfaces/teacher.ts b/backend/src/interfaces/teacher.ts new file mode 100644 index 00000000..5aee089c --- /dev/null +++ b/backend/src/interfaces/teacher.ts @@ -0,0 +1,23 @@ +import { Teacher } from "../entities/users/teacher.entity"; + +export interface TeacherDTO { + id: string; + username: string; + firstName: string; + lastName: string; + endpoints?: { + classes: string; + questions: string; + invitations: string; + groups: string; + }; +} + +export function mapToTeacherDTO(teacher: Teacher): TeacherDTO { + return { + id: teacher.username, + username: teacher.username, + firstName: teacher.firstName, + lastName: teacher.lastName, + }; +} \ No newline at end of file diff --git a/backend/src/routes/class.ts b/backend/src/routes/class.ts index bc502fb8..369e6ff4 100644 --- a/backend/src/routes/class.ts +++ b/backend/src/routes/class.ts @@ -1,5 +1,5 @@ import express from 'express' -import { getAllClassesHandler, getClassHandler, getClassStudentsHandler } from '../controllers/classes'; +import { getAllClassesHandler, getClassHandler, getClassStudentsHandler, getTeacherInvitationsHandler } from '../controllers/classes'; import assignmentRouter from './assignment.js'; const router = express.Router(); @@ -10,13 +10,7 @@ router.get('/', getAllClassesHandler); // information about an class with id 'id' router.get('/:id', getClassHandler); -router.get('/:id/invitations', (req, res) => { - res.json({ - invitations: [ - '0' - ], - }); -}) +router.get('/:id/teacher-invitations', getTeacherInvitationsHandler); router.get('/:id/students', getClassStudentsHandler); diff --git a/backend/src/services/class.ts b/backend/src/services/class.ts index 89628404..b009b66c 100644 --- a/backend/src/services/class.ts +++ b/backend/src/services/class.ts @@ -1,7 +1,8 @@ -import { getClassRepository } from "../data/repositories"; +import { getClassRepository, getTeacherInvitationRepository } from "../data/repositories"; import { Class } from "../entities/classes/class.entity"; import { ClassDTO, mapToClassDTO } from "../interfaces/classes"; import { mapToStudentDTO, StudentDTO } from "../interfaces/students"; +import { mapToTeacherInvitationDTO, mapToTeacherInvitationDTOIds, TeacherInvitationDTO } from "../interfaces/teacher-invitation"; export async function getAllClasses(full: boolean): Promise { const classRepository = getClassRepository(); @@ -41,4 +42,28 @@ export async function getClassStudents(classId: string, full: boolean): Promise< } else { return cls.students.map((student) => student.username); } +} + +export async function getClassTeacherInvitations(classId: string, full: boolean): Promise { + const classRepository = getClassRepository(); + const cls = await classRepository.findById(classId); + + if (!cls) { + return []; + } + + const teacherInvitationRepository = getTeacherInvitationRepository(); + const invitations = await teacherInvitationRepository.findAllInvitationsForClass(cls); + + console.log(invitations); + + if (!invitations) { + return []; + } + + if (full) { + return invitations.map(mapToTeacherInvitationDTO); + } + + return invitations.map(mapToTeacherInvitationDTOIds); } \ No newline at end of file From 3f62ab70e1d77ad8c3718e553d923c51c5388213 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sat, 8 Mar 2025 21:49:29 +0100 Subject: [PATCH 026/106] feat: group verbinding tussen databank en api aangemaakt --- backend/src/controllers/groups.ts | 34 ++++++++++++++++++ .../src/data/assignments/group-repository.ts | 7 ++-- backend/src/interfaces/groups.ts | 22 ++++++++++++ backend/src/routes/assignment.ts | 14 +++----- backend/src/routes/group.ts | 15 ++------ backend/src/services/groups.ts | 36 +++++++++++++++++++ 6 files changed, 104 insertions(+), 24 deletions(-) create mode 100644 backend/src/controllers/groups.ts create mode 100644 backend/src/services/groups.ts diff --git a/backend/src/controllers/groups.ts b/backend/src/controllers/groups.ts new file mode 100644 index 00000000..7ca6617d --- /dev/null +++ b/backend/src/controllers/groups.ts @@ -0,0 +1,34 @@ +import { Request, Response } from 'express'; +import { getGroup } from '../services/groups'; + +// typescript is annoywith with parameter forwarding from class.ts +interface GroupParams { + classid: string; + assignmentid: string; + groupid: string; +} + +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; + + if (isNaN(assignmentId)) { + res.status(400).json({ error: "Assignment id must be a number" }); + return; + } + + const groupId = +req.params.groupid; + + if (isNaN(groupId)) { + res.status(400).json({ error: "Group id must be a number" }); + return; + } + + const group = await getGroup(classId, assignmentId, groupId, full); + + res.json(group); +} \ No newline at end of file diff --git a/backend/src/data/assignments/group-repository.ts b/backend/src/data/assignments/group-repository.ts index ff8ca507..3992e113 100644 --- a/backend/src/data/assignments/group-repository.ts +++ b/backend/src/data/assignments/group-repository.ts @@ -7,10 +7,13 @@ export class GroupRepository extends DwengoEntityRepository { assignment: Assignment, groupNumber: number ): Promise { - return this.findOne({ + return this.findOne( + { assignment: assignment, groupNumber: groupNumber, - }); + }, + { populate: ["members"] }, + ); } public findAllGroupsForAssignment( assignment: Assignment diff --git a/backend/src/interfaces/groups.ts b/backend/src/interfaces/groups.ts index 6056a906..fe78cc69 100644 --- a/backend/src/interfaces/groups.ts +++ b/backend/src/interfaces/groups.ts @@ -1,3 +1,25 @@ +import { Group } from "../entities/assignments/group.entity"; +import { AssignmentDTO, mapToAssignmentDTO } from "./assignments"; +import { mapToStudentDTO, StudentDTO } from "./students"; + export interface GroupDTO { + assignment: number | AssignmentDTO, groupNumber: number, + members: string[] | StudentDTO[], +}; + +export function mapToGroupDTO(group: Group): GroupDTO { + return { + assignment: mapToAssignmentDTO(group.assignment, group.assignment.within), + groupNumber: group.groupNumber, + members: group.members.map(mapToStudentDTO), + } +} + +export function mapToGroupDTOId(group: Group): GroupDTO { + return { + assignment: group.assignment.id, + groupNumber: group.groupNumber, + members: group.members.map(member => member.username), + } } \ No newline at end of file diff --git a/backend/src/routes/assignment.ts b/backend/src/routes/assignment.ts index e9c91160..35b4b986 100644 --- a/backend/src/routes/assignment.ts +++ b/backend/src/routes/assignment.ts @@ -1,9 +1,9 @@ import express from 'express' import { getAssignmentHandler } from '../controllers/assignments'; +import groupRouter from './group.js'; + const router = express.Router({ mergeParams: true }); - - // root endpoint used to search objects router.get('/', (req, res) => { res.json({ @@ -25,14 +25,6 @@ router.get('/:id/submissions', (req, res) => { }); }); -router.get('/:id/groups', (req, res) => { - res.json({ - groups: [ - '0' - ], - }); -}); - router.get('/:id/questions', (req, res) => { res.json({ questions: [ @@ -41,4 +33,6 @@ router.get('/:id/questions', (req, res) => { }); }); +router.use('/:assignmentid/groups', groupRouter); + export default router \ No newline at end of file diff --git a/backend/src/routes/group.ts b/backend/src/routes/group.ts index e55dddd1..436e228a 100644 --- a/backend/src/routes/group.ts +++ b/backend/src/routes/group.ts @@ -1,5 +1,6 @@ import express from 'express' -const router = express.Router(); +import { getGroupHandler } from '../controllers/groups'; +const router = express.Router({ mergeParams: true }); // root endpoint used to search objects router.get('/', (req, res) => { @@ -12,17 +13,7 @@ router.get('/', (req, res) => { }); // information about a group (members, ... [TODO DOC]) -router.get('/:id', (req, res) => { - res.json({ - id: req.params.id, - assignment: '0', - students: [ '0' ], - submissions: [ '0' ], - // reference to other endpoint - // should be less hardcoded - questions: `/group/${req.params.id}/question`, - }); -}) +router.get('/:groupid', getGroupHandler); // the list of questions a group has made router.get('/:id/question', (req, res) => { diff --git a/backend/src/services/groups.ts b/backend/src/services/groups.ts new file mode 100644 index 00000000..c1b6cf33 --- /dev/null +++ b/backend/src/services/groups.ts @@ -0,0 +1,36 @@ +import { getAssignmentRepository, getClassRepository, getGroupRepository } from "../data/repositories"; +import { GroupDTO, mapToGroupDTO, mapToGroupDTOId } from "../interfaces/groups"; + +export async function getGroup( + classId: string, + assignmentNumber: number, + groupNumber: number, + full: boolean, +): Promise { + const classRepository = getClassRepository(); + const cls = await classRepository.findById(classId); + + if (!cls) { + return null; + } + + const assignmentRepository = getAssignmentRepository(); + const assignment = await assignmentRepository.findByClassAndId(cls, assignmentNumber); + + if (!assignment) { + return null; + } + + const groupRepository = getGroupRepository(); + const group = await groupRepository.findByAssignmentAndGroupNumber(assignment, groupNumber); + + if (!group) { + return null; + } + + if (full) { + return mapToGroupDTO(group); + } + + return mapToGroupDTOId(group); +} \ No newline at end of file From e139f59afa50604c3fd41a91cd1dc40fc06c5190 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sun, 9 Mar 2025 13:04:02 +0100 Subject: [PATCH 027/106] fix: Mikro-orm versie zonder ^ --- backend/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/package.json b/backend/package.json index 7d7b87a1..fafb24f4 100644 --- a/backend/package.json +++ b/backend/package.json @@ -14,9 +14,9 @@ "test:unit": "vitest" }, "dependencies": { - "@mikro-orm/core": "^6.4.6", - "@mikro-orm/postgresql": "^6.4.6", - "@mikro-orm/reflection": "^6.4.6", + "@mikro-orm/core": "6.4.6", + "@mikro-orm/postgresql": "6.4.6", + "@mikro-orm/reflection": "6.4.6", "@mikro-orm/sqlite": "6.4.6", "axios": "^1.8.1", "dotenv": "^16.4.7", From 7e051d412a92bb8113d76c792f8eb984b342ad07 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sun, 9 Mar 2025 13:39:53 +0100 Subject: [PATCH 028/106] feat: endpoint voor alle groepen van een assignment geimplementeerd --- backend/src/controllers/groups.ts | 27 ++++++++++++++-- .../src/data/assignments/group-repository.ts | 6 +++- backend/src/routes/group.ts | 11 ++----- backend/src/services/groups.ts | 31 +++++++++++++++++++ 4 files changed, 62 insertions(+), 13 deletions(-) diff --git a/backend/src/controllers/groups.ts b/backend/src/controllers/groups.ts index 7ca6617d..1c523744 100644 --- a/backend/src/controllers/groups.ts +++ b/backend/src/controllers/groups.ts @@ -1,11 +1,11 @@ import { Request, Response } from 'express'; -import { getGroup } from '../services/groups'; +import { getAllGroups, getGroup } from '../services/groups'; // typescript is annoywith with parameter forwarding from class.ts interface GroupParams { classid: string; assignmentid: string; - groupid: string; + groupid?: string; } export async function getGroupHandler( @@ -21,7 +21,7 @@ export async function getGroupHandler( return; } - const groupId = +req.params.groupid; + const groupId = +req.params.groupid!; // can't be undefined if (isNaN(groupId)) { res.status(400).json({ error: "Group id must be a number" }); @@ -31,4 +31,25 @@ export async function getGroupHandler( const group = await getGroup(classId, assignmentId, groupId, full); res.json(group); +} + +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; + + if (isNaN(assignmentId)) { + res.status(400).json({ error: "Assignment id must be a number" }); + return; + } + + const groups = await getAllGroups(classId, assignmentId, full); + + res.json({ + groups: groups, + }); } \ No newline at end of file diff --git a/backend/src/data/assignments/group-repository.ts b/backend/src/data/assignments/group-repository.ts index 3992e113..c8770eb5 100644 --- a/backend/src/data/assignments/group-repository.ts +++ b/backend/src/data/assignments/group-repository.ts @@ -18,7 +18,11 @@ export class GroupRepository extends DwengoEntityRepository { public findAllGroupsForAssignment( assignment: Assignment ): Promise { - return this.findAll({ where: { assignment: assignment } }); + return this.findAll({ + where: { assignment: assignment }, + populate: ["members"] + }, + ); } public deleteByAssignmentAndGroupNumber( assignment: Assignment, diff --git a/backend/src/routes/group.ts b/backend/src/routes/group.ts index 436e228a..a8d4dbe5 100644 --- a/backend/src/routes/group.ts +++ b/backend/src/routes/group.ts @@ -1,16 +1,9 @@ import express from 'express' -import { getGroupHandler } from '../controllers/groups'; +import { getAllGroupsHandler, getGroupHandler } from '../controllers/groups'; const router = express.Router({ mergeParams: true }); // root endpoint used to search objects -router.get('/', (req, res) => { - res.json({ - groups: [ - '0', - '1', - ] - }); -}); +router.get('/', getAllGroupsHandler); // information about a group (members, ... [TODO DOC]) router.get('/:groupid', getGroupHandler); diff --git a/backend/src/services/groups.ts b/backend/src/services/groups.ts index c1b6cf33..c9a4024e 100644 --- a/backend/src/services/groups.ts +++ b/backend/src/services/groups.ts @@ -33,4 +33,35 @@ export async function getGroup( } return mapToGroupDTOId(group); +} + +export async function getAllGroups( + classId: string, + assignmentNumber: number, + full: boolean, +): Promise { + const classRepository = getClassRepository(); + const cls = await classRepository.findById(classId); + + if (!cls) { + return []; + } + + const assignmentRepository = getAssignmentRepository(); + const assignment = await assignmentRepository.findByClassAndId(cls, assignmentNumber); + + if (!assignment) { + return []; + } + + const groupRepository = getGroupRepository(); + const groups = await groupRepository.findAllGroupsForAssignment(assignment); + + if (full) { + console.log('full'); + console.log(groups); + return groups.map(mapToGroupDTO); + } + + return groups.map(mapToGroupDTOId); } \ No newline at end of file From 855620cb676409a23b2a49ec6b3631f0a1f3865c Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sun, 9 Mar 2025 14:26:04 +0100 Subject: [PATCH 029/106] feat: Configureer auth Swagger --- backend/src/routes/auth.ts | 3 ++ docs/api/generate.ts | 32 +++++++++++++- docs/api/swagger.json | 91 +++++++++++++++++++++++++++++++++++++- idp/student-realm.json | 2 +- idp/teacher-realm.json | 2 +- 5 files changed, 125 insertions(+), 5 deletions(-) diff --git a/backend/src/routes/auth.ts b/backend/src/routes/auth.ts index 57af3a7d..0ab5b210 100644 --- a/backend/src/routes/auth.ts +++ b/backend/src/routes/auth.ts @@ -9,14 +9,17 @@ router.get('/config', (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) => { + /* #swagger.security = [{ "student": [ ] }] */ res.json({message: "If you see this, you should be a student!"}); }); router.get('/testTeachersOnly', teachersOnly, (req, res) => { + /* #swagger.security = [{ "teacher": [ ] }] */ res.json({message: "If you see this, you should be a teacher!"}); }); diff --git a/docs/api/generate.ts b/docs/api/generate.ts index 737619bb..695f30e1 100644 --- a/docs/api/generate.ts +++ b/docs/api/generate.ts @@ -17,7 +17,37 @@ const doc = { { url: 'https://sel2-1.ugent.be/api' } - ] + ], + components: { + securitySchemes: { + student: { + type: 'oauth2', + flows: { + implicit: { + authorizationUrl: 'http://localhost:7080/realms/student/protocol/openid-connect/auth', + scopes: { + openid: 'openid', + profile: 'profile', + email: 'email' + } + } + } + }, + teacher: { + type: 'oauth2', + flows: { + implicit: { + authorizationUrl: 'http://localhost:7080/realms/teacher/protocol/openid-connect/auth', + scopes: { + openid: 'openid', + profile: 'profile', + email: 'email' + } + } + } + } + } + } }; const outputFile = './swagger.json'; diff --git a/docs/api/swagger.json b/docs/api/swagger.json index 4f231cbd..85039f09 100644 --- a/docs/api/swagger.json +++ b/docs/api/swagger.json @@ -520,10 +520,10 @@ } } }, - "/login/": { + "/auth/config": { "get": { "tags": [ - "Login" + "Auth" ], "description": "", "responses": { @@ -533,6 +533,63 @@ } } }, + "/auth/testAuthenticatedOnly": { + "get": { + "tags": [ + "Auth" + ], + "description": "", + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "student": [] + }, + { + "teacher": [] + } + ] + } + }, + "/auth/testStudentsOnly": { + "get": { + "tags": [ + "Auth" + ], + "description": "", + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "student": [] + } + ] + } + }, + "/auth/testTeachersOnly": { + "get": { + "tags": [ + "Auth" + ], + "description": "", + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "teacher": [] + } + ] + } + }, "/theme/": { "get": { "tags": [ @@ -708,5 +765,35 @@ } } } + }, + "components": { + "securitySchemes": { + "student": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "http://localhost:7080/realms/student/protocol/openid-connect/auth", + "scopes": { + "openid": "openid", + "profile": "profile", + "email": "email" + } + } + } + }, + "teacher": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "http://localhost:7080/realms/teacher/protocol/openid-connect/auth", + "scopes": { + "openid": "openid", + "profile": "profile", + "email": "email" + } + } + } + } + } } } \ No newline at end of file diff --git a/idp/student-realm.json b/idp/student-realm.json index 7b6bc94b..15cbc666 100644 --- a/idp/student-realm.json +++ b/idp/student-realm.json @@ -551,7 +551,7 @@ "enabled" : true, "alwaysDisplayInConsole" : false, "clientAuthenticatorType" : "client-jwt", - "redirectUris" : [ "urn:ietf:wg:oauth:2.0:oob", "http://localhost:5173/*", "http://localhost:5173" ], + "redirectUris" : [ "urn:ietf:wg:oauth:2.0:oob", "http://localhost:5173/*", "http://localhost:5173", "http://localhost:3000/api-docs/oauth2-redirect.html" ], "webOrigins" : [ "+" ], "notBefore" : 0, "bearerOnly" : false, diff --git a/idp/teacher-realm.json b/idp/teacher-realm.json index f1bc513a..b5e88c22 100644 --- a/idp/teacher-realm.json +++ b/idp/teacher-realm.json @@ -551,7 +551,7 @@ "enabled" : true, "alwaysDisplayInConsole" : false, "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ "urn:ietf:wg:oauth:2.0:oob", "http://localhost:5173/*", "http://localhost:5173" ], + "redirectUris" : [ "urn:ietf:wg:oauth:2.0:oob", "http://localhost:5173/*", "http://localhost:5173", "http://localhost:3000/api-docs/oauth2-redirect.html" ], "webOrigins" : [ "+" ], "notBefore" : 0, "bearerOnly" : false, From 834ff236aa9b23433627e1b98eb0c0a29c050d3c Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Sun, 9 Mar 2025 14:26:41 +0100 Subject: [PATCH 030/106] docs: Beschrijving API servers --- docs/api/generate.ts | 4 +++- docs/api/swagger.json | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/api/generate.ts b/docs/api/generate.ts index 695f30e1..053c289f 100644 --- a/docs/api/generate.ts +++ b/docs/api/generate.ts @@ -13,9 +13,11 @@ const doc = { servers: [ { url: 'http://localhost:3000/', + description: 'Development server' }, { - url: 'https://sel2-1.ugent.be/api' + url: 'https://sel2-1.ugent.be/api', + description: 'Production server' } ], components: { diff --git a/docs/api/swagger.json b/docs/api/swagger.json index 85039f09..8f257518 100644 --- a/docs/api/swagger.json +++ b/docs/api/swagger.json @@ -11,10 +11,12 @@ }, "servers": [ { - "url": "http://localhost:3000/" + "url": "http://localhost:3000/", + "description": "Development server" }, { - "url": "https://sel2-1.ugent.be/api" + "url": "https://sel2-1.ugent.be/api", + "description": "Production server" } ], "paths": { From e0a5596994770305c79aa3859d696f8b7e9d4140 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sun, 9 Mar 2025 15:31:10 +0100 Subject: [PATCH 031/106] feat: alle assignments en student's assignments geimplementeerd --- backend/src/controllers/assignments.ts | 18 ++++++++++++++++-- backend/src/controllers/students.ts | 20 ++++++++++++++++++++ backend/src/interfaces/assignments.ts | 22 +++++++++++++++++----- backend/src/routes/assignment.ts | 11 ++--------- backend/src/routes/student.ts | 8 ++------ backend/src/services/assignments.ts | 22 ++++++++++++++++++++-- 6 files changed, 77 insertions(+), 24 deletions(-) diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index 775a60df..78d11336 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -1,12 +1,26 @@ import { Request, Response } from 'express' -import { getAssignment } from '../services/assignments'; +import { getAllAssignments, getAssignment } from '../services/assignments'; -// typescript is annoywith with parameter forwarding from class.ts +// typescript is annoy with with parameter forwarding from class.ts interface AssignmentParams { classid: string; id: string; } +export async function getAllAssignmentsHandler( + req: Request, + res: Response, +): Promise { + const classid = req.params.classid; + const full = req.query.full === 'true'; + + const assignments = await getAllAssignments(classid, full); + + res.json({ + assignments: assignments, + }); +} + export async function getAssignmentHandler( req: Request, res: Response, diff --git a/backend/src/controllers/students.ts b/backend/src/controllers/students.ts index c6973632..68a5fc76 100644 --- a/backend/src/controllers/students.ts +++ b/backend/src/controllers/students.ts @@ -1,6 +1,7 @@ import { Request, Response } from 'express'; import { getAllStudents, getStudent, getStudentClasses, getStudentClassIds } from '../services/students'; import { ClassDTO } from '../interfaces/classes'; +import { getAllAssignments } from '../services/assignments'; // TODO: accept arguments (full, ...) // TODO: endpoints @@ -72,4 +73,23 @@ export async function getStudentClassesHandler ( console.error('Error fetching learning objects:', error); res.status(500).json({ error: 'Internal server error' }); } +} + +// Might not be fully correct depending on if +// a class has an assignment, that all students +// have this assignment. +export async function getStudentAssignmentsHandler( + req: Request, + res: Response, +): Promise { + const full = req.query.full === 'true'; + const username = req.params.id; + + const classes = await getStudentClasses(username); + + const assignments = (await Promise.all(classes.map(async cls => await getAllAssignments(cls.id, full)))).flat(); + + res.json({ + assignments: assignments + }); } \ No newline at end of file diff --git a/backend/src/interfaces/assignments.ts b/backend/src/interfaces/assignments.ts index a3c3c4cc..7fabece5 100644 --- a/backend/src/interfaces/assignments.ts +++ b/backend/src/interfaces/assignments.ts @@ -1,6 +1,6 @@ import { Assignment } from "../entities/assignments/assignment.entity"; import { Class } from "../entities/classes/class.entity"; -import { GroupDTO } from "./groups"; +import { GroupDTO, mapToGroupDTO } from "./groups"; export interface AssignmentDTO { id: number, @@ -9,17 +9,29 @@ export interface AssignmentDTO { description: string, learningPath: string, language: string, - groups?: GroupDTO[], // TODO + groups?: GroupDTO[] | string[], // TODO } -export function mapToAssignmentDTO(assignment: Assignment, cls: Class): AssignmentDTO { +export function mapToAssignmentDTOId(assignment: Assignment): AssignmentDTO { return { id: assignment.id, - class: cls.classId, + class: assignment.within.classId, title: assignment.title, description: assignment.description, learningPath: assignment.learningPathHruid, language: assignment.learningPathLanguage, - //groups: assignment.groups.map(mapToGroupDTO), + // groups: assignment.groups.map(group => group.groupNumber), + } +} + +export function mapToAssignmentDTO(assignment: Assignment): AssignmentDTO { + return { + id: assignment.id, + class: assignment.within.classId, + title: assignment.title, + description: assignment.description, + learningPath: assignment.learningPathHruid, + language: assignment.learningPathLanguage, + // groups: assignment.groups.map(mapToGroupDTO), }; } \ No newline at end of file diff --git a/backend/src/routes/assignment.ts b/backend/src/routes/assignment.ts index 35b4b986..d6965c49 100644 --- a/backend/src/routes/assignment.ts +++ b/backend/src/routes/assignment.ts @@ -1,18 +1,11 @@ import express from 'express' -import { getAssignmentHandler } from '../controllers/assignments'; +import { getAllAssignmentsHandler, getAssignmentHandler } from '../controllers/assignments'; import groupRouter from './group.js'; const router = express.Router({ mergeParams: true }); // root endpoint used to search objects -router.get('/', (req, res) => { - res.json({ - assignments: [ - '0', - '1', - ] - }); -}); +router.get('/', getAllAssignmentsHandler); // information about an assignment with id 'id' router.get('/:id', getAssignmentHandler); diff --git a/backend/src/routes/student.ts b/backend/src/routes/student.ts index ca74d2f7..bc806b4e 100644 --- a/backend/src/routes/student.ts +++ b/backend/src/routes/student.ts @@ -1,5 +1,5 @@ import express from 'express' -import { getAllStudentsHandler, getStudentClassesHandler, getStudentHandler } from '../controllers/students'; +import { getAllStudentsHandler, getStudentAssignmentsHandler, getStudentClassesHandler, getStudentHandler } from '../controllers/students'; const router = express.Router(); // root endpoint used to search objects @@ -20,11 +20,7 @@ router.get('/:id/submissions', (req, res) => { // the list of assignments a student has -router.get('/:id/assignments', (req, res) => { - res.json({ - assignments: [ '0' ], - }); -}) +router.get('/:id/assignments', getStudentAssignmentsHandler); // the list of groups a student is in router.get('/:id/groups', (req, res) => { diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts index ab76f6a8..e0804750 100644 --- a/backend/src/services/assignments.ts +++ b/backend/src/services/assignments.ts @@ -1,5 +1,23 @@ import { getAssignmentRepository, getClassRepository } from "../data/repositories"; -import { AssignmentDTO, mapToAssignmentDTO } from "../interfaces/assignments"; +import { AssignmentDTO, mapToAssignmentDTO, mapToAssignmentDTOId } from "../interfaces/assignments"; + +export async function getAllAssignments(classid: string, full: boolean): Promise { + const classRepository = getClassRepository(); + const cls = await classRepository.findById(classid); + + if (!cls) { + return []; + } + + const assignmentRepository = getAssignmentRepository(); + const assignments = await assignmentRepository.findAllAssignmentsInClass(cls); + + if (full) { + return assignments.map(mapToAssignmentDTO); + } + + return assignments.map(mapToAssignmentDTOId); +} export async function getAssignment(classid: string, id: number): Promise { const classRepository = getClassRepository(); @@ -16,5 +34,5 @@ export async function getAssignment(classid: string, id: number): Promise Date: Sun, 9 Mar 2025 20:18:11 +0100 Subject: [PATCH 032/106] feat: add, delete student route met user logic + .js in files --- backend/src/app.ts | 1 + backend/src/controllers/assignments.ts | 4 +- backend/src/controllers/classes.ts | 7 +- backend/src/controllers/groups.ts | 4 +- backend/src/controllers/students.ts | 69 +++++------- backend/src/controllers/users.ts | 106 ++++++++++++++++++ backend/src/data/repositories.ts | 2 +- backend/src/data/users/student-repository.ts | 11 +- backend/src/data/users/teacher-repository.ts | 11 +- backend/src/data/users/user-repository.ts | 8 +- .../content/learning-object.entity.ts | 18 +-- backend/src/entities/users/student.entity.ts | 8 -- backend/src/interfaces/assignments.ts | 7 +- backend/src/interfaces/classes.ts | 2 +- backend/src/interfaces/groups.ts | 10 +- backend/src/interfaces/students.ts | 13 ++- backend/src/interfaces/teacher-invitation.ts | 8 +- backend/src/interfaces/teacher.ts | 4 +- backend/src/interfaces/user.ts | 30 +++++ backend/src/routes/assignment.ts | 4 +- backend/src/routes/class.ts | 4 +- backend/src/routes/group.ts | 4 +- backend/src/routes/student.ts | 22 +++- backend/src/services/assignments.ts | 10 +- backend/src/services/class.ts | 13 +-- backend/src/services/groups.ts | 10 +- backend/src/services/students.ts | 29 ++--- backend/src/services/users.ts | 39 +++++++ backend/src/util/translationHelper.ts | 2 +- 29 files changed, 301 insertions(+), 159 deletions(-) create mode 100644 backend/src/controllers/users.ts create mode 100644 backend/src/interfaces/user.ts create mode 100644 backend/src/services/users.ts diff --git a/backend/src/app.ts b/backend/src/app.ts index e4da44e7..8f01dea6 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -16,6 +16,7 @@ import loginRouter from './routes/login.js'; const app: Express = express(); const port: string | number = getNumericEnvVar(EnvVars.Port); +app.use(express.json()); // TODO Replace with Express routes app.get('/', (_, res: Response) => { diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index 78d11336..ab0f951f 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -1,5 +1,5 @@ import { Request, Response } from 'express' -import { getAllAssignments, getAssignment } from '../services/assignments'; +import { getAllAssignments, getAssignment } from '../services/assignments.js'; // typescript is annoy with with parameter forwarding from class.ts interface AssignmentParams { @@ -41,4 +41,4 @@ export async function getAssignmentHandler( } res.json(assignment); -} \ No newline at end of file +} diff --git a/backend/src/controllers/classes.ts b/backend/src/controllers/classes.ts index d7580d62..9f8f4669 100644 --- a/backend/src/controllers/classes.ts +++ b/backend/src/controllers/classes.ts @@ -1,6 +1,5 @@ import { Request, Response } from 'express'; -import { getAllClasses, getClass, getClassStudents, getClassTeacherInvitations } from '../services/class'; -import { ClassDTO } from '../interfaces/classes'; +import { getAllClasses, getClass, getClassStudents, getClassTeacherInvitations } from '../services/class.js'; export async function getAllClassesHandler( req: Request, @@ -61,10 +60,10 @@ export async function getTeacherInvitationsHandler( ): Promise { const classId = req.params.id; const full = req.query.full === "true"; // TODO: not implemented yet - + const invitations = await getClassTeacherInvitations(classId, full); res.json({ invitations: invitations, }); -} \ No newline at end of file +} diff --git a/backend/src/controllers/groups.ts b/backend/src/controllers/groups.ts index 1c523744..d301c98d 100644 --- a/backend/src/controllers/groups.ts +++ b/backend/src/controllers/groups.ts @@ -1,5 +1,5 @@ import { Request, Response } from 'express'; -import { getAllGroups, getGroup } from '../services/groups'; +import { getAllGroups, getGroup } from '../services/groups.js'; // typescript is annoywith with parameter forwarding from class.ts interface GroupParams { @@ -52,4 +52,4 @@ export async function getAllGroupsHandler( res.json({ groups: groups, }); -} \ No newline at end of file +} diff --git a/backend/src/controllers/students.ts b/backend/src/controllers/students.ts index 68a5fc76..b4fe7b47 100644 --- a/backend/src/controllers/students.ts +++ b/backend/src/controllers/students.ts @@ -1,53 +1,33 @@ import { Request, Response } from 'express'; -import { getAllStudents, getStudent, getStudentClasses, getStudentClassIds } from '../services/students'; -import { ClassDTO } from '../interfaces/classes'; -import { getAllAssignments } from '../services/assignments'; +import { + getStudentClasses, + getStudentClassIds, + StudentService +} from '../services/students.js'; +import { ClassDTO } from '../interfaces/classes.js'; +import { getAllAssignments } from '../services/assignments.js'; +import {createUserHandler, deleteUserHandler, getAllUsersHandler, getUserHandler} from "./users.js"; +import { Student } from "../entities/users/student.entity.js"; // TODO: accept arguments (full, ...) // TODO: endpoints -export async function getAllStudentsHandler ( - req: Request, - res: Response, -): Promise { - try { - const students = await getAllStudents(); - - res.json({ - students: students - }); - } catch (error) { - console.error('Error fetching learning objects:', error); - res.status(500).json({ error: 'Internal server error' }); - } +export async function getAllStudentsHandler (req: Request, res: Response): Promise { + await getAllUsersHandler(req, res, new StudentService()); } -export async function getStudentHandler( - req: Request, - res: Response, -): Promise { - try { - const username = req.params.id; - const student = await getStudent(username); - - if (!student) { - res.status(404).json({ error: "Student not found" }); - return; - } else { - student.endpoints = { - classes: `/student/${req.params.id}/classes`, - questions: `/student/${req.params.id}/submissions`, - invitations: `/student/${req.params.id}/assignments`, - groups: `/student/${req.params.id}/groups`, - } - res.json(student); - } - - } catch (error) { - console.error('Error fetching learning objects:', error); - res.status(500).json({ error: 'Internal server error' }); - } +export async function getStudentHandler(req: Request, res: Response): Promise { + await getUserHandler(req, res, new StudentService()); } +export async function createStudentHandler(req: Request, res: Response): Promise { + await createUserHandler(req, res, new StudentService(), Student); +} + +export async function deleteStudentHandler(req: Request, res: Response): Promise { + await deleteUserHandler(req, res, new StudentService()); +} + + export async function getStudentClassesHandler ( req: Request, res: Response, @@ -75,6 +55,7 @@ export async function getStudentClassesHandler ( } } +// TODO // Might not be fully correct depending on if // a class has an assignment, that all students // have this assignment. @@ -92,4 +73,6 @@ export async function getStudentAssignmentsHandler( res.json({ assignments: assignments }); -} \ No newline at end of file +} + + diff --git a/backend/src/controllers/users.ts b/backend/src/controllers/users.ts new file mode 100644 index 00000000..4b29617e --- /dev/null +++ b/backend/src/controllers/users.ts @@ -0,0 +1,106 @@ +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 async function getAllUsersHandler( + req: Request, + res: Response, + service: UserService +): Promise { + try { + const full = req.query.full === 'true'; + + let 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 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" }); + } +} diff --git a/backend/src/data/repositories.ts b/backend/src/data/repositories.ts index 35ad26cd..e65bb882 100644 --- a/backend/src/data/repositories.ts +++ b/backend/src/data/repositories.ts @@ -59,7 +59,7 @@ function repositoryGetter>( } /* Users */ -export const getUserRepository = repositoryGetter(User); +export const getUserRepository = repositoryGetter>(User); export const getStudentRepository = repositoryGetter< Student, StudentRepository diff --git a/backend/src/data/users/student-repository.ts b/backend/src/data/users/student-repository.ts index 1c3a6fae..c553aab8 100644 --- a/backend/src/data/users/student-repository.ts +++ b/backend/src/data/users/student-repository.ts @@ -1,11 +1,4 @@ -import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; import { Student } from '../../entities/users/student.entity.js'; +import {UserRepository} from "./user-repository.js"; -export class StudentRepository extends DwengoEntityRepository { - public findByUsername(username: string): Promise { - return this.findOne({ username: username }); - } - public deleteByUsername(username: string): Promise { - return this.deleteWhere({ username: username }); - } -} +export class StudentRepository extends UserRepository {} diff --git a/backend/src/data/users/teacher-repository.ts b/backend/src/data/users/teacher-repository.ts index 704ef409..9940b4bd 100644 --- a/backend/src/data/users/teacher-repository.ts +++ b/backend/src/data/users/teacher-repository.ts @@ -1,11 +1,4 @@ -import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; import { Teacher } from '../../entities/users/teacher.entity.js'; +import {UserRepository} from "./user-repository.js"; -export class TeacherRepository extends DwengoEntityRepository { - public findByUsername(username: string): Promise { - return this.findOne({ username: username }); - } - public deleteByUsername(username: string): Promise { - return this.deleteWhere({ username: username }); - } -} +export class TeacherRepository extends UserRepository {} diff --git a/backend/src/data/users/user-repository.ts b/backend/src/data/users/user-repository.ts index 7e2a42ad..21497b79 100644 --- a/backend/src/data/users/user-repository.ts +++ b/backend/src/data/users/user-repository.ts @@ -1,11 +1,11 @@ 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 { - return this.findOne({ username: username }); +export class UserRepository extends DwengoEntityRepository { + public findByUsername(username: string): Promise { + return this.findOne({ username } as Partial); } public deleteByUsername(username: string): Promise { - return this.deleteWhere({ username: username }); + return this.deleteWhere({ username } as Partial); } } diff --git a/backend/src/entities/content/learning-object.entity.ts b/backend/src/entities/content/learning-object.entity.ts index 179a2f5f..78d4aa00 100644 --- a/backend/src/entities/content/learning-object.entity.ts +++ b/backend/src/entities/content/learning-object.entity.ts @@ -13,6 +13,15 @@ import { Attachment } from './attachment.entity.js'; import { Teacher } from '../users/teacher.entity.js'; import { LearningObjectRepository } from '../../data/content/learning-object-repository.js'; +@Embeddable() +export class ReturnValue { + @Property({ type: 'string' }) + callbackUrl!: string; + + @Property({ type: 'json' }) + callbackSchema!: string; +} + @Entity({ repository: () => LearningObjectRepository }) export class LearningObject { @PrimaryKey({ type: 'string' }) @@ -88,15 +97,6 @@ export class EducationalGoal { id!: string; } -@Embeddable() -export class ReturnValue { - @Property({ type: 'string' }) - callbackUrl!: string; - - @Property({ type: 'json' }) - callbackSchema!: string; -} - export enum ContentType { Markdown = 'text/markdown', Image = 'image/image', diff --git a/backend/src/entities/users/student.entity.ts b/backend/src/entities/users/student.entity.ts index dc791adc..401b1ed2 100644 --- a/backend/src/entities/users/student.entity.ts +++ b/backend/src/entities/users/student.entity.ts @@ -11,12 +11,4 @@ export class Student extends User { @ManyToMany(() => Group) groups!: Collection; - - constructor( - public username: string, - public firstName: string, - public lastName: string - ) { - super(); - } } diff --git a/backend/src/interfaces/assignments.ts b/backend/src/interfaces/assignments.ts index 7fabece5..146a0517 100644 --- a/backend/src/interfaces/assignments.ts +++ b/backend/src/interfaces/assignments.ts @@ -1,6 +1,5 @@ -import { Assignment } from "../entities/assignments/assignment.entity"; -import { Class } from "../entities/classes/class.entity"; -import { GroupDTO, mapToGroupDTO } from "./groups"; +import { Assignment } from "../entities/assignments/assignment.entity.js"; +import { GroupDTO, mapToGroupDTO } from "./groups.js"; export interface AssignmentDTO { id: number, @@ -34,4 +33,4 @@ export function mapToAssignmentDTO(assignment: Assignment): AssignmentDTO { language: assignment.learningPathLanguage, // groups: assignment.groups.map(mapToGroupDTO), }; -} \ No newline at end of file +} diff --git a/backend/src/interfaces/classes.ts b/backend/src/interfaces/classes.ts index 5c285446..0274d90c 100644 --- a/backend/src/interfaces/classes.ts +++ b/backend/src/interfaces/classes.ts @@ -1,4 +1,4 @@ -import { Class } from "../entities/classes/class.entity"; +import { Class } from "../entities/classes/class.entity.js"; export interface ClassDTO { id: string; diff --git a/backend/src/interfaces/groups.ts b/backend/src/interfaces/groups.ts index fe78cc69..c4c95baf 100644 --- a/backend/src/interfaces/groups.ts +++ b/backend/src/interfaces/groups.ts @@ -1,6 +1,6 @@ -import { Group } from "../entities/assignments/group.entity"; -import { AssignmentDTO, mapToAssignmentDTO } from "./assignments"; -import { mapToStudentDTO, StudentDTO } from "./students"; +import { Group } from "../entities/assignments/group.entity.js"; +import { AssignmentDTO, mapToAssignmentDTO } from "./assignments.js"; +import { mapToStudentDTO, StudentDTO } from "./students.js"; export interface GroupDTO { assignment: number | AssignmentDTO, @@ -10,7 +10,7 @@ export interface GroupDTO { export function mapToGroupDTO(group: Group): GroupDTO { return { - assignment: mapToAssignmentDTO(group.assignment, group.assignment.within), + assignment: mapToAssignmentDTO(group.assignment), // ERROR: , group.assignment.within), groupNumber: group.groupNumber, members: group.members.map(mapToStudentDTO), } @@ -22,4 +22,4 @@ export function mapToGroupDTOId(group: Group): GroupDTO { groupNumber: group.groupNumber, members: group.members.map(member => member.username), } -} \ No newline at end of file +} diff --git a/backend/src/interfaces/students.ts b/backend/src/interfaces/students.ts index e87e707d..c18461e8 100644 --- a/backend/src/interfaces/students.ts +++ b/backend/src/interfaces/students.ts @@ -1,4 +1,4 @@ -import { Student } from "../entities/users/student.entity"; +import { Student } from "../entities/users/student.entity.js"; export interface StudentDTO { id: string; @@ -20,4 +20,13 @@ export function mapToStudentDTO(student: Student): StudentDTO { firstName: student.firstName, lastName: student.lastName, }; -} \ No newline at end of file +} + +export function mapToStudent(studentData: StudentDTO): Student { + const student = new Student(); + student.username = studentData.username; + student.firstName = studentData.firstName; + student.lastName = studentData.lastName; + + return student; +} diff --git a/backend/src/interfaces/teacher-invitation.ts b/backend/src/interfaces/teacher-invitation.ts index c563968b..489e5565 100644 --- a/backend/src/interfaces/teacher-invitation.ts +++ b/backend/src/interfaces/teacher-invitation.ts @@ -1,6 +1,6 @@ -import { TeacherInvitation } from "../entities/classes/teacher-invitation.entity"; -import { ClassDTO, mapToClassDTO } from "./classes"; -import { mapToTeacherDTO, TeacherDTO } from "./teacher"; +import { TeacherInvitation } from "../entities/classes/teacher-invitation.entity.js"; +import { ClassDTO, mapToClassDTO } from "./classes.js"; +import { mapToTeacherDTO, TeacherDTO } from "./teacher.js"; export interface TeacherInvitationDTO { sender: string | TeacherDTO, @@ -22,4 +22,4 @@ export function mapToTeacherInvitationDTOIds(invitation: TeacherInvitation): Tea receiver: invitation.receiver.username, class: invitation.class.classId, }; -} \ No newline at end of file +} diff --git a/backend/src/interfaces/teacher.ts b/backend/src/interfaces/teacher.ts index 5aee089c..4f489c57 100644 --- a/backend/src/interfaces/teacher.ts +++ b/backend/src/interfaces/teacher.ts @@ -1,4 +1,4 @@ -import { Teacher } from "../entities/users/teacher.entity"; +import { Teacher } from "../entities/users/teacher.entity.js"; export interface TeacherDTO { id: string; @@ -20,4 +20,4 @@ export function mapToTeacherDTO(teacher: Teacher): TeacherDTO { firstName: teacher.firstName, lastName: teacher.lastName, }; -} \ No newline at end of file +} diff --git a/backend/src/interfaces/user.ts b/backend/src/interfaces/user.ts new file mode 100644 index 00000000..02e27d83 --- /dev/null +++ b/backend/src/interfaces/user.ts @@ -0,0 +1,30 @@ +import { User } from "../entities/users/user.entity.js"; + +export interface UserDTO { + id?: string, + username: string; + firstName: string; + lastName: string; + endpoints?: { + self: string; + classes: string; + questions: string; + invitations: string; + }; +} + +export function mapToUserDTO(user: User): UserDTO { + return { + id: user.username, + username: user.username, + firstName: user.firstName, + lastName: user.lastName, + }; +} + +export function mapToUser(userData: UserDTO, userInstance: T): T { + userInstance.username = userData.username; + userInstance.firstName = userData.firstName; + userInstance.lastName = userData.lastName; + return userInstance; +} diff --git a/backend/src/routes/assignment.ts b/backend/src/routes/assignment.ts index d6965c49..9d83e755 100644 --- a/backend/src/routes/assignment.ts +++ b/backend/src/routes/assignment.ts @@ -1,5 +1,5 @@ import express from 'express' -import { getAllAssignmentsHandler, getAssignmentHandler } from '../controllers/assignments'; +import { getAllAssignmentsHandler, getAssignmentHandler } from '../controllers/assignments.js'; import groupRouter from './group.js'; const router = express.Router({ mergeParams: true }); @@ -28,4 +28,4 @@ router.get('/:id/questions', (req, res) => { router.use('/:assignmentid/groups', groupRouter); -export default router \ No newline at end of file +export default router diff --git a/backend/src/routes/class.ts b/backend/src/routes/class.ts index 369e6ff4..06154e60 100644 --- a/backend/src/routes/class.ts +++ b/backend/src/routes/class.ts @@ -1,5 +1,5 @@ import express from 'express' -import { getAllClassesHandler, getClassHandler, getClassStudentsHandler, getTeacherInvitationsHandler } from '../controllers/classes'; +import { getAllClassesHandler, getClassHandler, getClassStudentsHandler, getTeacherInvitationsHandler } from '../controllers/classes.js'; import assignmentRouter from './assignment.js'; const router = express.Router(); @@ -16,4 +16,4 @@ router.get('/:id/students', getClassStudentsHandler); router.use('/:classid/assignments', assignmentRouter); -export default router \ No newline at end of file +export default router diff --git a/backend/src/routes/group.ts b/backend/src/routes/group.ts index a8d4dbe5..76cdc61f 100644 --- a/backend/src/routes/group.ts +++ b/backend/src/routes/group.ts @@ -1,5 +1,5 @@ import express from 'express' -import { getAllGroupsHandler, getGroupHandler } from '../controllers/groups'; +import { getAllGroupsHandler, getGroupHandler } from '../controllers/groups.js'; const router = express.Router({ mergeParams: true }); // root endpoint used to search objects @@ -15,4 +15,4 @@ router.get('/:id/question', (req, res) => { }); }) -export default router \ No newline at end of file +export default router diff --git a/backend/src/routes/student.ts b/backend/src/routes/student.ts index bc806b4e..a5355c48 100644 --- a/backend/src/routes/student.ts +++ b/backend/src/routes/student.ts @@ -1,12 +1,24 @@ import express from 'express' -import { getAllStudentsHandler, getStudentAssignmentsHandler, getStudentClassesHandler, getStudentHandler } from '../controllers/students'; +import { + createStudentHandler, deleteStudentHandler, + getAllStudentsHandler, + getStudentAssignmentsHandler, + getStudentClassesHandler, + getStudentHandler +} from '../controllers/students.js'; const router = express.Router(); // root endpoint used to search objects router.get('/', getAllStudentsHandler); +router.post('/', createStudentHandler); + +router.delete('/:username', deleteStudentHandler); + // information about a student's profile -router.get('/:id', getStudentHandler); +router.get('/:username', getStudentHandler); + + // the list of classes a student is in router.get('/:id/classes', getStudentClassesHandler); @@ -18,10 +30,10 @@ router.get('/:id/submissions', (req, res) => { }); }) - + // the list of assignments a student has router.get('/:id/assignments', getStudentAssignmentsHandler); - + // the list of groups a student is in router.get('/:id/groups', (req, res) => { res.json({ @@ -36,4 +48,4 @@ router.get('/:id/questions', (req, res) => { }); }) -export default router \ No newline at end of file +export default router diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts index e0804750..aaa51c10 100644 --- a/backend/src/services/assignments.ts +++ b/backend/src/services/assignments.ts @@ -1,5 +1,5 @@ -import { getAssignmentRepository, getClassRepository } from "../data/repositories"; -import { AssignmentDTO, mapToAssignmentDTO, mapToAssignmentDTOId } from "../interfaces/assignments"; +import { getAssignmentRepository, getClassRepository } from "../data/repositories.js"; +import { AssignmentDTO, mapToAssignmentDTO, mapToAssignmentDTOId } from "../interfaces/assignments.js"; export async function getAllAssignments(classid: string, full: boolean): Promise { const classRepository = getClassRepository(); @@ -8,7 +8,7 @@ export async function getAllAssignments(classid: string, full: boolean): Promise if (!cls) { return []; } - + const assignmentRepository = getAssignmentRepository(); const assignments = await assignmentRepository.findAllAssignmentsInClass(cls); @@ -26,7 +26,7 @@ export async function getAssignment(classid: string, id: number): Promise { const classRepository = getClassRepository(); @@ -11,7 +10,7 @@ export async function getAllClasses(full: boolean): Promise { @@ -64,4 +64,4 @@ export async function getAllGroups( } return groups.map(mapToGroupDTOId); -} \ No newline at end of file +} diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts index a69cab85..24c9029b 100644 --- a/backend/src/services/students.ts +++ b/backend/src/services/students.ts @@ -1,26 +1,13 @@ -import { getClassRepository, getStudentRepository } from "../data/repositories"; -import { Class } from "../entities/classes/class.entity"; -import { Student } from "../entities/users/student.entity"; -import { ClassDTO, mapToClassDTO } from "../interfaces/classes"; -import { StudentDTO, mapToStudentDTO } from "../interfaces/students"; +import { getClassRepository, getStudentRepository } from "../data/repositories.js"; +import { Class } from "../entities/classes/class.entity.js"; +import { Student } from "../entities/users/student.entity.js"; +import { ClassDTO, mapToClassDTO } from "../interfaces/classes.js"; +import {UserService} from "./users.js"; - -export async function getAllStudents(): Promise { - const studentRepository = getStudentRepository(); - const students = await studentRepository.find({}); - - return students.map(mapToStudentDTO); -} - -export async function getStudent(username: string): Promise { - const studentRepository = getStudentRepository(); - const student = await studentRepository.findByUsername(username); - - if (!student) { - return null; +export class StudentService extends UserService { + constructor() { + super(getStudentRepository()); } - - return mapToStudentDTO(student); } async function fetchStudentClasses(username: string): Promise { diff --git a/backend/src/services/users.ts b/backend/src/services/users.ts new file mode 100644 index 00000000..bf88e916 --- /dev/null +++ b/backend/src/services/users.ts @@ -0,0 +1,39 @@ +import { UserRepository } from "../data/users/user-repository.js"; +import { UserDTO, mapToUser, mapToUserDTO } from "../interfaces/user.js"; +import {User} from "../entities/users/user.entity.js"; + +export class UserService { + protected repository: UserRepository; + + constructor(repository: UserRepository) { + this.repository = repository; + } + + async getAllUsers(): Promise { + const users = await this.repository.findAll(); + return users.map(mapToUserDTO); + } + + async getAllUserIds(): Promise { + const users = await this.getAllUsers(); + return users.map((user) => user.username); + } + + async getUserByUsername(username: string): Promise { + const user = await this.repository.findByUsername(username) + return user ? mapToUserDTO(user) : null; + } + + async createUser(userData: UserDTO, UserClass: new () => T): Promise { + const newUser = mapToUser(userData, new UserClass()); + await this.repository.save(newUser); + return newUser; + } + + async deleteUser(username: string): Promise { + const user = await this.getUserByUsername(username); + if (!user) return null; + await this.repository.deleteByUsername(username) + return mapToUserDTO(user); + } +} diff --git a/backend/src/util/translationHelper.ts b/backend/src/util/translationHelper.ts index f4443531..eb7141ee 100644 --- a/backend/src/util/translationHelper.ts +++ b/backend/src/util/translationHelper.ts @@ -1,7 +1,7 @@ import fs from 'fs'; import path from 'path'; import yaml from 'js-yaml'; -import {FALLBACK_LANG} from "../../config"; +import {FALLBACK_LANG} from "../config.js"; export function loadTranslations(language: string): T { try { From 6918f45e34eb9cf2884f68ab0f76b736452a6932 Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Sun, 9 Mar 2025 22:30:43 +0100 Subject: [PATCH 033/106] =?UTF-8?q?chore:=20nginx=20configuratie=20toegevo?= =?UTF-8?q?ed=20nginx=20geconfigureerd=20en=20via=20docker=20gekopi=C3=ABe?= =?UTF-8?q?rd,=20SSL=20ingesteld=20en=20volume=20voor=20gemaakt=20in=20doc?= =?UTF-8?q?ker=20compose=20zodat=20de=20certificates=20op=20de=20server=20?= =?UTF-8?q?gevonden=20worden=20door=20docker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 3 +++ frontend.Dockerfile | 2 ++ nginx/nginx.conf | 50 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 nginx/nginx.conf diff --git a/docker-compose.yml b/docker-compose.yml index 673b3d4d..b7c73821 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,8 @@ services: ports: - '443:443' - '80:80' + volumes: + - ssl:/etc/letsencrypt/live/sel2-1.ugent.be/ api: build: dockerfile: ./backend.Dockerfile @@ -46,3 +48,4 @@ volumes: dwengo_postgres_data: dwengo_loki_data: dwengo_grafana_data: + ssl: diff --git a/frontend.Dockerfile b/frontend.Dockerfile index 6491bb5f..b5b765d2 100644 --- a/frontend.Dockerfile +++ b/frontend.Dockerfile @@ -10,6 +10,8 @@ RUN npm run build # production stage FROM nginx:stable AS production-stage +COPY ./nginx/nginx.conf /etc/nginx/ COPY --from=build-stage /app/frontend/dist /usr/share/nginx/html EXPOSE 80 +EXPOSE 443 CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 00000000..81bf2ae5 --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,50 @@ +worker_processes auto; + +events { + worker_connections 1024; +} + +http { + server { + server_name sel2-1.ugent.be; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + } + + location /api/ { + proxy_pass http://127.0.0.1:2002/; + } + + listen 80; + listen 443 default_server ssl; # managed by Certbot + ssl_certificate /etc/letsencrypt/live/sel2-1.ugent.be/fullchain.pem; # managed by Certbot + ssl_certificate_key /etc/letsencrypt/live/sel2-1.ugent.be/privkey.pem; # managed by Certbot + include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot + +} + + server { + listen 2002; + server_name dwengo-api; + + location / { + root /usr/share/api; + } + + } + +# server { +# if ($host = sel2-1.ugent.be) { +# return 301 https://$host$request_uri; +# } # managed by Certbot +# +# +# listen 80; +# server_name sel2-1.ugent.be; +# return 404; # managed by Certbot +# +# } +} \ No newline at end of file From 0c16f127e9a82374889a4c4be5424b5b667489be Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Sun, 9 Mar 2025 22:39:04 +0100 Subject: [PATCH 034/106] chore: docker-compose update context toegevoegd aan frontend en backend services --- docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index b7c73821..31044cc7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,7 @@ services: web: build: + context: . dockerfile: ./frontend.Dockerfile depends_on: - api @@ -11,6 +12,7 @@ services: - ssl:/etc/letsencrypt/live/sel2-1.ugent.be/ api: build: + context: . dockerfile: ./backend.Dockerfile ports: - '2002:2002' From 8020b967032dbfc74b86f4b612ec768b57c39a48 Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Sun, 9 Mar 2025 22:44:32 +0100 Subject: [PATCH 035/106] chore: docker-compose update ssl volume pad aangepast zodat elk nodig bestand gebruikt kan worden --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 31044cc7..acc7beb7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,7 +9,7 @@ services: - '443:443' - '80:80' volumes: - - ssl:/etc/letsencrypt/live/sel2-1.ugent.be/ + - ssl:/etc/letsencrypt/ api: build: context: . From da86ac94eedffaceb4a2f2151b898286a20fc527 Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Sun, 9 Mar 2025 22:51:48 +0100 Subject: [PATCH 036/106] chore: update docker-compose SSL volume aangepast zodat het nu naar de juiste direcotry kijkt en read only is --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index acc7beb7..07b88488 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,7 +9,7 @@ services: - '443:443' - '80:80' volumes: - - ssl:/etc/letsencrypt/ + - /etc/letsencrypt/:/etc/letsencrypt/:ro api: build: context: . From 1b096b411b5c24230b4d64658832c4d283eeb553 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Sun, 9 Mar 2025 23:59:31 +0100 Subject: [PATCH 037/106] fix: integratie user + errors gefixt zodat het runt + format --- backend/src/app.ts | 2 +- backend/src/controllers/assignments.ts | 12 +- backend/src/controllers/classes.ts | 46 ++-- backend/src/controllers/groups.ts | 18 +- backend/src/controllers/learning-objects.ts | 2 +- backend/src/controllers/students.ts | 65 ++++-- backend/src/controllers/teachers.ts | 136 +++++------- backend/src/controllers/themes.ts | 3 +- backend/src/controllers/users.ts | 52 +++-- .../src/data/assignments/group-repository.ts | 21 +- backend/src/data/classes/class-repository.ts | 10 +- .../content/learning-object-repository.ts | 2 +- backend/src/data/dwengo-entity-repository.ts | 6 +- .../src/data/questions/answer-repository.ts | 2 +- .../src/data/questions/question-repository.ts | 16 +- backend/src/data/repositories.ts | 4 +- backend/src/data/users/student-repository.ts | 2 +- backend/src/data/users/teacher-repository.ts | 2 +- .../entities/assignments/assignment.entity.ts | 26 ++- .../src/entities/assignments/group.entity.ts | 19 +- .../entities/assignments/submission.entity.ts | 26 ++- .../classes/class-join-request.entity.ts | 24 +- backend/src/entities/classes/class.entity.ts | 14 +- .../classes/teacher-invitation.entity.ts | 27 ++- .../src/entities/content/attachment.entity.ts | 13 +- .../content/learning-object.entity.ts | 39 +++- .../entities/content/learning-path.entity.ts | 45 +++- .../src/entities/questions/answer.entity.ts | 20 +- .../src/entities/questions/question.entity.ts | 19 +- backend/src/entities/users/student.entity.ts | 14 +- backend/src/entities/users/teacher.entity.ts | 10 +- backend/src/interfaces/assignment.ts | 24 +- backend/src/interfaces/class.ts | 14 +- backend/src/interfaces/group.ts | 22 +- backend/src/interfaces/list.ts | 6 +- backend/src/interfaces/question.ts | 21 +- backend/src/interfaces/student.ts | 2 +- backend/src/interfaces/teacher-invitation.ts | 24 +- backend/src/interfaces/teacher.ts | 36 --- backend/src/interfaces/user.ts | 9 +- backend/src/mikro-orm.config.ts | 48 ++-- backend/src/routes/assignments.ts | 23 +- backend/src/routes/classes.ts | 17 +- backend/src/routes/groups.ts | 15 +- backend/src/routes/questions.ts | 28 +-- backend/src/routes/students.ts | 38 ++-- backend/src/routes/submissions.ts | 15 +- backend/src/routes/teachers.ts | 25 ++- backend/src/services/assignments.ts | 24 +- backend/src/services/class.ts | 73 ++++-- backend/src/services/groups.ts | 31 ++- backend/src/services/students.ts | 26 ++- backend/src/services/teachers.ts | 208 ++++++++---------- backend/src/services/users.ts | 18 +- backend/src/util/translation-helper.ts | 8 +- 55 files changed, 858 insertions(+), 594 deletions(-) delete mode 100644 backend/src/interfaces/teacher.ts diff --git a/backend/src/app.ts b/backend/src/app.ts index 37cfec67..20581e35 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -7,7 +7,7 @@ import learningPathRoutes from './routes/learning-paths.js'; import learningObjectRoutes from './routes/learning-objects.js'; import studentRoutes from './routes/students.js'; -import teacherRoutes from './routes/teachers.js' +import teacherRoutes from './routes/teachers.js'; import groupRoutes from './routes/groups.js'; import submissionRoutes from './routes/submissions.js'; import classRoutes from './routes/classes.js'; diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index ab0f951f..b1be1302 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -1,7 +1,7 @@ -import { Request, Response } from 'express' +import { Request, Response } from 'express'; import { getAllAssignments, getAssignment } from '../services/assignments.js'; -// typescript is annoy with with parameter forwarding from class.ts +// Typescript is annoy with with parameter forwarding from class.ts interface AssignmentParams { classid: string; id: string; @@ -9,7 +9,7 @@ interface AssignmentParams { export async function getAllAssignmentsHandler( req: Request, - res: Response, + res: Response ): Promise { const classid = req.params.classid; const full = req.query.full === 'true'; @@ -23,20 +23,20 @@ export async function getAllAssignmentsHandler( export async function getAssignmentHandler( req: Request, - res: Response, + res: Response ): Promise { const id = +req.params.id; const classid = req.params.classid; if (isNaN(id)) { - res.status(400).json({ error: "Assignment id must be a number" }); + res.status(400).json({ error: 'Assignment id must be a number' }); return; } const assignment = await getAssignment(classid, id); if (!assignment) { - res.status(404).json({ error: "Assignment not found" }); + res.status(404).json({ error: 'Assignment not found' }); return; } diff --git a/backend/src/controllers/classes.ts b/backend/src/controllers/classes.ts index 6fb9d7f4..b8061b5a 100644 --- a/backend/src/controllers/classes.ts +++ b/backend/src/controllers/classes.ts @@ -1,40 +1,44 @@ import { Request, Response } from 'express'; -import { getAllClasses, getClass, getClassStudents, getClassStudentsIds, getClassTeacherInvitations } from '../services/class.js'; -import { ClassDTO } from '../interfaces/classes.js'; +import { + getAllClasses, + getClass, + getClassStudents, + getClassStudentsIds, + getClassTeacherInvitations, +} from '../services/class.js'; export async function getAllClassesHandler( req: Request, - res: Response, + res: Response ): Promise { - const full = req.query.full === "true"; + const full = req.query.full === 'true'; const classes = await getAllClasses(full); res.json({ - classes: classes + classes: classes, }); } export async function getClassHandler( req: Request, - res: Response, + res: Response ): Promise { try { const classId = req.params.id; const cls = await getClass(classId); if (!cls) { - res.status(404).json({ error: "Student not found" }); + res.status(404).json({ error: 'Student not found' }); return; - } else { - cls.endpoints = { - self: `${req.baseUrl}/${req.params.id}`, - invitations: `${req.baseUrl}/${req.params.id}/invitations`, - assignments: `${req.baseUrl}/${req.params.id}/assignments`, - students: `${req.baseUrl}/${req.params.id}/students`, - } - - res.json(cls); } + cls.endpoints = { + self: `${req.baseUrl}/${req.params.id}`, + invitations: `${req.baseUrl}/${req.params.id}/invitations`, + assignments: `${req.baseUrl}/${req.params.id}/assignments`, + students: `${req.baseUrl}/${req.params.id}/students`, + }; + + res.json(cls); } catch (error) { console.error('Error fetching learning objects:', error); res.status(500).json({ error: 'Internal server error' }); @@ -43,12 +47,12 @@ export async function getClassHandler( export async function getClassStudentsHandler( req: Request, - res: Response, + res: Response ): Promise { const classId = req.params.id; - const full = req.query.full === "true"; + const full = req.query.full === 'true'; - let students = full + const students = full ? await getClassStudents(classId) : await getClassStudentsIds(classId); @@ -59,10 +63,10 @@ export async function getClassStudentsHandler( export async function getTeacherInvitationsHandler( req: Request, - res: Response, + res: Response ): Promise { const classId = req.params.id; - const full = req.query.full === "true"; // TODO: not implemented yet + const full = req.query.full === 'true'; // TODO: not implemented yet const invitations = await getClassTeacherInvitations(classId, full); diff --git a/backend/src/controllers/groups.ts b/backend/src/controllers/groups.ts index d301c98d..e484c409 100644 --- a/backend/src/controllers/groups.ts +++ b/backend/src/controllers/groups.ts @@ -1,7 +1,7 @@ import { Request, Response } from 'express'; import { getAllGroups, getGroup } from '../services/groups.js'; -// typescript is annoywith with parameter forwarding from class.ts +// Typescript is annoywith with parameter forwarding from class.ts interface GroupParams { classid: string; assignmentid: string; @@ -10,21 +10,21 @@ interface GroupParams { export async function getGroupHandler( req: Request, - res: Response, + res: Response ): Promise { const classId = req.params.classid; - const full = req.query.full === "true"; + const full = req.query.full === 'true'; const assignmentId = +req.params.assignmentid; if (isNaN(assignmentId)) { - res.status(400).json({ error: "Assignment id must be a number" }); + res.status(400).json({ error: 'Assignment id must be a number' }); return; } - const groupId = +req.params.groupid!; // can't be undefined + const groupId = +req.params.groupid!; // Can't be undefined if (isNaN(groupId)) { - res.status(400).json({ error: "Group id must be a number" }); + res.status(400).json({ error: 'Group id must be a number' }); return; } @@ -35,15 +35,15 @@ export async function getGroupHandler( export async function getAllGroupsHandler( req: Request, - res: Response, + res: Response ): Promise { const classId = req.params.classid; - const full = req.query.full === "true"; + const full = req.query.full === 'true'; const assignmentId = +req.params.assignmentid; if (isNaN(assignmentId)) { - res.status(400).json({ error: "Assignment id must be a number" }); + res.status(400).json({ error: 'Assignment id must be a number' }); return; } diff --git a/backend/src/controllers/learning-objects.ts b/backend/src/controllers/learning-objects.ts index c8a51734..12c0201f 100644 --- a/backend/src/controllers/learning-objects.ts +++ b/backend/src/controllers/learning-objects.ts @@ -5,7 +5,7 @@ import { getLearningObjectsFromPath, } from '../services/learning-objects.js'; import { FALLBACK_LANG } from '../config.js'; -import { FilteredLearningObject } from '../interfaces/learning-path'; +import { FilteredLearningObject } from '../interfaces/learning-path.js'; export async function getAllLearningObjects( req: Request, diff --git a/backend/src/controllers/students.ts b/backend/src/controllers/students.ts index b4fe7b47..2134a9f2 100644 --- a/backend/src/controllers/students.ts +++ b/backend/src/controllers/students.ts @@ -2,43 +2,62 @@ import { Request, Response } from 'express'; import { getStudentClasses, getStudentClassIds, - StudentService + StudentService, } from '../services/students.js'; -import { ClassDTO } from '../interfaces/classes.js'; +import { ClassDTO } from '../interfaces/class.js'; import { getAllAssignments } from '../services/assignments.js'; -import {createUserHandler, deleteUserHandler, getAllUsersHandler, getUserHandler} from "./users.js"; -import { Student } from "../entities/users/student.entity.js"; +import { + createUserHandler, + deleteUserHandler, + getAllUsersHandler, + getUserHandler, +} from './users.js'; +import { Student } from '../entities/users/student.entity.js'; // TODO: accept arguments (full, ...) // TODO: endpoints -export async function getAllStudentsHandler (req: Request, res: Response): Promise { +export async function getAllStudentsHandler( + req: Request, + res: Response +): Promise { await getAllUsersHandler(req, res, new StudentService()); } -export async function getStudentHandler(req: Request, res: Response): Promise { +export async function getStudentHandler( + req: Request, + res: Response +): Promise { await getUserHandler(req, res, new StudentService()); } -export async function createStudentHandler(req: Request, res: Response): Promise { +export async function createStudentHandler( + req: Request, + res: Response +): Promise { await createUserHandler(req, res, new StudentService(), Student); } -export async function deleteStudentHandler(req: Request, res: Response): Promise { +export async function deleteStudentHandler( + req: Request, + res: Response +): Promise { await deleteUserHandler(req, res, new StudentService()); } - -export async function getStudentClassesHandler ( +export async function getStudentClassesHandler( req: Request, - res: Response, + res: Response ): Promise { try { const full = req.query.full === 'true'; const username = req.params.id; let classes: ClassDTO[] | string[]; - if (full) classes = await getStudentClasses(username); - else classes = await getStudentClassIds(username); + if (full) { + classes = await getStudentClasses(username); + } else { + classes = await getStudentClassIds(username); + } res.json({ classes: classes, @@ -47,7 +66,7 @@ export async function getStudentClassesHandler ( 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); @@ -57,22 +76,26 @@ export async function getStudentClassesHandler ( // TODO // Might not be fully correct depending on if -// a class has an assignment, that all students -// have this assignment. +// A class has an assignment, that all students +// Have this assignment. export async function getStudentAssignmentsHandler( req: Request, - res: Response, + res: Response ): Promise { const full = req.query.full === 'true'; const username = req.params.id; const classes = await getStudentClasses(username); - const assignments = (await Promise.all(classes.map(async cls => await getAllAssignments(cls.id, full)))).flat(); + const assignments = ( + await Promise.all( + classes.map(async (cls) => { + return await getAllAssignments(cls.id, full); + }) + ) + ).flat(); res.json({ - assignments: assignments + assignments: assignments, }); } - - diff --git a/backend/src/controllers/teachers.ts b/backend/src/controllers/teachers.ts index d3b6a4db..c083710d 100644 --- a/backend/src/controllers/teachers.ts +++ b/backend/src/controllers/teachers.ts @@ -1,92 +1,53 @@ import { Request, Response } from 'express'; +import { TeacherUserService, TeacherService } from '../services/teachers.js'; +import { ClassDTO } from '../interfaces/class.js'; +import { StudentDTO } from '../interfaces/student.js'; +import { QuestionDTO, QuestionId } from '../interfaces/question.js'; import { - createTeacher, - deleteTeacher, - getTeacherByUsername, - getClassesByTeacher, - getClassIdsByTeacher, - getAllTeachers, - getAllTeachersIds, getStudentsByTeacher, getStudentIdsByTeacher, getQuestionsByTeacher, getQuestionIdsByTeacher -} from '../services/teachers.js'; -import {TeacherDTO} from "../interfaces/teacher"; -import {ClassDTO} from "../interfaces/class"; -import {StudentDTO} from "../interfaces/student"; -import {QuestionDTO, QuestionId} from "../interfaces/question"; + createUserHandler, + deleteUserHandler, + getAllUsersHandler, + getUserHandler, +} from './users.js'; +import { Teacher } from '../entities/users/teacher.entity.js'; -export async function getTeacherHandler(req: Request, res: Response): Promise { - try { - const full = req.query.full === 'true'; - const username = req.query.username as string; +export async function getAllTeachersHandler( + req: Request, + res: Response +): Promise { + await getAllUsersHandler(req, res, new TeacherUserService()); +} - if (username){ - const teacher = await getTeacherByUsername(username); - if (!teacher){ - res.status(404).json({ error: `Teacher with username '${username}' not found.` }); - return; - } - res.json(teacher); - return; - } - - let teachers: TeacherDTO[] | string[]; - - if (full) teachers = await getAllTeachers(); - else teachers = await getAllTeachersIds(); - - res.json(teachers); - } catch (error) { - console.error("❌ Error fetching teachers:", error); - res.status(500).json({ error: "Internal server error" }); - } +export async function getTeacherHandler( + req: Request, + res: Response +): Promise { + await getUserHandler(req, res, new TeacherUserService()); } export async function createTeacherHandler( req: Request, res: Response ): Promise { - try { - const teacherData = req.body as TeacherDTO; - - if (!teacherData.username || !teacherData.firstName || !teacherData.lastName) { - res.status(400).json({ error: 'Missing required fields: username, firstName, lastName' }); - return; - } - - const newTeacher = await createTeacher(teacherData); - res.status(201).json(newTeacher); - } catch (error) { - console.error('Error creating teacher:', error); - res.status(500).json({ error: 'Internal server error' }); - } + await createUserHandler( + req, + res, + new TeacherUserService(), + Teacher + ); } export async function deleteTeacherHandler( req: Request, res: Response ): Promise { - try { - const username = req.params.username as string; - - if (!username) { - res.status(400).json({ error: 'Missing required field: username' }); - return; - } - - const deletedTeacher = await deleteTeacher(username); - - if (!deletedTeacher) { - res.status(400).json({ error: `Did not find teacher with username ${username}` }); - return; - } - - res.status(201).json(deletedTeacher); - } catch (error) { - console.error('Error deleting teacher:', error); - res.status(500).json({ error: 'Internal server error' }); - } + await deleteUserHandler(req, res, new TeacherUserService()); } -export async function getTeacherClassHandler(req: Request, res: Response): Promise { +export async function getTeacherClassHandler( + req: Request, + res: Response +): Promise { try { const username = req.params.username as string; const full = req.query.full === 'true'; @@ -96,10 +57,11 @@ export async function getTeacherClassHandler(req: Request, res: Response): Promi return; } - let classes: ClassDTO[] | string[]; + const teacherService = new TeacherService(); - if (full) classes = await getClassesByTeacher(username); - else classes = await getClassIdsByTeacher(username); + const classes: ClassDTO[] | string[] = full + ? await teacherService.getClassesByTeacher(username) + : await teacherService.getClassIdsByTeacher(username); res.status(201).json(classes); } catch (error) { @@ -108,7 +70,10 @@ export async function getTeacherClassHandler(req: Request, res: Response): Promi } } -export async function getTeacherStudentHandler(req: Request, res: Response): Promise { +export async function getTeacherStudentHandler( + req: Request, + res: Response +): Promise { try { const username = req.params.username as string; const full = req.query.full === 'true'; @@ -118,10 +83,11 @@ export async function getTeacherStudentHandler(req: Request, res: Response): Pro return; } - let students: StudentDTO[] | string[]; + const teacherService = new TeacherService(); - if (full) students = await getStudentsByTeacher(username); - else students = await getStudentIdsByTeacher(username); + const students: StudentDTO[] | string[] = full + ? await teacherService.getStudentsByTeacher(username) + : await teacherService.getStudentIdsByTeacher(username); res.status(201).json(students); } catch (error) { @@ -130,7 +96,10 @@ export async function getTeacherStudentHandler(req: Request, res: Response): Pro } } -export async function getTeacherQuestionHandler(req: Request, res: Response): Promise { +export async function getTeacherQuestionHandler( + req: Request, + res: Response +): Promise { try { const username = req.params.username as string; const full = req.query.full === 'true'; @@ -140,10 +109,11 @@ export async function getTeacherQuestionHandler(req: Request, res: Response): Pr return; } - let questions: QuestionDTO[] | QuestionId[]; + const teacherService = new TeacherService(); - if (full) questions = await getQuestionsByTeacher(username); - else questions = await getQuestionIdsByTeacher(username); + const questions: QuestionDTO[] | QuestionId[] = full + ? await teacherService.getQuestionsByTeacher(username) + : await teacherService.getQuestionIdsByTeacher(username); res.status(201).json(questions); } catch (error) { @@ -151,5 +121,3 @@ export async function getTeacherQuestionHandler(req: Request, res: Response): Pr res.status(500).json({ error: 'Internal server error' }); } } - - diff --git a/backend/src/controllers/themes.ts b/backend/src/controllers/themes.ts index 208b27d1..30cb2b13 100644 --- a/backend/src/controllers/themes.ts +++ b/backend/src/controllers/themes.ts @@ -1,7 +1,6 @@ import { Request, Response } from 'express'; import { themes } from '../data/themes.js'; -import { loadTranslations } from "../util/translation-helper.js"; -import { FALLBACK_LANG } from '../config.js'; +import { loadTranslations } from '../util/translation-helper.js'; interface Translations { curricula_page: { diff --git a/backend/src/controllers/users.ts b/backend/src/controllers/users.ts index 4b29617e..8b256a0e 100644 --- a/backend/src/controllers/users.ts +++ b/backend/src/controllers/users.ts @@ -1,30 +1,29 @@ 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 { UserService } from '../services/users.js'; +import { UserDTO } from '../interfaces/user.js'; +import { User } from '../entities/users/user.entity.js'; export async function getAllUsersHandler( req: Request, res: Response, service: UserService -): Promise { +): Promise { try { const full = req.query.full === 'true'; - let users: UserDTO[] | string[] = full + const users: UserDTO[] | string[] = full ? await service.getAllUsers() : await service.getAllUserIds(); - if (!users){ + 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" }); + console.error('❌ Error fetching users:', error); + res.status(500).json({ error: 'Internal server error' }); } } @@ -32,7 +31,7 @@ export async function getUserHandler( req: Request, res: Response, service: UserService -): Promise { +): Promise { try { const username = req.params.username as string; @@ -43,16 +42,17 @@ export async function getUserHandler( const user = await service.getUserByUsername(username); - if (!user){ - res.status(404).json({ error: `User with username '${username}' not found.` }); + 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" }); + console.error('❌ Error fetching users:', error); + res.status(500).json({ error: 'Internal server error' }); } } @@ -63,23 +63,25 @@ export async function createUserHandler( UserClass: new () => T ) { try { - console.log("req", req) + 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" }); + 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" }); + console.error('❌ Error creating user:', error); + res.status(500).json({ error: 'Internal server error' }); } } -export async function deleteUserHandler ( +export async function deleteUserHandler( req: Request, res: Response, service: UserService @@ -88,19 +90,21 @@ export async function deleteUserHandler ( const username = req.params.username; if (!username) { - res.status(400).json({ error: "Missing required field: 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.` }); + 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" }); + console.error('❌ Error deleting user:', error); + res.status(500).json({ error: 'Internal server error' }); } } diff --git a/backend/src/data/assignments/group-repository.ts b/backend/src/data/assignments/group-repository.ts index c8770eb5..e9b668b9 100644 --- a/backend/src/data/assignments/group-repository.ts +++ b/backend/src/data/assignments/group-repository.ts @@ -8,21 +8,20 @@ export class GroupRepository extends DwengoEntityRepository { groupNumber: number ): Promise { return this.findOne( - { - assignment: assignment, - groupNumber: groupNumber, - }, - { populate: ["members"] }, - ); + { + assignment: assignment, + groupNumber: groupNumber, + }, + { populate: ['members'] } + ); } public findAllGroupsForAssignment( assignment: Assignment ): Promise { - return this.findAll({ - where: { assignment: assignment }, - populate: ["members"] - }, - ); + return this.findAll({ + where: { assignment: assignment }, + populate: ['members'], + }); } public deleteByAssignmentAndGroupNumber( assignment: Assignment, diff --git a/backend/src/data/classes/class-repository.ts b/backend/src/data/classes/class-repository.ts index 9fd50d75..4f2f5b0e 100644 --- a/backend/src/data/classes/class-repository.ts +++ b/backend/src/data/classes/class-repository.ts @@ -1,13 +1,13 @@ import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; import { Class } from '../../entities/classes/class.entity.js'; import { Student } from '../../entities/users/student.entity.js'; -import {Teacher} from "../../entities/users/teacher.entity"; +import { Teacher } from '../../entities/users/teacher.entity'; export class ClassRepository extends DwengoEntityRepository { public findById(id: string): Promise { return this.findOne( { classId: id }, - { populate: ["students", "teachers"] }, + { populate: ['students', 'teachers'] } ); } public deleteById(id: string): Promise { @@ -16,14 +16,14 @@ export class ClassRepository extends DwengoEntityRepository { public findByStudent(student: Student): Promise { return this.find( { students: student }, - { populate: ["students", "teachers"] } // voegt student en teacher objecten toe - ) + { populate: ['students', 'teachers'] } // Voegt student en teacher objecten toe + ); } public findByTeacher(teacher: Teacher): Promise { return this.find( { teachers: teacher }, - { populate: ["students", "teachers"] } + { populate: ['students', 'teachers'] } ); } } diff --git a/backend/src/data/content/learning-object-repository.ts b/backend/src/data/content/learning-object-repository.ts index d8b07943..4de31076 100644 --- a/backend/src/data/content/learning-object-repository.ts +++ b/backend/src/data/content/learning-object-repository.ts @@ -1,7 +1,7 @@ import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; import { LearningObject } from '../../entities/content/learning-object.entity.js'; import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js'; -import {Teacher} from "../../entities/users/teacher.entity"; +import { Teacher } from '../../entities/users/teacher.entity'; export class LearningObjectRepository extends DwengoEntityRepository { public findByIdentifier( diff --git a/backend/src/data/dwengo-entity-repository.ts b/backend/src/data/dwengo-entity-repository.ts index 368f3a2c..e29d9ede 100644 --- a/backend/src/data/dwengo-entity-repository.ts +++ b/backend/src/data/dwengo-entity-repository.ts @@ -4,13 +4,13 @@ export abstract class DwengoEntityRepository< T extends object, > extends EntityRepository { public async save(entity: T) { - let em = this.getEntityManager(); + const em = this.getEntityManager(); em.persist(entity); await em.flush(); } public async deleteWhere(query: FilterQuery) { - let toDelete = await this.findOne(query); - let em = this.getEntityManager(); + const toDelete = await this.findOne(query); + const em = this.getEntityManager(); if (toDelete) { em.remove(toDelete); await em.flush(); diff --git a/backend/src/data/questions/answer-repository.ts b/backend/src/data/questions/answer-repository.ts index 6a2629f4..6c45211c 100644 --- a/backend/src/data/questions/answer-repository.ts +++ b/backend/src/data/questions/answer-repository.ts @@ -9,7 +9,7 @@ export class AnswerRepository extends DwengoEntityRepository { author: Teacher; content: string; }): Promise { - let answerEntity = new Answer(); + const answerEntity = new Answer(); answerEntity.toQuestion = answer.toQuestion; answerEntity.author = answer.author; answerEntity.content = answer.content; diff --git a/backend/src/data/questions/question-repository.ts b/backend/src/data/questions/question-repository.ts index 05430ddd..7dee3b96 100644 --- a/backend/src/data/questions/question-repository.ts +++ b/backend/src/data/questions/question-repository.ts @@ -2,7 +2,7 @@ import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; import { Question } from '../../entities/questions/question.entity.js'; import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js'; import { Student } from '../../entities/users/student.entity.js'; -import {LearningObject} from "../../entities/content/learning-object.entity"; +import { LearningObject } from '../../entities/content/learning-object.entity'; export class QuestionRepository extends DwengoEntityRepository { public createQuestion(question: { @@ -10,7 +10,7 @@ export class QuestionRepository extends DwengoEntityRepository { author: Student; content: string; }): Promise { - let questionEntity = new Question(); + const questionEntity = new Question(); questionEntity.learningObjectHruid = question.loId.hruid; questionEntity.learningObjectLanguage = question.loId.language; questionEntity.learningObjectVersion = question.loId.version; @@ -44,12 +44,16 @@ export class QuestionRepository extends DwengoEntityRepository { }); } - public async findAllByLearningObjects(learningObjects: LearningObject[]): Promise { - const objectIdentifiers = learningObjects.map(lo => ({ + public async findAllByLearningObjects( + learningObjects: LearningObject[] + ): Promise { + const objectIdentifiers = learningObjects.map((lo) => { + return { learningObjectHruid: lo.hruid, learningObjectLanguage: lo.language, - learningObjectVersion: lo.version - })); + learningObjectVersion: lo.version, + }; + }); return this.findAll({ where: { $or: objectIdentifiers }, diff --git a/backend/src/data/repositories.ts b/backend/src/data/repositories.ts index e65bb882..74e9a07e 100644 --- a/backend/src/data/repositories.ts +++ b/backend/src/data/repositories.ts @@ -59,7 +59,9 @@ function repositoryGetter>( } /* Users */ -export const getUserRepository = repositoryGetter>(User); +export const getUserRepository = repositoryGetter>( + User +); export const getStudentRepository = repositoryGetter< Student, StudentRepository diff --git a/backend/src/data/users/student-repository.ts b/backend/src/data/users/student-repository.ts index c553aab8..6835e430 100644 --- a/backend/src/data/users/student-repository.ts +++ b/backend/src/data/users/student-repository.ts @@ -1,4 +1,4 @@ import { Student } from '../../entities/users/student.entity.js'; -import {UserRepository} from "./user-repository.js"; +import { UserRepository } from './user-repository.js'; export class StudentRepository extends UserRepository {} diff --git a/backend/src/data/users/teacher-repository.ts b/backend/src/data/users/teacher-repository.ts index 9940b4bd..4c4f7687 100644 --- a/backend/src/data/users/teacher-repository.ts +++ b/backend/src/data/users/teacher-repository.ts @@ -1,4 +1,4 @@ import { Teacher } from '../../entities/users/teacher.entity.js'; -import {UserRepository} from "./user-repository.js"; +import { UserRepository } from './user-repository.js'; export class TeacherRepository extends UserRepository {} diff --git a/backend/src/entities/assignments/assignment.entity.ts b/backend/src/entities/assignments/assignment.entity.ts index 785a8b9c..f6e3c3eb 100644 --- a/backend/src/entities/assignments/assignment.entity.ts +++ b/backend/src/entities/assignments/assignment.entity.ts @@ -11,9 +11,18 @@ import { Group } from './group.entity.js'; import { Language } from '../content/language.js'; import { AssignmentRepository } from '../../data/assignments/assignment-repository.js'; -@Entity({ repository: () => AssignmentRepository }) +@Entity({ + repository: () => { + return AssignmentRepository; + }, +}) export class Assignment { - @ManyToOne({ entity: () => Class, primary: true }) + @ManyToOne({ + entity: () => { + return Class; + }, + primary: true, + }) within!: Class; @PrimaryKey({ type: 'number' }) @@ -28,9 +37,18 @@ export class Assignment { @Property({ type: 'string' }) learningPathHruid!: string; - @Enum({ items: () => Language }) + @Enum({ + items: () => { + return Language; + }, + }) learningPathLanguage!: Language; - @OneToMany({ entity: () => Group, mappedBy: 'assignment' }) + @OneToMany({ + entity: () => { + return Group; + }, + mappedBy: 'assignment', + }) groups!: Group[]; } diff --git a/backend/src/entities/assignments/group.entity.ts b/backend/src/entities/assignments/group.entity.ts index cb4cd6e6..9c2ed2cf 100644 --- a/backend/src/entities/assignments/group.entity.ts +++ b/backend/src/entities/assignments/group.entity.ts @@ -3,14 +3,27 @@ import { Assignment } from './assignment.entity.js'; import { Student } from '../users/student.entity.js'; import { GroupRepository } from '../../data/assignments/group-repository.js'; -@Entity({ repository: () => GroupRepository }) +@Entity({ + repository: () => { + return GroupRepository; + }, +}) export class Group { - @ManyToOne({ entity: () => Assignment, primary: true }) + @ManyToOne({ + entity: () => { + return Assignment; + }, + primary: true, + }) assignment!: Assignment; @PrimaryKey({ type: 'integer' }) groupNumber!: number; - @ManyToMany({ entity: () => Student }) + @ManyToMany({ + entity: () => { + return Student; + }, + }) members!: Student[]; } diff --git a/backend/src/entities/assignments/submission.entity.ts b/backend/src/entities/assignments/submission.entity.ts index 26269020..2836c8dc 100644 --- a/backend/src/entities/assignments/submission.entity.ts +++ b/backend/src/entities/assignments/submission.entity.ts @@ -4,12 +4,21 @@ import { Entity, Enum, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'; import { Language } from '../content/language.js'; import { SubmissionRepository } from '../../data/assignments/submission-repository.js'; -@Entity({ repository: () => SubmissionRepository }) +@Entity({ + repository: () => { + return SubmissionRepository; + }, +}) export class Submission { @PrimaryKey({ type: 'string' }) learningObjectHruid!: string; - @Enum({ items: () => Language, primary: true }) + @Enum({ + items: () => { + return Language; + }, + primary: true, + }) learningObjectLanguage!: Language; @PrimaryKey({ type: 'string' }) @@ -18,13 +27,22 @@ export class Submission { @PrimaryKey({ type: 'integer' }) submissionNumber!: number; - @ManyToOne({ entity: () => Student }) + @ManyToOne({ + entity: () => { + return Student; + }, + }) submitter!: Student; @Property({ type: 'datetime' }) submissionTime!: Date; - @ManyToOne({ entity: () => Group, nullable: true }) + @ManyToOne({ + entity: () => { + return Group; + }, + nullable: true, + }) onBehalfOf?: Group; @Property({ type: 'json' }) diff --git a/backend/src/entities/classes/class-join-request.entity.ts b/backend/src/entities/classes/class-join-request.entity.ts index 0636e044..6055454b 100644 --- a/backend/src/entities/classes/class-join-request.entity.ts +++ b/backend/src/entities/classes/class-join-request.entity.ts @@ -3,15 +3,31 @@ import { Student } from '../users/student.entity.js'; import { Class } from './class.entity.js'; import { ClassJoinRequestRepository } from '../../data/classes/class-join-request-repository.js'; -@Entity({ repository: () => ClassJoinRequestRepository }) +@Entity({ + repository: () => { + return ClassJoinRequestRepository; + }, +}) export class ClassJoinRequest { - @ManyToOne({ entity: () => Student, primary: true }) + @ManyToOne({ + entity: () => { + return Student; + }, + primary: true, + }) requester!: Student; - @ManyToOne({ entity: () => Class, primary: true }) + @ManyToOne({ + entity: () => { + return Class; + }, + primary: true, + }) class!: Class; - @Enum(() => ClassJoinRequestStatus) + @Enum(() => { + return ClassJoinRequestStatus; + }) status!: ClassJoinRequestStatus; } diff --git a/backend/src/entities/classes/class.entity.ts b/backend/src/entities/classes/class.entity.ts index 06d31479..b40b5baf 100644 --- a/backend/src/entities/classes/class.entity.ts +++ b/backend/src/entities/classes/class.entity.ts @@ -10,7 +10,11 @@ import { Teacher } from '../users/teacher.entity.js'; import { Student } from '../users/student.entity.js'; import { ClassRepository } from '../../data/classes/class-repository.js'; -@Entity({ repository: () => ClassRepository }) +@Entity({ + repository: () => { + return ClassRepository; + }, +}) export class Class { @PrimaryKey() classId = v4(); @@ -18,9 +22,13 @@ export class Class { @Property({ type: 'string' }) displayName!: string; - @ManyToMany(() => Teacher) + @ManyToMany(() => { + return Teacher; + }) teachers!: Collection; - @ManyToMany(() => Student) + @ManyToMany(() => { + return Student; + }) students!: Collection; } diff --git a/backend/src/entities/classes/teacher-invitation.entity.ts b/backend/src/entities/classes/teacher-invitation.entity.ts index 42700d3a..877a183b 100644 --- a/backend/src/entities/classes/teacher-invitation.entity.ts +++ b/backend/src/entities/classes/teacher-invitation.entity.ts @@ -6,14 +6,33 @@ import { TeacherInvitationRepository } from '../../data/classes/teacher-invitati /** * Invitation of a teacher into a class (in order to teach it). */ -@Entity({ repository: () => TeacherInvitationRepository }) +@Entity({ + repository: () => { + return TeacherInvitationRepository; + }, +}) export class TeacherInvitation { - @ManyToOne({ entity: () => Teacher, primary: true }) + @ManyToOne({ + entity: () => { + return Teacher; + }, + primary: true, + }) sender!: Teacher; - @ManyToOne({ entity: () => Teacher, primary: true }) + @ManyToOne({ + entity: () => { + return Teacher; + }, + primary: true, + }) receiver!: Teacher; - @ManyToOne({ entity: () => Class, primary: true }) + @ManyToOne({ + entity: () => { + return Class; + }, + primary: true, + }) class!: Class; } diff --git a/backend/src/entities/content/attachment.entity.ts b/backend/src/entities/content/attachment.entity.ts index 7f14d178..8225ebf2 100644 --- a/backend/src/entities/content/attachment.entity.ts +++ b/backend/src/entities/content/attachment.entity.ts @@ -2,9 +2,18 @@ import { Entity, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'; import { LearningObject } from './learning-object.entity.js'; import { AttachmentRepository } from '../../data/content/attachment-repository.js'; -@Entity({ repository: () => AttachmentRepository }) +@Entity({ + repository: () => { + return AttachmentRepository; + }, +}) export class Attachment { - @ManyToOne({ entity: () => LearningObject, primary: true }) + @ManyToOne({ + entity: () => { + return LearningObject; + }, + primary: true, + }) learningObject!: LearningObject; @PrimaryKey({ type: 'integer' }) diff --git a/backend/src/entities/content/learning-object.entity.ts b/backend/src/entities/content/learning-object.entity.ts index 78d4aa00..888de5e3 100644 --- a/backend/src/entities/content/learning-object.entity.ts +++ b/backend/src/entities/content/learning-object.entity.ts @@ -22,18 +22,31 @@ export class ReturnValue { callbackSchema!: string; } -@Entity({ repository: () => LearningObjectRepository }) +@Entity({ + repository: () => { + return LearningObjectRepository; + }, +}) export class LearningObject { @PrimaryKey({ type: 'string' }) hruid!: string; - @Enum({ items: () => Language, primary: true }) + @Enum({ + items: () => { + return Language; + }, + primary: true, + }) language!: Language; @PrimaryKey({ type: 'string' }) version: string = '1'; - @ManyToMany({ entity: () => Teacher }) + @ManyToMany({ + entity: () => { + return Teacher; + }, + }) admins!: Teacher[]; @Property({ type: 'string' }) @@ -57,7 +70,12 @@ export class LearningObject { @Property({ type: 'array' }) skosConcepts!: string[]; - @Embedded({ entity: () => EducationalGoal, array: true }) + @Embedded({ + entity: () => { + return EducationalGoal; + }, + array: true, + }) educationalGoals: EducationalGoal[] = []; @Property({ type: 'string' }) @@ -72,7 +90,11 @@ export class LearningObject { @Property({ type: 'integer' }) estimatedTime!: number; - @Embedded({ entity: () => ReturnValue }) + @Embedded({ + entity: () => { + return ReturnValue; + }, + }) returnValue!: ReturnValue; @Property({ type: 'bool' }) @@ -81,7 +103,12 @@ export class LearningObject { @Property({ type: 'string', nullable: true }) contentLocation?: string; - @OneToMany({ entity: () => Attachment, mappedBy: 'learningObject' }) + @OneToMany({ + entity: () => { + return Attachment; + }, + mappedBy: 'learningObject', + }) attachments: Attachment[] = []; @Property({ type: 'blob' }) diff --git a/backend/src/entities/content/learning-path.entity.ts b/backend/src/entities/content/learning-path.entity.ts index de62aa15..34026461 100644 --- a/backend/src/entities/content/learning-path.entity.ts +++ b/backend/src/entities/content/learning-path.entity.ts @@ -12,15 +12,28 @@ import { Language } from './language.js'; import { Teacher } from '../users/teacher.entity.js'; import { LearningPathRepository } from '../../data/content/learning-path-repository.js'; -@Entity({ repository: () => LearningPathRepository }) +@Entity({ + repository: () => { + return LearningPathRepository; + }, +}) export class LearningPath { @PrimaryKey({ type: 'string' }) hruid!: string; - @Enum({ items: () => Language, primary: true }) + @Enum({ + items: () => { + return Language; + }, + primary: true, + }) language!: Language; - @ManyToMany({ entity: () => Teacher }) + @ManyToMany({ + entity: () => { + return Teacher; + }, + }) admins!: Teacher[]; @Property({ type: 'string' }) @@ -32,7 +45,12 @@ export class LearningPath { @Property({ type: 'blob' }) image!: string; - @Embedded({ entity: () => LearningPathNode, array: true }) + @Embedded({ + entity: () => { + return LearningPathNode; + }, + array: true, + }) nodes: LearningPathNode[] = []; } @@ -41,7 +59,11 @@ export class LearningPathNode { @Property({ type: 'string' }) learningObjectHruid!: string; - @Enum({ items: () => Language }) + @Enum({ + items: () => { + return Language; + }, + }) language!: Language; @Property({ type: 'string' }) @@ -53,7 +75,12 @@ export class LearningPathNode { @Property({ type: 'bool' }) startNode!: boolean; - @Embedded({ entity: () => LearningPathTransition, array: true }) + @Embedded({ + entity: () => { + return LearningPathTransition; + }, + array: true, + }) transitions!: LearningPathTransition[]; } @@ -62,6 +89,10 @@ export class LearningPathTransition { @Property({ type: 'string' }) condition!: string; - @OneToOne({ entity: () => LearningPathNode }) + @OneToOne({ + entity: () => { + return LearningPathNode; + }, + }) next!: LearningPathNode; } diff --git a/backend/src/entities/questions/answer.entity.ts b/backend/src/entities/questions/answer.entity.ts index 7fb64575..b73c7014 100644 --- a/backend/src/entities/questions/answer.entity.ts +++ b/backend/src/entities/questions/answer.entity.ts @@ -3,12 +3,26 @@ import { Question } from './question.entity.js'; import { Teacher } from '../users/teacher.entity.js'; import { AnswerRepository } from '../../data/questions/answer-repository.js'; -@Entity({ repository: () => AnswerRepository }) +@Entity({ + repository: () => { + return AnswerRepository; + }, +}) export class Answer { - @ManyToOne({ entity: () => Teacher, primary: true }) + @ManyToOne({ + entity: () => { + return Teacher; + }, + primary: true, + }) author!: Teacher; - @ManyToOne({ entity: () => Question, primary: true }) + @ManyToOne({ + entity: () => { + return Question; + }, + primary: true, + }) toQuestion!: Question; @PrimaryKey({ type: 'integer' }) diff --git a/backend/src/entities/questions/question.entity.ts b/backend/src/entities/questions/question.entity.ts index 67efdfe7..689b6ca1 100644 --- a/backend/src/entities/questions/question.entity.ts +++ b/backend/src/entities/questions/question.entity.ts @@ -3,12 +3,21 @@ import { Language } from '../content/language.js'; import { Student } from '../users/student.entity.js'; import { QuestionRepository } from '../../data/questions/question-repository.js'; -@Entity({ repository: () => QuestionRepository }) +@Entity({ + repository: () => { + return QuestionRepository; + }, +}) export class Question { @PrimaryKey({ type: 'string' }) learningObjectHruid!: string; - @Enum({ items: () => Language, primary: true }) + @Enum({ + items: () => { + return Language; + }, + primary: true, + }) learningObjectLanguage!: Language; @PrimaryKey({ type: 'string' }) @@ -17,7 +26,11 @@ export class Question { @PrimaryKey({ type: 'integer' }) sequenceNumber!: number; - @ManyToOne({ entity: () => Student }) + @ManyToOne({ + entity: () => { + return Student; + }, + }) author!: Student; @Property({ type: 'datetime' }) diff --git a/backend/src/entities/users/student.entity.ts b/backend/src/entities/users/student.entity.ts index 401b1ed2..7ad8c9b3 100644 --- a/backend/src/entities/users/student.entity.ts +++ b/backend/src/entities/users/student.entity.ts @@ -4,11 +4,19 @@ import { Class } from '../classes/class.entity.js'; import { Group } from '../assignments/group.entity.js'; import { StudentRepository } from '../../data/users/student-repository.js'; -@Entity({ repository: () => StudentRepository }) +@Entity({ + repository: () => { + return StudentRepository; + }, +}) export class Student extends User { - @ManyToMany(() => Class) + @ManyToMany(() => { + return Class; + }) classes!: Collection; - @ManyToMany(() => Group) + @ManyToMany(() => { + return Group; + }) groups!: Collection; } diff --git a/backend/src/entities/users/teacher.entity.ts b/backend/src/entities/users/teacher.entity.ts index d53ca603..52cc9f7b 100644 --- a/backend/src/entities/users/teacher.entity.ts +++ b/backend/src/entities/users/teacher.entity.ts @@ -3,8 +3,14 @@ import { User } from './user.entity.js'; import { Class } from '../classes/class.entity.js'; import { TeacherRepository } from '../../data/users/teacher-repository.js'; -@Entity({ repository: () => TeacherRepository }) +@Entity({ + repository: () => { + return TeacherRepository; + }, +}) export class Teacher extends User { - @ManyToMany(() => Class) + @ManyToMany(() => { + return Class; + }) classes!: Collection; } diff --git a/backend/src/interfaces/assignment.ts b/backend/src/interfaces/assignment.ts index 146a0517..ad6d8330 100644 --- a/backend/src/interfaces/assignment.ts +++ b/backend/src/interfaces/assignment.ts @@ -1,14 +1,14 @@ -import { Assignment } from "../entities/assignments/assignment.entity.js"; -import { GroupDTO, mapToGroupDTO } from "./groups.js"; +import { Assignment } from '../entities/assignments/assignment.entity.js'; +import { GroupDTO, mapToGroupDTO } from './group.js'; export interface AssignmentDTO { - id: number, - class: string, // id of class 'within' - title: string, - description: string, - learningPath: string, - language: string, - groups?: GroupDTO[] | string[], // TODO + id: number; + class: string; // Id of class 'within' + title: string; + description: string; + learningPath: string; + language: string; + groups?: GroupDTO[] | string[]; // TODO } export function mapToAssignmentDTOId(assignment: Assignment): AssignmentDTO { @@ -19,8 +19,8 @@ export function mapToAssignmentDTOId(assignment: Assignment): AssignmentDTO { description: assignment.description, learningPath: assignment.learningPathHruid, language: assignment.learningPathLanguage, - // groups: assignment.groups.map(group => group.groupNumber), - } + // Groups: assignment.groups.map(group => group.groupNumber), + }; } export function mapToAssignmentDTO(assignment: Assignment): AssignmentDTO { @@ -31,6 +31,6 @@ export function mapToAssignmentDTO(assignment: Assignment): AssignmentDTO { description: assignment.description, learningPath: assignment.learningPathHruid, language: assignment.learningPathLanguage, - // groups: assignment.groups.map(mapToGroupDTO), + // Groups: assignment.groups.map(mapToGroupDTO), }; } diff --git a/backend/src/interfaces/class.ts b/backend/src/interfaces/class.ts index 0274d90c..c18ee842 100644 --- a/backend/src/interfaces/class.ts +++ b/backend/src/interfaces/class.ts @@ -1,4 +1,4 @@ -import { Class } from "../entities/classes/class.entity.js"; +import { Class } from '../entities/classes/class.entity.js'; export interface ClassDTO { id: string; @@ -18,8 +18,12 @@ export function mapToClassDTO(cls: Class): ClassDTO { return { id: cls.classId, displayName: cls.displayName, - teachers: cls.teachers.map(teacher => teacher.username), - students: cls.students.map(student => student.username), + teachers: cls.teachers.map((teacher) => { + return teacher.username; + }), + students: cls.students.map((student) => { + return student.username; + }), joinRequests: [], // TODO - } -}; + }; +} diff --git a/backend/src/interfaces/group.ts b/backend/src/interfaces/group.ts index c4c95baf..9c4cecdc 100644 --- a/backend/src/interfaces/group.ts +++ b/backend/src/interfaces/group.ts @@ -1,25 +1,27 @@ -import { Group } from "../entities/assignments/group.entity.js"; -import { AssignmentDTO, mapToAssignmentDTO } from "./assignments.js"; -import { mapToStudentDTO, StudentDTO } from "./students.js"; +import { Group } from '../entities/assignments/group.entity.js'; +import { AssignmentDTO, mapToAssignmentDTO } from './assignment.js'; +import { mapToStudentDTO, StudentDTO } from './student.js'; export interface GroupDTO { - assignment: number | AssignmentDTO, - groupNumber: number, - members: string[] | StudentDTO[], -}; + assignment: number | AssignmentDTO; + groupNumber: number; + members: string[] | StudentDTO[]; +} export function mapToGroupDTO(group: Group): GroupDTO { return { assignment: mapToAssignmentDTO(group.assignment), // ERROR: , group.assignment.within), groupNumber: group.groupNumber, members: group.members.map(mapToStudentDTO), - } + }; } export function mapToGroupDTOId(group: Group): GroupDTO { return { assignment: group.assignment.id, groupNumber: group.groupNumber, - members: group.members.map(member => member.username), - } + members: group.members.map((member) => { + return member.username; + }), + }; } diff --git a/backend/src/interfaces/list.ts b/backend/src/interfaces/list.ts index 0037403d..6892fb9d 100644 --- a/backend/src/interfaces/list.ts +++ b/backend/src/interfaces/list.ts @@ -1,5 +1,5 @@ // TODO: implement something like this but with named endpoints export interface List { - items: T[], - endpoints?: string[], -}; \ No newline at end of file + items: T[]; + endpoints?: string[]; +} diff --git a/backend/src/interfaces/question.ts b/backend/src/interfaces/question.ts index 1a8c914a..90de4999 100644 --- a/backend/src/interfaces/question.ts +++ b/backend/src/interfaces/question.ts @@ -1,6 +1,4 @@ -import {Question} from "../entities/questions/question.entity"; -import {Enum, PrimaryKey} from "@mikro-orm/core"; -import {Language} from "../entities/content/language"; +import { Question } from '../entities/questions/question.entity.js'; export interface QuestionDTO { learningObjectHruid: string; @@ -34,8 +32,17 @@ export function mapToQuestionDTO(question: Question): QuestionDTO { } export interface QuestionId { - learningObjectHruid: string, - learningObjectLanguage: Language, - learningObjectVersion: string, - sequenceNumber: number + learningObjectHruid: string; + learningObjectLanguage: string; + learningObjectVersion: string; + sequenceNumber: number; +} + +export function mapToQuestionId(question: QuestionDTO): QuestionId { + return { + learningObjectHruid: question.learningObjectHruid, + learningObjectLanguage: question.learningObjectLanguage, + learningObjectVersion: question.learningObjectVersion, + sequenceNumber: question.sequenceNumber, + }; } diff --git a/backend/src/interfaces/student.ts b/backend/src/interfaces/student.ts index c18461e8..529c6ead 100644 --- a/backend/src/interfaces/student.ts +++ b/backend/src/interfaces/student.ts @@ -1,4 +1,4 @@ -import { Student } from "../entities/users/student.entity.js"; +import { Student } from '../entities/users/student.entity.js'; export interface StudentDTO { id: string; diff --git a/backend/src/interfaces/teacher-invitation.ts b/backend/src/interfaces/teacher-invitation.ts index 489e5565..badbbbd7 100644 --- a/backend/src/interfaces/teacher-invitation.ts +++ b/backend/src/interfaces/teacher-invitation.ts @@ -1,22 +1,26 @@ -import { TeacherInvitation } from "../entities/classes/teacher-invitation.entity.js"; -import { ClassDTO, mapToClassDTO } from "./classes.js"; -import { mapToTeacherDTO, TeacherDTO } from "./teacher.js"; +import { TeacherInvitation } from '../entities/classes/teacher-invitation.entity.js'; +import { ClassDTO, mapToClassDTO } from './class.js'; +import { mapToUserDTO, UserDTO } from './user.js'; export interface TeacherInvitationDTO { - sender: string | TeacherDTO, - receiver: string | TeacherDTO, - class: string | ClassDTO, + sender: string | UserDTO; + receiver: string | UserDTO; + class: string | ClassDTO; } -export function mapToTeacherInvitationDTO(invitation: TeacherInvitation): TeacherInvitationDTO { +export function mapToTeacherInvitationDTO( + invitation: TeacherInvitation +): TeacherInvitationDTO { return { - sender: mapToTeacherDTO(invitation.sender), - receiver: mapToTeacherDTO(invitation.receiver), + sender: mapToUserDTO(invitation.sender), + receiver: mapToUserDTO(invitation.receiver), class: mapToClassDTO(invitation.class), }; } -export function mapToTeacherInvitationDTOIds(invitation: TeacherInvitation): TeacherInvitationDTO { +export function mapToTeacherInvitationDTOIds( + invitation: TeacherInvitation +): TeacherInvitationDTO { return { sender: invitation.sender.username, receiver: invitation.receiver.username, diff --git a/backend/src/interfaces/teacher.ts b/backend/src/interfaces/teacher.ts deleted file mode 100644 index 15c89520..00000000 --- a/backend/src/interfaces/teacher.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Teacher } from "../entities/users/teacher.entity.js"; - -/** - * Teacher Data Transfer Object - */ -export interface TeacherDTO { - username: string; - firstName: string; - lastName: string; - endpoints?: { - self: string; - classes: string; - questions: string; - invitations: string; - }; -} - -/** - * Maps a Teacher entity to a TeacherDTO - */ -export function mapToTeacherDTO(teacher: Teacher): TeacherDTO { - return { - username: teacher.username, - firstName: teacher.firstName, - lastName: teacher.lastName, - }; -} - -export function mapToTeacher(teacherData: TeacherDTO): Teacher { - const teacher = new Teacher(); - teacher.username = teacherData.username; - teacher.firstName = teacherData.firstName; - teacher.lastName = teacherData.lastName; - - return teacher; -} diff --git a/backend/src/interfaces/user.ts b/backend/src/interfaces/user.ts index 02e27d83..64081d48 100644 --- a/backend/src/interfaces/user.ts +++ b/backend/src/interfaces/user.ts @@ -1,7 +1,7 @@ -import { User } from "../entities/users/user.entity.js"; +import { User } from '../entities/users/user.entity.js'; export interface UserDTO { - id?: string, + id?: string; username: string; firstName: string; lastName: string; @@ -22,7 +22,10 @@ export function mapToUserDTO(user: User): UserDTO { }; } -export function mapToUser(userData: UserDTO, userInstance: T): T { +export function mapToUser( + userData: UserDTO, + userInstance: T +): T { userInstance.username = userData.username; userInstance.firstName = userData.firstName; userInstance.lastName = userData.lastName; diff --git a/backend/src/mikro-orm.config.ts b/backend/src/mikro-orm.config.ts index 6af867e9..05b68fef 100644 --- a/backend/src/mikro-orm.config.ts +++ b/backend/src/mikro-orm.config.ts @@ -24,11 +24,20 @@ import { Answer } from './entities/questions/answer.entity.js'; import { Question } from './entities/questions/question.entity.js'; const entities = [ - User, Student, Teacher, - Assignment, Group, Submission, - Class, ClassJoinRequest, TeacherInvitation, - Attachment, LearningObject, LearningPath, - Answer, Question + User, + Student, + Teacher, + Assignment, + Group, + Submission, + Class, + ClassJoinRequest, + TeacherInvitation, + Attachment, + LearningObject, + LearningPath, + Answer, + Question, ]; function config(testingMode: boolean = false): Options { @@ -37,25 +46,26 @@ function config(testingMode: boolean = false): Options { driver: SqliteDriver, dbName: getEnvVar(EnvVars.DbName), entities: entities, - // entitiesTs: entitiesTs, + // EntitiesTs: entitiesTs, // 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), - }; - } else { - return { - driver: PostgreSqlDriver, - host: getEnvVar(EnvVars.DbHost), - port: getNumericEnvVar(EnvVars.DbPort), - dbName: getEnvVar(EnvVars.DbName), - user: getEnvVar(EnvVars.DbUsername), - password: getEnvVar(EnvVars.DbPassword), - entities: entities, - //entitiesTs: entitiesTs, - debug: true, + dynamicImportProvider: (id) => { + return import(id); + }, }; } + return { + driver: PostgreSqlDriver, + host: getEnvVar(EnvVars.DbHost), + port: getNumericEnvVar(EnvVars.DbPort), + dbName: getEnvVar(EnvVars.DbName), + user: getEnvVar(EnvVars.DbUsername), + password: getEnvVar(EnvVars.DbPassword), + entities: entities, + //EntitiesTs: entitiesTs, + debug: true, + }; } export default config; diff --git a/backend/src/routes/assignments.ts b/backend/src/routes/assignments.ts index 9d83e755..85f3bc82 100644 --- a/backend/src/routes/assignments.ts +++ b/backend/src/routes/assignments.ts @@ -1,31 +1,30 @@ -import express from 'express' -import { getAllAssignmentsHandler, getAssignmentHandler } from '../controllers/assignments.js'; -import groupRouter from './group.js'; +import express from 'express'; +import { + getAllAssignmentsHandler, + getAssignmentHandler, +} from '../controllers/assignments.js'; +import groupRouter from './groups.js'; const router = express.Router({ mergeParams: true }); -// root endpoint used to search objects +// Root endpoint used to search objects router.get('/', getAllAssignmentsHandler); -// information about an assignment with id 'id' +// Information about an assignment with id 'id' router.get('/:id', getAssignmentHandler); router.get('/:id/submissions', (req, res) => { res.json({ - submissions: [ - '0' - ], + submissions: ['0'], }); }); router.get('/:id/questions', (req, res) => { res.json({ - questions: [ - '0' - ], + questions: ['0'], }); }); router.use('/:assignmentid/groups', groupRouter); -export default router +export default router; diff --git a/backend/src/routes/classes.ts b/backend/src/routes/classes.ts index 06154e60..c67b573b 100644 --- a/backend/src/routes/classes.ts +++ b/backend/src/routes/classes.ts @@ -1,13 +1,18 @@ -import express from 'express' -import { getAllClassesHandler, getClassHandler, getClassStudentsHandler, getTeacherInvitationsHandler } from '../controllers/classes.js'; -import assignmentRouter from './assignment.js'; +import express from 'express'; +import { + getAllClassesHandler, + getClassHandler, + getClassStudentsHandler, + getTeacherInvitationsHandler, +} from '../controllers/classes.js'; +import assignmentRouter from './assignments.js'; const router = express.Router(); -// root endpoint used to search objects +// Root endpoint used to search objects router.get('/', getAllClassesHandler); -// information about an class with id 'id' +// Information about an class with id 'id' router.get('/:id', getClassHandler); router.get('/:id/teacher-invitations', getTeacherInvitationsHandler); @@ -16,4 +21,4 @@ router.get('/:id/students', getClassStudentsHandler); router.use('/:classid/assignments', assignmentRouter); -export default router +export default router; diff --git a/backend/src/routes/groups.ts b/backend/src/routes/groups.ts index 76cdc61f..d604c088 100644 --- a/backend/src/routes/groups.ts +++ b/backend/src/routes/groups.ts @@ -1,18 +1,19 @@ -import express from 'express' +import express from 'express'; import { getAllGroupsHandler, getGroupHandler } from '../controllers/groups.js'; + const router = express.Router({ mergeParams: true }); -// root endpoint used to search objects +// Root endpoint used to search objects router.get('/', getAllGroupsHandler); -// information about a group (members, ... [TODO DOC]) +// Information about a group (members, ... [TODO DOC]) router.get('/:groupid', getGroupHandler); -// the list of questions a group has made +// The list of questions a group has made router.get('/:id/question', (req, res) => { res.json({ - questions: [ '0' ], + questions: ['0'], }); -}) +}); -export default router +export default router; diff --git a/backend/src/routes/questions.ts b/backend/src/routes/questions.ts index 25d168b7..f683d998 100644 --- a/backend/src/routes/questions.ts +++ b/backend/src/routes/questions.ts @@ -1,38 +1,34 @@ -import express from 'express' +import express from 'express'; const router = express.Router(); -// root endpoint used to search objects +// Root endpoint used to search objects router.get('/', (req, res) => { res.json({ - questions: [ - '0', - '1', - ] + questions: ['0', '1'], }); }); -// information about an question with id 'id' +// Information about an question with id 'id' router.get('/:id', (req, res) => { res.json({ id: req.params.id, student: '0', group: '0', time: new Date(2025, 1, 1), - content: 'Zijn alle gehele getallen groter dan 2 gelijk aan de som van 2 priemgetallen????', + content: + 'Zijn alle gehele getallen groter dan 2 gelijk aan de som van 2 priemgetallen????', learningObject: '0', links: { self: `${req.baseUrl}/${req.params.id}`, answers: `${req.baseUrl}/${req.params.id}/answers`, - } + }, }); -}) +}); router.get('/:id/answers', (req, res) => { res.json({ - answers: [ - '0' - ], - }) -}) + answers: ['0'], + }); +}); -export default router \ No newline at end of file +export default router; diff --git a/backend/src/routes/students.ts b/backend/src/routes/students.ts index a5355c48..da1c4308 100644 --- a/backend/src/routes/students.ts +++ b/backend/src/routes/students.ts @@ -1,51 +1,49 @@ -import express from 'express' +import express from 'express'; import { - createStudentHandler, deleteStudentHandler, + createStudentHandler, + deleteStudentHandler, getAllStudentsHandler, getStudentAssignmentsHandler, getStudentClassesHandler, - getStudentHandler + getStudentHandler, } from '../controllers/students.js'; const router = express.Router(); -// root endpoint used to search objects +// Root endpoint used to search objects router.get('/', getAllStudentsHandler); router.post('/', createStudentHandler); router.delete('/:username', deleteStudentHandler); -// information about a student's profile +// Information about a student's profile router.get('/:username', getStudentHandler); - - -// the list of classes a student is in +// The list of classes a student is in router.get('/:id/classes', getStudentClassesHandler); -// the list of submissions a student has made +// The list of submissions a student has made router.get('/:id/submissions', (req, res) => { res.json({ - submissions: [ '0' ], + submissions: ['0'], }); -}) +}); - -// the list of assignments a student has +// The list of assignments a student has router.get('/:id/assignments', getStudentAssignmentsHandler); -// the list of groups a student is in +// The list of groups a student is in router.get('/:id/groups', (req, res) => { res.json({ - groups: [ '0' ], + groups: ['0'], }); -}) +}); -// a list of questions a user has created +// A list of questions a user has created router.get('/:id/questions', (req, res) => { res.json({ - questions: [ '0' ], + questions: ['0'], }); -}) +}); -export default router +export default router; diff --git a/backend/src/routes/submissions.ts b/backend/src/routes/submissions.ts index 8d09cf8e..cb4d3e85 100644 --- a/backend/src/routes/submissions.ts +++ b/backend/src/routes/submissions.ts @@ -1,17 +1,14 @@ -import express from 'express' +import express from 'express'; const router = express.Router(); -// root endpoint used to search objects +// Root endpoint used to search objects router.get('/', (req, res) => { res.json({ - submissions: [ - '0', - '1', - ] + submissions: ['0', '1'], }); }); -// information about an submission with id 'id' +// Information about an submission with id 'id' router.get('/:id', (req, res) => { res.json({ id: req.params.id, @@ -21,6 +18,6 @@ router.get('/:id', (req, res) => { content: 'Wortel 2 is rationeel', learningObject: '0', }); -}) +}); -export default router \ No newline at end of file +export default router; diff --git a/backend/src/routes/teachers.ts b/backend/src/routes/teachers.ts index 2a29897b..8e7f709d 100644 --- a/backend/src/routes/teachers.ts +++ b/backend/src/routes/teachers.ts @@ -1,17 +1,22 @@ -import express from 'express' +import express from 'express'; import { createTeacherHandler, deleteTeacherHandler, + getAllTeachersHandler, getTeacherClassHandler, - getTeacherHandler, getTeacherQuestionHandler, getTeacherStudentHandler -} from "../controllers/teachers.js"; + getTeacherHandler, + getTeacherQuestionHandler, + getTeacherStudentHandler, +} from '../controllers/teachers.js'; const router = express.Router(); -// root endpoint used to search objects -router.get('/', getTeacherHandler); +// Root endpoint used to search objects +router.get('/', getAllTeachersHandler); router.post('/', createTeacherHandler); +router.get('/:username', getTeacherHandler); + router.delete('/:username', deleteTeacherHandler); router.get('/:username/classes', getTeacherClassHandler); @@ -20,15 +25,11 @@ router.get('/:username/students', getTeacherStudentHandler); router.get('/:username/questions', getTeacherQuestionHandler); -// invitations to other classes a teacher received +// Invitations to other classes a teacher received router.get('/:id/invitations', (req, res) => { res.json({ - invitations: [ - '0' - ], + invitations: ['0'], }); }); - - -export default router +export default router; diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts index aaa51c10..1ecfb4d4 100644 --- a/backend/src/services/assignments.ts +++ b/backend/src/services/assignments.ts @@ -1,7 +1,17 @@ -import { getAssignmentRepository, getClassRepository } from "../data/repositories.js"; -import { AssignmentDTO, mapToAssignmentDTO, mapToAssignmentDTOId } from "../interfaces/assignments.js"; +import { + getAssignmentRepository, + getClassRepository, +} from '../data/repositories.js'; +import { + AssignmentDTO, + mapToAssignmentDTO, + mapToAssignmentDTOId, +} from '../interfaces/assignment.js'; -export async function getAllAssignments(classid: string, full: boolean): Promise { +export async function getAllAssignments( + classid: string, + full: boolean +): Promise { const classRepository = getClassRepository(); const cls = await classRepository.findById(classid); @@ -10,7 +20,8 @@ export async function getAllAssignments(classid: string, full: boolean): Promise } const assignmentRepository = getAssignmentRepository(); - const assignments = await assignmentRepository.findAllAssignmentsInClass(cls); + const assignments = + await assignmentRepository.findAllAssignmentsInClass(cls); if (full) { return assignments.map(mapToAssignmentDTO); @@ -19,7 +30,10 @@ export async function getAllAssignments(classid: string, full: boolean): Promise return assignments.map(mapToAssignmentDTOId); } -export async function getAssignment(classid: string, id: number): Promise { +export async function getAssignment( + classid: string, + id: number +): Promise { const classRepository = getClassRepository(); const cls = await classRepository.findById(classid); diff --git a/backend/src/services/class.ts b/backend/src/services/class.ts index 3867d910..a26aa7c2 100644 --- a/backend/src/services/class.ts +++ b/backend/src/services/class.ts @@ -1,11 +1,23 @@ -import { getClassRepository } from "../data/repositories"; -import { Class } from "../entities/classes/class.entity"; -import { ClassDTO, mapToClassDTO } from "../interfaces/class"; -import { mapToStudentDTO, StudentDTO } from "../interfaces/student"; +import { + getClassRepository, + getTeacherInvitationRepository, +} from '../data/repositories.js'; +import { ClassDTO, mapToClassDTO } from '../interfaces/class.js'; +import { mapToStudentDTO, StudentDTO } from '../interfaces/student.js'; +import { + mapToTeacherInvitationDTO, + mapToTeacherInvitationDTOIds, + TeacherInvitationDTO, +} from '../interfaces/teacher-invitation.js'; -export async function getAllClasses(full: boolean): Promise { +export async function getAllClasses( + full: boolean +): Promise { const classRepository = getClassRepository(); - const classes = await classRepository.find({}, { populate: ["students", "teachers"] }); + const classes = await classRepository.find( + {}, + { populate: ['students', 'teachers'] } + ); if (!classes) { return []; @@ -13,27 +25,30 @@ export async function getAllClasses(full: boolean): Promise cls.classId); } + return classes.map((cls) => { + return cls.classId; + }); } export async function getClass(classId: string): Promise { const classRepository = getClassRepository(); const cls = await classRepository.findById(classId); - if (!cls) return null; - else { - return mapToClassDTO(cls); + if (!cls) { + return null; } + + return mapToClassDTO(cls); } -async function fetchClassStudents(classId: string, full: boolean): Promise { +async function fetchClassStudents(classId: string): Promise { const classRepository = getClassRepository(); const cls = await classRepository.findById(classId); - if (!cls) + if (!cls) { return []; + } return cls.students.map(mapToStudentDTO); } @@ -43,6 +58,36 @@ export async function getClassStudents(classId: string): Promise { } export async function getClassStudentsIds(classId: string): Promise { - return await fetchClassStudents(classId).map((student) => student.username); + const students: StudentDTO[] = await fetchClassStudents(classId); + return students.map((student) => { + return student.username; + }); } +export async function getClassTeacherInvitations( + classId: string, + full: boolean +): Promise { + const classRepository = getClassRepository(); + const cls = await classRepository.findById(classId); + + if (!cls) { + return []; + } + + const teacherInvitationRepository = getTeacherInvitationRepository(); + const invitations = + await teacherInvitationRepository.findAllInvitationsForClass(cls); + + console.log(invitations); + + if (!invitations) { + return []; + } + + if (full) { + return invitations.map(mapToTeacherInvitationDTO); + } + + return invitations.map(mapToTeacherInvitationDTOIds); +} diff --git a/backend/src/services/groups.ts b/backend/src/services/groups.ts index b37c7c5c..467f90a3 100644 --- a/backend/src/services/groups.ts +++ b/backend/src/services/groups.ts @@ -1,11 +1,19 @@ -import { getAssignmentRepository, getClassRepository, getGroupRepository } from "../data/repositories.js"; -import { GroupDTO, mapToGroupDTO, mapToGroupDTOId } from "../interfaces/groups.js"; +import { + getAssignmentRepository, + getClassRepository, + getGroupRepository, +} from '../data/repositories.js'; +import { + GroupDTO, + mapToGroupDTO, + mapToGroupDTOId, +} from '../interfaces/group.js'; export async function getGroup( classId: string, assignmentNumber: number, groupNumber: number, - full: boolean, + full: boolean ): Promise { const classRepository = getClassRepository(); const cls = await classRepository.findById(classId); @@ -15,14 +23,20 @@ export async function getGroup( } const assignmentRepository = getAssignmentRepository(); - const assignment = await assignmentRepository.findByClassAndId(cls, assignmentNumber); + const assignment = await assignmentRepository.findByClassAndId( + cls, + assignmentNumber + ); if (!assignment) { return null; } const groupRepository = getGroupRepository(); - const group = await groupRepository.findByAssignmentAndGroupNumber(assignment, groupNumber); + const group = await groupRepository.findByAssignmentAndGroupNumber( + assignment, + groupNumber + ); if (!group) { return null; @@ -38,7 +52,7 @@ export async function getGroup( export async function getAllGroups( classId: string, assignmentNumber: number, - full: boolean, + full: boolean ): Promise { const classRepository = getClassRepository(); const cls = await classRepository.findById(classId); @@ -48,7 +62,10 @@ export async function getAllGroups( } const assignmentRepository = getAssignmentRepository(); - const assignment = await assignmentRepository.findByClassAndId(cls, assignmentNumber); + const assignment = await assignmentRepository.findByClassAndId( + cls, + assignmentNumber + ); if (!assignment) { return []; diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts index 24c9029b..952c3cf1 100644 --- a/backend/src/services/students.ts +++ b/backend/src/services/students.ts @@ -1,8 +1,11 @@ -import { getClassRepository, getStudentRepository } from "../data/repositories.js"; -import { Class } from "../entities/classes/class.entity.js"; -import { Student } from "../entities/users/student.entity.js"; -import { ClassDTO, mapToClassDTO } from "../interfaces/classes.js"; -import {UserService} from "./users.js"; +import { + getClassRepository, + getStudentRepository, +} from '../data/repositories.js'; +import { Class } from '../entities/classes/class.entity.js'; +import { Student } from '../entities/users/student.entity.js'; +import { ClassDTO, mapToClassDTO } from '../interfaces/class.js'; +import { UserService } from './users.js'; export class StudentService extends UserService { constructor() { @@ -14,12 +17,16 @@ async function fetchStudentClasses(username: string): Promise { const studentRepository = getStudentRepository(); const student = await studentRepository.findByUsername(username); - if (!student) return []; + if (!student) { + return []; + } const classRepository = getClassRepository(); const classes = await classRepository.findByStudent(student); - if (!classes) return []; + if (!classes) { + return []; + } return classes; } @@ -31,6 +38,7 @@ export async function getStudentClasses(username: string): Promise { export async function getStudentClassIds(username: string): Promise { const classes = await fetchStudentClasses(username); - return classes.map(cls => cls.classId); // 'class' is a native keyword + return classes.map((cls) => { + return cls.classId; + }); // 'class' is a native keyword } - diff --git a/backend/src/services/teachers.ts b/backend/src/services/teachers.ts index 4b8affc5..b87c3b92 100644 --- a/backend/src/services/teachers.ts +++ b/backend/src/services/teachers.ts @@ -2,130 +2,108 @@ import { getClassRepository, getLearningObjectRepository, getQuestionRepository, - getTeacherRepository -} from "../data/repositories.js"; -import {mapToTeacher, mapToTeacherDTO, TeacherDTO} from "../interfaces/teacher.js"; -import { Teacher } from "../entities/users/teacher.entity"; -import {ClassDTO, mapToClassDTO} from "../interfaces/class"; -import {getClassStudents, getClassStudentsIds} from "./class"; -import {StudentDTO} from "../interfaces/student"; -import {mapToQuestionDTO, QuestionDTO, QuestionId} from "../interfaces/question"; + 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'; - -async function fetchAllTeachers(): Promise { - const teacherRepository = getTeacherRepository(); - const teachers = await teacherRepository.find({}); - - return teachers.map(mapToTeacherDTO); +export class TeacherUserService extends UserService { + constructor() { + super(getTeacherRepository()); + } } -export async function getAllTeachers(): Promise { - return await fetchAllTeachers(); -} +export class TeacherService { + protected teacherService = new TeacherUserService(); + protected teacherRepository = getTeacherRepository(); + protected classRepository = getClassRepository(); + protected learningObjectRepository = getLearningObjectRepository(); + protected questionRepository = getQuestionRepository(); -export async function getAllTeachersIds(): Promise { - return await fetchAllTeachers().map((teacher) => teacher.username) -} + async fetchClassesByTeacher(username: string): Promise { + const teacher = await this.teacherRepository.findByUsername(username); + if (!teacher) { + return []; + } -export async function createTeacher(teacherData: TeacherDTO): Promise { - const teacherRepository = getTeacherRepository(); - const newTeacher = mapToTeacher(teacherData); - - await teacherRepository.addTeacher(newTeacher); - return newTeacher; -} - -export async function getTeacherByUsername(username: string): Promise { - const teacherRepository = getTeacherRepository(); - const teacher = await teacherRepository.findByUsername(username); - - return teacher ? mapToTeacherDTO(teacher) : null; -} - -export async function deleteTeacher(username: string): Promise { - const teacherRepository = getTeacherRepository(); - const teacher = await teacherRepository.findByUsername(username); - - if (!teacher) - return null; - - await teacherRepository.deleteByUsername(username); - return teacher; -} - -async function fetchClassesByTeacher(username: string): Promise { - const teacherRepository = getTeacherRepository(); - const classRepository = getClassRepository(); - - const teacher = await teacherRepository.findByUsername(username); - if (!teacher) { - return []; + const classes = await this.classRepository.findByTeacher(teacher); + return classes.map(mapToClassDTO); } - const classes = await classRepository.findByTeacher(teacher); - return classes.map(mapToClassDTO); -} - -export async function getClassesByTeacher(username: string): Promise { - return await fetchClassesByTeacher(username) -} - -export async function getClassIdsByTeacher(): Promise { - return await fetchClassesByTeacher(username).map((cls) => cls.id); -} - -async function fetchStudentsByTeacher(username: string) { - const classes = await getClassIdsByTeacher(); - - return Promise.all( - classes.map( async (id) => getClassStudents(id)) - ); -} - -export async function getStudentsByTeacher(username: string): Promise { - return await fetchStudentsByTeacher(username); -} - -export async function getStudentIdsByTeacher(): Promise { - return await fetchStudentsByTeacher(username).map((student) => student.username); -} - -async function fetchTeacherQuestions(username: string): Promise { - const learningObjectRepository = getLearningObjectRepository(); - const questionRepository = getQuestionRepository(); - - const teacher = getTeacherByUsername(username); - if (!teacher) { - throw new Error(`Teacher with username '${username}' not found.`); + async getClassesByTeacher(username: string): Promise { + return await this.fetchClassesByTeacher(username); } - // Find all learning objects that this teacher manages - const learningObjects = await learningObjectRepository.findAllByTeacher(teacher); + async getClassIdsByTeacher(username: string): Promise { + const classes = await this.fetchClassesByTeacher(username); + return classes.map((cls) => { + return cls.id; + }); + } - // Fetch all questions related to these learning objects - const questions = await questionRepository.findAllByLearningObjects(learningObjects); + async fetchStudentsByTeacher(username: string) { + const classes = await this.getClassIdsByTeacher(username); - return questions.map(mapToQuestionDTO); + return ( + await Promise.all( + classes.map(async (id) => { + return getClassStudents(id); + }) + ) + ).flat(); + } + + async getStudentsByTeacher(username: string): Promise { + return await this.fetchStudentsByTeacher(username); + } + + async getStudentIdsByTeacher(username: string): Promise { + const students = await this.fetchStudentsByTeacher(username); + return students.map((student) => { + return student.username; + }); + } + + async fetchTeacherQuestions(username: string): Promise { + const teacherDTO = + await this.teacherService.getUserByUsername(username); + if (!teacherDTO) { + throw new Error(`Teacher with username '${username}' not found.`); + } + + const teacher = mapToUser(teacherDTO, new Teacher()); + + // Find all learning objects that this teacher manages + const learningObjects = + await this.learningObjectRepository.findAllByTeacher(teacher); + + // Fetch all questions related to these learning objects + const questions = + await this.questionRepository.findAllByLearningObjects( + learningObjects + ); + + return questions.map(mapToQuestionDTO); + } + + async getQuestionsByTeacher(username: string): Promise { + return await this.fetchTeacherQuestions(username); + } + + async getQuestionIdsByTeacher(username: string): Promise { + const questions = await this.fetchTeacherQuestions(username); + + return questions.map(mapToQuestionId); + } } - -export async function getQuestionsByTeacher(username: string): Promise { - return await fetchTeacherQuestions(username); -} - -export async function getQuestionIdsByTeacher(username: string): Promise { - const questions = await fetchTeacherQuestions(username); - - return questions.map((question) => ({ - learningObjectHruid: question.learningObjectHruid, - learningObjectLanguage: question.learningObjectLanguage, - learningObjectVersion: question.learningObjectVersion, - sequenceNumber: question.sequenceNumber - })); -} - - - - - - - diff --git a/backend/src/services/users.ts b/backend/src/services/users.ts index bf88e916..fc1ea599 100644 --- a/backend/src/services/users.ts +++ b/backend/src/services/users.ts @@ -1,6 +1,6 @@ -import { UserRepository } from "../data/users/user-repository.js"; -import { UserDTO, mapToUser, mapToUserDTO } from "../interfaces/user.js"; -import {User} from "../entities/users/user.entity.js"; +import { UserRepository } from '../data/users/user-repository.js'; +import { UserDTO, mapToUser, mapToUserDTO } from '../interfaces/user.js'; +import { User } from '../entities/users/user.entity.js'; export class UserService { protected repository: UserRepository; @@ -16,11 +16,13 @@ export class UserService { async getAllUserIds(): Promise { const users = await this.getAllUsers(); - return users.map((user) => user.username); + return users.map((user) => { + return user.username; + }); } async getUserByUsername(username: string): Promise { - const user = await this.repository.findByUsername(username) + const user = await this.repository.findByUsername(username); return user ? mapToUserDTO(user) : null; } @@ -32,8 +34,10 @@ export class UserService { async deleteUser(username: string): Promise { const user = await this.getUserByUsername(username); - if (!user) return null; - await this.repository.deleteByUsername(username) + if (!user) { + return null; + } + await this.repository.deleteByUsername(username); return mapToUserDTO(user); } } diff --git a/backend/src/util/translation-helper.ts b/backend/src/util/translation-helper.ts index d6d842ff..f02a3a1b 100644 --- a/backend/src/util/translation-helper.ts +++ b/backend/src/util/translation-helper.ts @@ -1,7 +1,7 @@ import fs from 'fs'; import path from 'path'; import yaml from 'js-yaml'; -import { FALLBACK_LANG } from "../config.js"; +import { FALLBACK_LANG } from '../config.js'; export function loadTranslations(language: string): T { try { @@ -13,7 +13,11 @@ export function loadTranslations(language: string): T { `Cannot load translation for ${language}, fallen back to dutch` ); console.error(error); - const fallbackPath = path.join(process.cwd(), '_i18n', `${FALLBACK_LANG}.yml`); + const fallbackPath = path.join( + process.cwd(), + '_i18n', + `${FALLBACK_LANG}.yml` + ); return yaml.load(fs.readFileSync(fallbackPath, 'utf8')) as T; } } From 38ec0c75cafcdf27e7e2d2d598447ac413e98680 Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Mon, 10 Mar 2025 00:07:25 +0100 Subject: [PATCH 038/106] =?UTF-8?q?chore:=20update=20backend=20dockerfile?= =?UTF-8?q?=20compiler=20config=20deftig=20gekopi=C3=ABerd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend.Dockerfile | 5 ++++- backend/.env.production | 6 ++++++ backend/package.json | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 backend/.env.production diff --git a/backend.Dockerfile b/backend.Dockerfile index f2967423..88be591d 100644 --- a/backend.Dockerfile +++ b/backend.Dockerfile @@ -7,9 +7,12 @@ COPY ./backend/package*.json ./ RUN npm install COPY ./backend ./backend +COPY ./tsconfig.json /app WORKDIR /app/backend +RUN npm run build + EXPOSE 2002 -CMD ["npm", "start"] \ No newline at end of file +CMD ["npm", "run", "start"] \ No newline at end of file diff --git a/backend/.env.production b/backend/.env.production new file mode 100644 index 00000000..58694df4 --- /dev/null +++ b/backend/.env.production @@ -0,0 +1,6 @@ +DWENGO_PORT=3000 +DWENGO_DB_HOST=localhost +DWENGO_DB_PORT=5431 +DWENGO_DB_USERNAME=postgres +DWENGO_DB_PASSWORD=postgres +DWENGO_DB_UPDATE=true diff --git a/backend/package.json b/backend/package.json index 478b26e6..3267d28f 100644 --- a/backend/package.json +++ b/backend/package.json @@ -7,7 +7,7 @@ "scripts": { "build": "NODE_ENV=production tsc --project tsconfig.json", "dev": "NODE_ENV=development tsx watch --env-file=.env.development.local src/app.ts", - "start": "NODE_ENV=production node --env-file=.env dist/app.js", + "start": "NODE_ENV=production node --env-file=.env.production dist/app.js", "format": "prettier --write src/", "format-check": "prettier --check src/", "lint": "eslint . --fix", From 00541461885dd77339079d4e67d032896ee4d063 Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Mon, 10 Mar 2025 00:37:09 +0100 Subject: [PATCH 039/106] chore: update nginx config SSL wordt standaard gebruikt --- nginx/nginx.conf | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 81bf2ae5..0185fdbc 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -17,7 +17,6 @@ http { proxy_pass http://127.0.0.1:2002/; } - listen 80; listen 443 default_server ssl; # managed by Certbot ssl_certificate /etc/letsencrypt/live/sel2-1.ugent.be/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/sel2-1.ugent.be/privkey.pem; # managed by Certbot @@ -36,15 +35,15 @@ http { } -# server { -# if ($host = sel2-1.ugent.be) { -# return 301 https://$host$request_uri; -# } # managed by Certbot -# -# -# listen 80; -# server_name sel2-1.ugent.be; -# return 404; # managed by Certbot -# -# } + server { + if ($host = sel2-1.ugent.be) { + return 301 https://$host$request_uri; + } # managed by Certbot + + + listen 80; + server_name sel2-1.ugent.be; + return 404; # managed by Certbot + + } } \ No newline at end of file From 22cdf58fed42109f8ca69a6b62be534d611dd764 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Mon, 10 Mar 2025 11:24:37 +0100 Subject: [PATCH 040/106] feat: groepen van een leerling geimplmenteerd in backend --- backend/src/controllers/students.ts | 34 +++++------ .../src/data/assignments/group-repository.ts | 9 +++ backend/src/services/students.ts | 56 +++++++++++++++---- 3 files changed, 71 insertions(+), 28 deletions(-) diff --git a/backend/src/controllers/students.ts b/backend/src/controllers/students.ts index 2134a9f2..a104979a 100644 --- a/backend/src/controllers/students.ts +++ b/backend/src/controllers/students.ts @@ -1,7 +1,8 @@ import { Request, Response } from 'express'; import { + getStudentAssignments, getStudentClasses, - getStudentClassIds, + getStudentGroups, StudentService, } from '../services/students.js'; import { ClassDTO } from '../interfaces/class.js'; @@ -52,12 +53,7 @@ export async function getStudentClassesHandler( const full = req.query.full === 'true'; const username = req.params.id; - let classes: ClassDTO[] | string[]; - if (full) { - classes = await getStudentClasses(username); - } else { - classes = await getStudentClassIds(username); - } + const classes = await getStudentClasses(username, full); res.json({ classes: classes, @@ -85,17 +81,23 @@ export async function getStudentAssignmentsHandler( const full = req.query.full === 'true'; const username = req.params.id; - const classes = await getStudentClasses(username); - - const assignments = ( - await Promise.all( - classes.map(async (cls) => { - return await getAllAssignments(cls.id, full); - }) - ) - ).flat(); + const assignments = getStudentAssignments(username, full); res.json({ assignments: assignments, }); } + +export async function getStudentGroupsHandler( + req: Request, + res: Response, +): Promise { + const full = req.query.full === 'true'; + const username = req.params.id; + + const groups = await getStudentGroups(username, full); + + res.json({ + groups: groups, + }); +} diff --git a/backend/src/data/assignments/group-repository.ts b/backend/src/data/assignments/group-repository.ts index e9b668b9..56736787 100644 --- a/backend/src/data/assignments/group-repository.ts +++ b/backend/src/data/assignments/group-repository.ts @@ -1,6 +1,7 @@ import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; import { Group } from '../../entities/assignments/group.entity.js'; import { Assignment } from '../../entities/assignments/assignment.entity.js'; +import { Student } from '../../entities/users/student.entity.js'; export class GroupRepository extends DwengoEntityRepository { public findByAssignmentAndGroupNumber( @@ -23,6 +24,14 @@ export class GroupRepository extends DwengoEntityRepository { populate: ['members'], }); } + public findAllGroupsWithStudent( + student: Student + ): Promise { + return this.find( + { members: student }, + { populate: ['members'] } + ) + } public deleteByAssignmentAndGroupNumber( assignment: Assignment, groupNumber: number diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts index 952c3cf1..fc0a8e1d 100644 --- a/backend/src/services/students.ts +++ b/backend/src/services/students.ts @@ -1,10 +1,14 @@ import { getClassRepository, + getGroupRepository, getStudentRepository, } 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 { getAllAssignments } from './assignments.js'; import { UserService } from './users.js'; export class StudentService extends UserService { @@ -13,7 +17,7 @@ export class StudentService extends UserService { } } -async function fetchStudentClasses(username: string): Promise { +export async function getStudentClasses(username: string, full: boolean): Promise { const studentRepository = getStudentRepository(); const student = await studentRepository.findByUsername(username); @@ -24,21 +28,49 @@ async function fetchStudentClasses(username: string): Promise { const classRepository = getClassRepository(); const classes = await classRepository.findByStudent(student); - if (!classes) { + if (full) { + return classes.map(mapToClassDTO); + } + + return classes.map((cls) => cls.classId); +} + +export async function getStudentAssignments(username: string, full: boolean): Promise { + const studentRepository = getStudentRepository(); + const student = await studentRepository.findByUsername(username); + + if (!student) { return []; } - return classes; + const classRepository = getClassRepository(); + const classes = await classRepository.findByStudent(student); + + const assignments = ( + await Promise.all( + classes.map(async (cls) => { + return await getAllAssignments(cls.classId, full); + }) + ) + ).flat(); + + return assignments; } -export async function getStudentClasses(username: string): Promise { - const classes = await fetchStudentClasses(username); - return classes.map(mapToClassDTO); -} +export async function getStudentGroups(username: string, full: boolean): Promise { + const studentRepository = getStudentRepository(); + const student = await studentRepository.findByUsername(username); -export async function getStudentClassIds(username: string): Promise { - const classes = await fetchStudentClasses(username); - return classes.map((cls) => { - return cls.classId; - }); // 'class' is a native keyword + if (!student) { + return []; + } + + const groupRepository = getGroupRepository(); + const groups = await groupRepository.findAllGroupsWithStudent(student); + + if (full) { + return groups.map(mapToGroupDTO); + } + + return groups.map(mapToGroupDTOId); } From 9f889fa5f149bf9a3ad9fd041305de4f202acfaf Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Mon, 10 Mar 2025 11:27:46 +0100 Subject: [PATCH 041/106] fix: student group handler in routes/student.ts toegevoegd --- backend/src/routes/students.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/backend/src/routes/students.ts b/backend/src/routes/students.ts index da1c4308..c18b3e1c 100644 --- a/backend/src/routes/students.ts +++ b/backend/src/routes/students.ts @@ -5,8 +5,10 @@ import { getAllStudentsHandler, getStudentAssignmentsHandler, getStudentClassesHandler, + getStudentGroupsHandler, getStudentHandler, } from '../controllers/students.js'; +import { getStudentGroups } from '../services/students.js'; const router = express.Router(); // Root endpoint used to search objects @@ -33,11 +35,7 @@ router.get('/:id/submissions', (req, res) => { router.get('/:id/assignments', getStudentAssignmentsHandler); // The list of groups a student is in -router.get('/:id/groups', (req, res) => { - res.json({ - groups: ['0'], - }); -}); +router.get('/:id/groups', getStudentGroupsHandler); // A list of questions a user has created router.get('/:id/questions', (req, res) => { From 394deba56db2613160d8990486ebb13e8a27f3fc Mon Sep 17 00:00:00 2001 From: Lint Action Date: Mon, 10 Mar 2025 11:20:10 +0000 Subject: [PATCH 042/106] style: fix linting issues met ESLint --- backend/src/middleware/auth/auth.ts | 14 +++++++------- backend/src/routes/auth.ts | 2 +- frontend/src/services/auth/auth-service.ts | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/backend/src/middleware/auth/auth.ts b/backend/src/middleware/auth/auth.ts index 9c988146..fe6c9fbf 100644 --- a/backend/src/middleware/auth/auth.ts +++ b/backend/src/middleware/auth/auth.ts @@ -12,7 +12,7 @@ const JWKS_CACHE = true; const JWKS_RATE_LIMIT = true; const REQUEST_PROPERTY_FOR_JWT_PAYLOAD = "jwtPayload"; const JWT_ALGORITHM = "RS256"; // Not configurable via env vars since supporting other algorithms would - // require additional libraries to be added. + // Require additional libraries to be added. const JWT_PROPERTY_NAMES = { username: "preferred_username", @@ -50,9 +50,9 @@ const verifyJwtToken = expressjwt({ throw new Error("Invalid token"); } - let issuer = (token.payload as JwtPayload).iss; + const issuer = (token.payload as JwtPayload).iss; - let idpConfig = Object.values(idpConfigs).find(config => config.issuer === issuer); + const idpConfig = Object.values(idpConfigs).find(config => {return config.issuer === issuer}); if (!idpConfig) { throw new Error("Issuer not accepted."); } @@ -76,7 +76,7 @@ function getAuthenticationInfo(req: AuthenticatedRequest): AuthenticationInfo | if (!req.jwtPayload) { return; } - let issuer = req.jwtPayload.iss; + const issuer = req.jwtPayload.iss; let accountType: "student" | "teacher"; if (issuer === idpConfigs.student.issuer) { @@ -128,14 +128,14 @@ export const authorize = (accessCondition: (auth: AuthenticationInfo) => boolean /** * Middleware which rejects all unauthenticated users, but accepts all authenticated users. */ -export const authenticatedOnly = authorize(_ => true); +export const authenticatedOnly = authorize(_ => {return true}); /** * Middleware which rejects requests from unauthenticated users or users that aren't students. */ -export const studentsOnly = authorize(auth => auth.accountType === "student"); +export const studentsOnly = authorize(auth => {return auth.accountType === "student"}); /** * Middleware which rejects requests from unauthenticated users or users that aren't teachers. */ -export const teachersOnly = authorize(auth => auth.accountType === "teacher"); +export const teachersOnly = authorize(auth => {return auth.accountType === "teacher"}); diff --git a/backend/src/routes/auth.ts b/backend/src/routes/auth.ts index 0ab5b210..cf280719 100644 --- a/backend/src/routes/auth.ts +++ b/backend/src/routes/auth.ts @@ -3,7 +3,7 @@ import {getFrontendAuthConfig} from "../controllers/auth.js"; import {authenticatedOnly, studentsOnly, teachersOnly} from "../middleware/auth/auth.js"; const router = express.Router(); -// returns auth configuration for frontend +// Returns auth configuration for frontend router.get('/config', (req, res) => { res.json(getFrontendAuthConfig()); }); diff --git a/frontend/src/services/auth/auth-service.ts b/frontend/src/services/auth/auth-service.ts index a38d5f2a..5bd64ea1 100644 --- a/frontend/src/services/auth/auth-service.ts +++ b/frontend/src/services/auth/auth-service.ts @@ -27,7 +27,7 @@ async function loadUser(): Promise { if (!activeRole) { return null; } - let user = await userManagers[activeRole].getUser(); + const user = await userManagers[activeRole].getUser(); authState.user = user; authState.accessToken = user?.access_token || null; authState.activeRole = activeRole || null; @@ -43,7 +43,7 @@ const authState = reactive({ activeRole: authStorage.getActiveRole() || null }); -const isLoggedIn = computed(() => authState.user !== null); +const isLoggedIn = computed(() => {return authState.user !== null}); /** * Redirect the user to the login page where he/she can choose whether to log in as a student or teacher. @@ -110,20 +110,20 @@ apiClient.interceptors.request.use(async (reqConfig) => { reqConfig.headers.Authorization = `Bearer ${token}`; } return reqConfig; -}, (error) => Promise.reject(error)); +}, (error) => {return Promise.reject(error)}); // Registering interceptor to refresh the token when a request failed because it was expired. apiClient.interceptors.response.use( - response => response, + response => {return response}, async (error: AxiosError<{message?: string}>) => { if (error.response?.status === 401) { if (error.response!.data.message === "token_expired") { console.log("Access token expired, trying to refresh..."); await renewToken(); return apiClient(error.config!); // Retry the request - } else { // Apparently, the user got a 401 because he was not logged in yet at all. Redirect him to login. + } // Apparently, the user got a 401 because he was not logged in yet at all. Redirect him to login. await initiateLogin() - } + } return Promise.reject(error); } From 464dcbf73c2e30400015f4e96d2c347667773393 Mon Sep 17 00:00:00 2001 From: Lint Action Date: Mon, 10 Mar 2025 11:20:14 +0000 Subject: [PATCH 043/106] style: fix linting issues met Prettier --- backend/src/app.ts | 16 +- backend/src/controllers/auth.ts | 26 +- backend/src/exceptions.ts | 4 +- backend/src/middleware/auth/auth.ts | 94 +- .../auth/authenticated-request.d.ts | 6 +- .../middleware/auth/authentication-info.d.ts | 12 +- backend/src/middleware/cors.ts | 6 +- backend/src/routes/auth.ts | 16 +- backend/src/util/envvars.ts | 30 +- docker-compose.yml | 17 +- docs/api/generate.ts | 44 +- docs/api/swagger.json | 1512 +++--- frontend/src/config.ts | 4 +- frontend/src/router/index.ts | 2 +- frontend/src/services/api-client.ts | 2 +- .../src/services/auth/auth-config-loader.ts | 4 +- frontend/src/services/auth/auth-service.ts | 56 +- frontend/src/services/auth/auth-storage.ts | 6 +- frontend/src/services/auth/auth.d.ts | 22 +- frontend/src/views/CallbackPage.vue | 10 +- frontend/src/views/HomePage.vue | 10 +- frontend/src/views/LoginPage.vue | 4 +- idp/README.md | 12 +- idp/student-realm.json | 4506 +++++++++-------- idp/teacher-realm.json | 4502 ++++++++-------- 25 files changed, 5861 insertions(+), 5062 deletions(-) diff --git a/backend/src/app.ts b/backend/src/app.ts index 15304ec7..a80980d2 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -12,14 +12,14 @@ import submissionRouter from './routes/submission.js'; import classRouter from './routes/class.js'; import questionRouter from './routes/question.js'; import authRouter from './routes/auth.js'; -import {authenticateUser} from './middleware/auth/auth.js'; +import { authenticateUser } from './middleware/auth/auth.js'; 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 swaggerMiddleware from "./swagger"; -import swaggerUi from "swagger-ui-express"; +import swaggerMiddleware from './swagger'; +import swaggerUi from 'swagger-ui-express'; const logger: Logger = getLogger(); @@ -50,8 +50,14 @@ app.use('/question', questionRouter /* #swagger.tags = ['Question'] */); app.use('/auth', authRouter /* #swagger.tags = ['Auth'] */); app.use('/theme', themeRoutes /* #swagger.tags = ['Theme'] */); -app.use('/learningPath', learningPathRoutes /* #swagger.tags = ['Learning Path'] */); -app.use('/learningObject', learningObjectRoutes /* #swagger.tags = ['Learning Object'] */); +app.use( + '/learningPath', + learningPathRoutes /* #swagger.tags = ['Learning Path'] */ +); +app.use( + '/learningObject', + learningObjectRoutes /* #swagger.tags = ['Learning Object'] */ +); // Swagger UI for API documentation app.use('/api-docs', swaggerUi.serve, swaggerMiddleware); diff --git a/backend/src/controllers/auth.ts b/backend/src/controllers/auth.ts index 14c614a5..409ead0c 100644 --- a/backend/src/controllers/auth.ts +++ b/backend/src/controllers/auth.ts @@ -1,19 +1,19 @@ -import {EnvVars, getEnvVar} from "../util/envvars.js"; +import { EnvVars, getEnvVar } from '../util/envvars.js'; type FrontendIdpConfig = { - authority: string, - clientId: string, - scope: string, - responseType: string -} + authority: string; + clientId: string; + scope: string; + responseType: string; +}; type FrontendAuthConfig = { - student: FrontendIdpConfig, - teacher: FrontendIdpConfig -} + student: FrontendIdpConfig; + teacher: FrontendIdpConfig; +}; -const SCOPE = "openid profile email"; -const RESPONSE_TYPE = "code"; +const SCOPE = 'openid profile email'; +const RESPONSE_TYPE = 'code'; export function getFrontendAuthConfig(): FrontendAuthConfig { return { @@ -21,13 +21,13 @@ export function getFrontendAuthConfig(): FrontendAuthConfig { authority: getEnvVar(EnvVars.IdpStudentUrl), clientId: getEnvVar(EnvVars.IdpStudentClientId), scope: SCOPE, - responseType: RESPONSE_TYPE + responseType: RESPONSE_TYPE, }, teacher: { authority: getEnvVar(EnvVars.IdpTeacherUrl), clientId: getEnvVar(EnvVars.IdpTeacherClientId), scope: SCOPE, - responseType: RESPONSE_TYPE + responseType: RESPONSE_TYPE, }, }; } diff --git a/backend/src/exceptions.ts b/backend/src/exceptions.ts index 2b6e6d3c..a76e2b72 100644 --- a/backend/src/exceptions.ts +++ b/backend/src/exceptions.ts @@ -1,13 +1,13 @@ export class UnauthorizedException extends Error { status = 401; - constructor(message: string = "Unauthorized") { + constructor(message: string = 'Unauthorized') { super(message); } } export class ForbiddenException extends Error { status = 403; - constructor(message: string = "Forbidden") { + constructor(message: string = 'Forbidden') { super(message); } } diff --git a/backend/src/middleware/auth/auth.ts b/backend/src/middleware/auth/auth.ts index fe6c9fbf..f9db6688 100644 --- a/backend/src/middleware/auth/auth.ts +++ b/backend/src/middleware/auth/auth.ts @@ -1,25 +1,25 @@ -import {EnvVars, getEnvVar} from "../../util/envvars.js"; -import {expressjwt} from 'express-jwt'; -import {JwtPayload} from 'jsonwebtoken' +import { EnvVars, getEnvVar } from '../../util/envvars.js'; +import { expressjwt } from 'express-jwt'; +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"; +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'; const JWKS_CACHE = true; const JWKS_RATE_LIMIT = true; -const REQUEST_PROPERTY_FOR_JWT_PAYLOAD = "jwtPayload"; -const JWT_ALGORITHM = "RS256"; // Not configurable via env vars since supporting other algorithms would - // Require additional libraries to be added. +const REQUEST_PROPERTY_FOR_JWT_PAYLOAD = 'jwtPayload'; +const JWT_ALGORITHM = 'RS256'; // Not configurable via env vars since supporting other algorithms would +// Require additional libraries to be added. const JWT_PROPERTY_NAMES = { - username: "preferred_username", - firstName: "given_name", - lastName: "family_name", - name: "name", - email: "email" + username: 'preferred_username', + firstName: 'given_name', + lastName: 'family_name', + name: 'name', + email: 'email', }; function createJwksClient(uri: string): jwksClient.JwksClient { @@ -38,7 +38,7 @@ const idpConfigs = { teacher: { issuer: getEnvVar(EnvVars.IdpTeacherUrl), jwksClient: createJwksClient(getEnvVar(EnvVars.IdpTeacherJwksEndpoint)), - } + }, }; /** @@ -47,42 +47,48 @@ const idpConfigs = { const verifyJwtToken = expressjwt({ secret: async (_: express.Request, token: jwt.Jwt | undefined) => { if (!token?.payload || !(token.payload as JwtPayload).iss) { - throw new Error("Invalid token"); + throw new Error('Invalid token'); } const issuer = (token.payload as JwtPayload).iss; - const idpConfig = Object.values(idpConfigs).find(config => {return config.issuer === issuer}); + const idpConfig = Object.values(idpConfigs).find((config) => { + return config.issuer === issuer; + }); if (!idpConfig) { - throw new Error("Issuer not accepted."); + throw new Error('Issuer not accepted.'); } - const signingKey = await idpConfig.jwksClient.getSigningKey(token.header.kid); + const signingKey = await idpConfig.jwksClient.getSigningKey( + token.header.kid + ); if (!signingKey) { - throw new Error("Signing key not found."); + throw new Error('Signing key not found.'); } return signingKey.getPublicKey(); }, audience: getEnvVar(EnvVars.IdpAudience), algorithms: [JWT_ALGORITHM], credentialsRequired: false, - requestProperty: REQUEST_PROPERTY_FOR_JWT_PAYLOAD + requestProperty: REQUEST_PROPERTY_FOR_JWT_PAYLOAD, }); /** * Get an object with information about the authenticated user from a given authenticated request. */ -function getAuthenticationInfo(req: AuthenticatedRequest): AuthenticationInfo | undefined { +function getAuthenticationInfo( + req: AuthenticatedRequest +): AuthenticationInfo | undefined { if (!req.jwtPayload) { return; } const issuer = req.jwtPayload.iss; - let accountType: "student" | "teacher"; + let accountType: 'student' | 'teacher'; if (issuer === idpConfigs.student.issuer) { - accountType = "student"; + accountType = 'student'; } else if (issuer === idpConfigs.teacher.issuer) { - accountType = "teacher"; + accountType = 'teacher'; } else { return; } @@ -93,14 +99,18 @@ function getAuthenticationInfo(req: AuthenticatedRequest): AuthenticationInfo | firstName: req.jwtPayload[JWT_PROPERTY_NAMES.firstName], lastName: req.jwtPayload[JWT_PROPERTY_NAMES.lastName], email: req.jwtPayload[JWT_PROPERTY_NAMES.email], - } + }; } /** * 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(); }; @@ -113,8 +123,14 @@ 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) => { - return (req: AuthenticatedRequest, res: express.Response, next: express.NextFunction): void => { +export const 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)) { @@ -122,20 +138,26 @@ export const authorize = (accessCondition: (auth: AuthenticationInfo) => boolean } else { next(); } - } -} + }; +}; /** * Middleware which rejects all unauthenticated users, but accepts all authenticated users. */ -export const authenticatedOnly = authorize(_ => {return true}); +export const authenticatedOnly = authorize((_) => { + return true; +}); /** * Middleware which rejects requests from unauthenticated users or users that aren't students. */ -export const studentsOnly = authorize(auth => {return auth.accountType === "student"}); +export const studentsOnly = authorize((auth) => { + return auth.accountType === 'student'; +}); /** * Middleware which rejects requests from unauthenticated users or users that aren't teachers. */ -export const teachersOnly = authorize(auth => {return auth.accountType === "teacher"}); +export const teachersOnly = authorize((auth) => { + return auth.accountType === 'teacher'; +}); diff --git a/backend/src/middleware/auth/authenticated-request.d.ts b/backend/src/middleware/auth/authenticated-request.d.ts index 275b2d19..9737fa7e 100644 --- a/backend/src/middleware/auth/authenticated-request.d.ts +++ b/backend/src/middleware/auth/authenticated-request.d.ts @@ -1,6 +1,6 @@ -import { Request } from "express"; -import { JwtPayload } from "jsonwebtoken"; -import {AuthenticationInfo} from "./authentication-info.js"; +import { Request } from 'express'; +import { JwtPayload } from 'jsonwebtoken'; +import { AuthenticationInfo } from './authentication-info.js'; export interface AuthenticatedRequest extends Request { // Properties are optional since the user is not necessarily authenticated. diff --git a/backend/src/middleware/auth/authentication-info.d.ts b/backend/src/middleware/auth/authentication-info.d.ts index 6711edd0..4b060dfa 100644 --- a/backend/src/middleware/auth/authentication-info.d.ts +++ b/backend/src/middleware/auth/authentication-info.d.ts @@ -2,10 +2,10 @@ * Object with information about the user who is currently logged in. */ export type AuthenticationInfo = { - accountType: "student" | "teacher", - username: string, - name?: string, - firstName?: string, - lastName?: string, - email?: string + accountType: 'student' | 'teacher'; + username: string; + name?: string; + firstName?: string; + lastName?: string; + email?: string; }; diff --git a/backend/src/middleware/cors.ts b/backend/src/middleware/cors.ts index 88104fb5..3d2c9be0 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 cors from 'cors'; +import { EnvVars, getEnvVar } from '../util/envvars.js'; export default cors({ origin: getEnvVar(EnvVars.CorsAllowedOrigins).split(','), - allowedHeaders: getEnvVar(EnvVars.CorsAllowedHeaders).split(',') + allowedHeaders: getEnvVar(EnvVars.CorsAllowedHeaders).split(','), }); diff --git a/backend/src/routes/auth.ts b/backend/src/routes/auth.ts index cf280719..35c805e9 100644 --- a/backend/src/routes/auth.ts +++ b/backend/src/routes/auth.ts @@ -1,6 +1,10 @@ -import express from 'express' -import {getFrontendAuthConfig} from "../controllers/auth.js"; -import {authenticatedOnly, studentsOnly, teachersOnly} from "../middleware/auth/auth.js"; +import express from 'express'; +import { getFrontendAuthConfig } from '../controllers/auth.js'; +import { + authenticatedOnly, + studentsOnly, + teachersOnly, +} from '../middleware/auth/auth.js'; const router = express.Router(); // Returns auth configuration for frontend @@ -10,17 +14,17 @@ router.get('/config', (req, res) => { router.get('/testAuthenticatedOnly', authenticatedOnly, (req, res) => { /* #swagger.security = [{ "student": [ ] }, { "teacher": [ ] }] */ - res.json({message: "If you see this, you should be authenticated!"}); + res.json({ message: 'If you see this, you should be authenticated!' }); }); router.get('/testStudentsOnly', studentsOnly, (req, res) => { /* #swagger.security = [{ "student": [ ] }] */ - res.json({message: "If you see this, you should be a student!"}); + res.json({ message: 'If you see this, you should be a student!' }); }); router.get('/testTeachersOnly', teachersOnly, (req, res) => { /* #swagger.security = [{ "teacher": [ ] }] */ - res.json({message: "If you see this, you should be a teacher!"}); + res.json({ message: 'If you see this, you should be a teacher!' }); }); export default router; diff --git a/backend/src/util/envvars.ts b/backend/src/util/envvars.ts index b5142e58..449c799e 100644 --- a/backend/src/util/envvars.ts +++ b/backend/src/util/envvars.ts @@ -16,14 +16,32 @@ export const EnvVars: { [key: string]: EnvVar } = { DbPassword: { key: DB_PREFIX + 'PASSWORD', required: true }, DbUpdate: { key: DB_PREFIX + 'UPDATE', defaultValue: false }, IdpStudentUrl: { key: STUDENT_IDP_PREFIX + 'URL', required: true }, - IdpStudentClientId: { key: STUDENT_IDP_PREFIX + 'CLIENT_ID', required: true }, - IdpStudentJwksEndpoint: { key: STUDENT_IDP_PREFIX + 'JWKS_ENDPOINT', required: true }, + IdpStudentClientId: { + key: STUDENT_IDP_PREFIX + 'CLIENT_ID', + required: true, + }, + IdpStudentJwksEndpoint: { + key: STUDENT_IDP_PREFIX + 'JWKS_ENDPOINT', + required: true, + }, IdpTeacherUrl: { key: TEACHER_IDP_PREFIX + 'URL', required: true }, - IdpTeacherClientId: { key: TEACHER_IDP_PREFIX + 'CLIENT_ID', required: true }, - IdpTeacherJwksEndpoint: { key: TEACHER_IDP_PREFIX + 'JWKS_ENDPOINT', required: true }, + IdpTeacherClientId: { + key: TEACHER_IDP_PREFIX + 'CLIENT_ID', + required: true, + }, + IdpTeacherJwksEndpoint: { + key: TEACHER_IDP_PREFIX + 'JWKS_ENDPOINT', + required: true, + }, IdpAudience: { key: IDP_PREFIX + 'AUDIENCE', defaultValue: 'account' }, - CorsAllowedOrigins: { key: CORS_PREFIX + 'ALLOWED_ORIGINS', defaultValue: ''}, - CorsAllowedHeaders: { key: CORS_PREFIX + 'ALLOWED_HEADERS', defaultValue: 'Authorization,Content-Type'} + CorsAllowedOrigins: { + key: CORS_PREFIX + 'ALLOWED_ORIGINS', + defaultValue: '', + }, + CorsAllowedHeaders: { + key: CORS_PREFIX + 'ALLOWED_HEADERS', + defaultValue: 'Authorization,Content-Type', + }, } as const; /** diff --git a/docker-compose.yml b/docker-compose.yml index f43cdf4e..4c61a1c9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,16 +23,19 @@ services: KC_HEALTH_ENABLED: 'true' KC_LOG_LEVEL: info healthcheck: - test: [ 'CMD', 'curl', '-f', 'http://localhost:7080/health/ready' ] + test: ['CMD', 'curl', '-f', 'http://localhost:7080/health/ready'] interval: 15s timeout: 2s retries: 15 - command: [ - 'start-dev', - '--http-port', '7080', - '--https-port', '7443', - '--import-realm' - ] + command: + [ + 'start-dev', + '--http-port', + '7080', + '--https-port', + '7443', + '--import-realm', + ] ports: - '7080:7080' - '7443:7443' diff --git a/docs/api/generate.ts b/docs/api/generate.ts index 053c289f..32f3922d 100644 --- a/docs/api/generate.ts +++ b/docs/api/generate.ts @@ -1,4 +1,4 @@ -import swaggerAutogen from "swagger-autogen"; +import swaggerAutogen from 'swagger-autogen'; const doc = { info: { @@ -7,18 +7,18 @@ const doc = { description: 'Dwengo-1 Backend API using Express, based on VZW Dwengo', license: { name: 'MIT', - url: 'https://github.com/SELab-2/Dwengo-1/blob/336496ab6352ee3f8bf47490c90b5cf81526cef6/LICENSE' - } + url: 'https://github.com/SELab-2/Dwengo-1/blob/336496ab6352ee3f8bf47490c90b5cf81526cef6/LICENSE', + }, }, servers: [ { url: 'http://localhost:3000/', - description: 'Development server' + description: 'Development server', }, { url: 'https://sel2-1.ugent.be/api', - description: 'Production server' - } + description: 'Production server', + }, ], components: { securitySchemes: { @@ -26,35 +26,35 @@ const doc = { type: 'oauth2', flows: { implicit: { - authorizationUrl: 'http://localhost:7080/realms/student/protocol/openid-connect/auth', + authorizationUrl: + 'http://localhost:7080/realms/student/protocol/openid-connect/auth', scopes: { openid: 'openid', profile: 'profile', - email: 'email' - } - } - } + email: 'email', + }, + }, + }, }, teacher: { type: 'oauth2', flows: { implicit: { - authorizationUrl: 'http://localhost:7080/realms/teacher/protocol/openid-connect/auth', + authorizationUrl: + 'http://localhost:7080/realms/teacher/protocol/openid-connect/auth', scopes: { openid: 'openid', profile: 'profile', - email: 'email' - } - } - } - } - } - } + email: 'email', + }, + }, + }, + }, + }, + }, }; const outputFile = './swagger.json'; -const routes = [ - '../../backend/src/app.ts' -]; +const routes = ['../../backend/src/app.ts']; swaggerAutogen({ openapi: '3.1.0' })(outputFile, routes, doc); diff --git a/docs/api/swagger.json b/docs/api/swagger.json index 8f257518..22337c4b 100644 --- a/docs/api/swagger.json +++ b/docs/api/swagger.json @@ -1,801 +1,735 @@ { - "openapi": "3.1.0", - "info": { - "version": "0.1.0", - "title": "Dwengo-1 Backend API", - "description": "Dwengo-1 Backend API using Express, based on VZW Dwengo", - "license": { - "name": "MIT", - "url": "https://github.com/SELab-2/Dwengo-1/blob/336496ab6352ee3f8bf47490c90b5cf81526cef6/LICENSE" - } - }, - "servers": [ - { - "url": "http://localhost:3000/", - "description": "Development server" - }, - { - "url": "https://sel2-1.ugent.be/api", - "description": "Production server" - } - ], - "paths": { - "/": { - "get": { - "description": "", - "responses": { - "200": { - "description": "OK" - } + "openapi": "3.1.0", + "info": { + "version": "0.1.0", + "title": "Dwengo-1 Backend API", + "description": "Dwengo-1 Backend API using Express, based on VZW Dwengo", + "license": { + "name": "MIT", + "url": "https://github.com/SELab-2/Dwengo-1/blob/336496ab6352ee3f8bf47490c90b5cf81526cef6/LICENSE" } - } }, - "/student/": { - "get": { - "tags": [ - "Student" - ], - "description": "", - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/student/{id}": { - "get": { - "tags": [ - "Student" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/student/{id}/classes": { - "get": { - "tags": [ - "Student" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/student/{id}/submissions": { - "get": { - "tags": [ - "Student" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/student/{id}/assignments": { - "get": { - "tags": [ - "Student" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/student/{id}/groups": { - "get": { - "tags": [ - "Student" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/group/": { - "get": { - "tags": [ - "Group" - ], - "description": "", - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/group/{id}": { - "get": { - "tags": [ - "Group" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/group/{id}/question": { - "get": { - "tags": [ - "Group" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/assignment/": { - "get": { - "tags": [ - "Assignment" - ], - "description": "", - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/assignment/{id}": { - "get": { - "tags": [ - "Assignment" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/assignment/{id}/submissions": { - "get": { - "tags": [ - "Assignment" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/assignment/{id}/groups": { - "get": { - "tags": [ - "Assignment" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/assignment/{id}/questions": { - "get": { - "tags": [ - "Assignment" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/submission/": { - "get": { - "tags": [ - "Submission" - ], - "description": "", - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/submission/{id}": { - "get": { - "tags": [ - "Submission" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/class/": { - "get": { - "tags": [ - "Class" - ], - "description": "", - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/class/{id}": { - "get": { - "tags": [ - "Class" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/class/{id}/invitations": { - "get": { - "tags": [ - "Class" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/class/{id}/assignments": { - "get": { - "tags": [ - "Class" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/class/{id}/students": { - "get": { - "tags": [ - "Class" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/question/": { - "get": { - "tags": [ - "Question" - ], - "description": "", - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/question/{id}": { - "get": { - "tags": [ - "Question" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/question/{id}/answers": { - "get": { - "tags": [ - "Question" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/auth/config": { - "get": { - "tags": [ - "Auth" - ], - "description": "", - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/auth/testAuthenticatedOnly": { - "get": { - "tags": [ - "Auth" - ], - "description": "", - "responses": { - "200": { - "description": "OK" - } + "servers": [ + { + "url": "http://localhost:3000/", + "description": "Development server" }, - "security": [ - { - "student": [] - }, - { - "teacher": [] - } - ] - } - }, - "/auth/testStudentsOnly": { - "get": { - "tags": [ - "Auth" - ], - "description": "", - "responses": { - "200": { - "description": "OK" - } + { + "url": "https://sel2-1.ugent.be/api", + "description": "Production server" + } + ], + "paths": { + "/": { + "get": { + "description": "", + "responses": { + "200": { + "description": "OK" + } + } + } }, - "security": [ - { - "student": [] - } - ] - } - }, - "/auth/testTeachersOnly": { - "get": { - "tags": [ - "Auth" - ], - "description": "", - "responses": { - "200": { - "description": "OK" - } + "/student/": { + "get": { + "tags": ["Student"], + "description": "", + "responses": { + "200": { + "description": "OK" + } + } + } }, - "security": [ - { - "teacher": [] - } - ] - } - }, - "/theme/": { - "get": { - "tags": [ - "Theme" - ], - "description": "", - "parameters": [ - { - "name": "language", - "in": "query", - "schema": { - "type": "string" + "/student/{id}": { + "get": { + "tags": ["Student"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/student/{id}/classes": { + "get": { + "tags": ["Student"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/student/{id}/submissions": { + "get": { + "tags": ["Student"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/student/{id}/assignments": { + "get": { + "tags": ["Student"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/student/{id}/groups": { + "get": { + "tags": ["Student"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/group/": { + "get": { + "tags": ["Group"], + "description": "", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/group/{id}": { + "get": { + "tags": ["Group"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/group/{id}/question": { + "get": { + "tags": ["Group"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/assignment/": { + "get": { + "tags": ["Assignment"], + "description": "", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/assignment/{id}": { + "get": { + "tags": ["Assignment"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/assignment/{id}/submissions": { + "get": { + "tags": ["Assignment"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/assignment/{id}/groups": { + "get": { + "tags": ["Assignment"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/assignment/{id}/questions": { + "get": { + "tags": ["Assignment"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/submission/": { + "get": { + "tags": ["Submission"], + "description": "", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/submission/{id}": { + "get": { + "tags": ["Submission"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/class/": { + "get": { + "tags": ["Class"], + "description": "", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/class/{id}": { + "get": { + "tags": ["Class"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/class/{id}/invitations": { + "get": { + "tags": ["Class"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/class/{id}/assignments": { + "get": { + "tags": ["Class"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/class/{id}/students": { + "get": { + "tags": ["Class"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/question/": { + "get": { + "tags": ["Question"], + "description": "", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/question/{id}": { + "get": { + "tags": ["Question"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/question/{id}/answers": { + "get": { + "tags": ["Question"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/auth/config": { + "get": { + "tags": ["Auth"], + "description": "", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/auth/testAuthenticatedOnly": { + "get": { + "tags": ["Auth"], + "description": "", + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "student": [] + }, + { + "teacher": [] + } + ] + } + }, + "/auth/testStudentsOnly": { + "get": { + "tags": ["Auth"], + "description": "", + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "student": [] + } + ] + } + }, + "/auth/testTeachersOnly": { + "get": { + "tags": ["Auth"], + "description": "", + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "teacher": [] + } + ] + } + }, + "/theme/": { + "get": { + "tags": ["Theme"], + "description": "", + "parameters": [ + { + "name": "language", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/theme/{theme}": { + "get": { + "tags": ["Theme"], + "description": "", + "parameters": [ + { + "name": "theme", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/learningPath/": { + "get": { + "tags": ["Learning Path"], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "theme", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "search", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "language", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/learningObject/": { + "get": { + "tags": ["Learning Object"], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "language", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/learningObject/{hruid}": { + "get": { + "tags": ["Learning Object"], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "language", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "500": { + "description": "Internal Server Error" + } + } } - } - ], - "responses": { - "200": { - "description": "OK" - } } - } }, - "/theme/{theme}": { - "get": { - "tags": [ - "Theme" - ], - "description": "", - "parameters": [ - { - "name": "theme", - "in": "path", - "required": true, - "schema": { - "type": "string" + "components": { + "securitySchemes": { + "student": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "http://localhost:7080/realms/student/protocol/openid-connect/auth", + "scopes": { + "openid": "openid", + "profile": "profile", + "email": "email" + } + } + } + }, + "teacher": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "http://localhost:7080/realms/teacher/protocol/openid-connect/auth", + "scopes": { + "openid": "openid", + "profile": "profile", + "email": "email" + } + } + } } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "404": { - "description": "Not Found" - } } - } - }, - "/learningPath/": { - "get": { - "tags": [ - "Learning Path" - ], - "description": "", - "parameters": [ - { - "name": "hruid", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "theme", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "search", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "language", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "404": { - "description": "Not Found" - }, - "500": { - "description": "Internal Server Error" - } - } - } - }, - "/learningObject/": { - "get": { - "tags": [ - "Learning Object" - ], - "description": "", - "parameters": [ - { - "name": "hruid", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "full", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "language", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "400": { - "description": "Bad Request" - }, - "500": { - "description": "Internal Server Error" - } - } - } - }, - "/learningObject/{hruid}": { - "get": { - "tags": [ - "Learning Object" - ], - "description": "", - "parameters": [ - { - "name": "hruid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "language", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "400": { - "description": "Bad Request" - }, - "500": { - "description": "Internal Server Error" - } - } - } } - }, - "components": { - "securitySchemes": { - "student": { - "type": "oauth2", - "flows": { - "implicit": { - "authorizationUrl": "http://localhost:7080/realms/student/protocol/openid-connect/auth", - "scopes": { - "openid": "openid", - "profile": "profile", - "email": "email" - } - } - } - }, - "teacher": { - "type": "oauth2", - "flows": { - "implicit": { - "authorizationUrl": "http://localhost:7080/realms/teacher/protocol/openid-connect/auth", - "scopes": { - "openid": "openid", - "profile": "profile", - "email": "email" - } - } - } - } - } - } -} \ No newline at end of file +} diff --git a/frontend/src/config.ts b/frontend/src/config.ts index c34f5e3c..9feb71b3 100644 --- a/frontend/src/config.ts +++ b/frontend/src/config.ts @@ -1,5 +1,5 @@ export const apiConfig = { - baseUrl: window.location.hostname == "localhost" ? "http://localhost:3000" : window.location.origin -} + baseUrl: window.location.hostname == "localhost" ? "http://localhost:3000" : window.location.origin, +}; export const loginRoute = "/login"; diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index b9c23541..9f4779c9 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -36,7 +36,7 @@ const router = createRouter({ }, { path: "/callback", - component: CallbackPage + component: CallbackPage, }, { path: "/student/:id", diff --git a/frontend/src/services/api-client.ts b/frontend/src/services/api-client.ts index bddeeff7..21134762 100644 --- a/frontend/src/services/api-client.ts +++ b/frontend/src/services/api-client.ts @@ -1,5 +1,5 @@ import axios from "axios"; -import {apiConfig} from "@/config.ts"; +import { apiConfig } from "@/config.ts"; const apiClient = axios.create({ baseURL: apiConfig.baseUrl, diff --git a/frontend/src/services/auth/auth-config-loader.ts b/frontend/src/services/auth/auth-config-loader.ts index d8b862ad..ce8a33ca 100644 --- a/frontend/src/services/auth/auth-config-loader.ts +++ b/frontend/src/services/auth/auth-config-loader.ts @@ -1,5 +1,5 @@ import apiClient from "@/services/api-client.ts"; -import type {FrontendAuthConfig} from "@/services/auth/auth.d.ts"; +import type { FrontendAuthConfig } from "@/services/auth/auth.d.ts"; /** * Fetch the authentication configuration from the backend. @@ -22,6 +22,6 @@ export async function loadAuthConfig() { response_type: authConfig.teacher.responseType, scope: authConfig.teacher.scope, post_logout_redirect_uri: window.location.origin, - } + }, }; } diff --git a/frontend/src/services/auth/auth-service.ts b/frontend/src/services/auth/auth-service.ts index 5bd64ea1..3759718a 100644 --- a/frontend/src/services/auth/auth-service.ts +++ b/frontend/src/services/auth/auth-service.ts @@ -2,15 +2,15 @@ * Service for all authentication- and authorization-related tasks. */ -import {computed, reactive} from "vue"; -import type {AuthState, Role, UserManagersForRoles} from "@/services/auth/auth.d.ts"; -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 { computed, reactive } from "vue"; +import type { AuthState, Role, UserManagersForRoles } from "@/services/auth/auth.d.ts"; +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 router from "@/router"; -import type {AxiosError} from "axios"; +import type { AxiosError } from "axios"; const authConfig = await loadAuthConfig(); @@ -40,10 +40,12 @@ async function loadUser(): Promise { const authState = reactive({ user: null, accessToken: null, - activeRole: authStorage.getActiveRole() || null + activeRole: authStorage.getActiveRole() || null, }); -const isLoggedIn = computed(() => {return authState.user !== null}); +const isLoggedIn = computed(() => { + return authState.user !== null; +}); /** * Redirect the user to the login page where he/she can choose whether to log in as a student or teacher. @@ -70,7 +72,7 @@ async function handleLoginCallback(): Promise { if (!activeRole) { throw new Error("Login callback received, but the user is not logging in!"); } - authState.user = await userManagers[activeRole].signinCallback() || null; + authState.user = (await userManagers[activeRole].signinCallback()) || null; } /** @@ -104,29 +106,35 @@ async function logout(): Promise { } // Registering interceptor to add the authorization header to each request when the user is logged in. -apiClient.interceptors.request.use(async (reqConfig) => { - const token = authState?.user?.access_token; - if (token) { - reqConfig.headers.Authorization = `Bearer ${token}`; - } - return reqConfig; -}, (error) => {return Promise.reject(error)}); +apiClient.interceptors.request.use( + async (reqConfig) => { + const token = authState?.user?.access_token; + if (token) { + reqConfig.headers.Authorization = `Bearer ${token}`; + } + return reqConfig; + }, + (error) => { + return Promise.reject(error); + }, +); // Registering interceptor to refresh the token when a request failed because it was expired. apiClient.interceptors.response.use( - response => {return response}, - async (error: AxiosError<{message?: string}>) => { + (response) => { + return response; + }, + async (error: AxiosError<{ message?: string }>) => { if (error.response?.status === 401) { if (error.response!.data.message === "token_expired") { console.log("Access token expired, trying to refresh..."); await renewToken(); return apiClient(error.config!); // Retry the request - } // Apparently, the user got a 401 because he was not logged in yet at all. Redirect him to login. - await initiateLogin() - + } // Apparently, the user got a 401 because he was not logged in yet at all. Redirect him to login. + await initiateLogin(); } return Promise.reject(error); - } + }, ); -export default {authState, isLoggedIn, initiateLogin, loadUser, handleLoginCallback, loginAs, logout}; +export default { authState, isLoggedIn, initiateLogin, loadUser, handleLoginCallback, loginAs, logout }; diff --git a/frontend/src/services/auth/auth-storage.ts b/frontend/src/services/auth/auth-storage.ts index 26183451..0f5eb43d 100644 --- a/frontend/src/services/auth/auth-storage.ts +++ b/frontend/src/services/auth/auth-storage.ts @@ -1,4 +1,4 @@ -import type {Role} from "@/services/auth/auth.d.ts"; +import type { Role } from "@/services/auth/auth.d.ts"; export default { /** @@ -22,5 +22,5 @@ export default { */ deleteActiveRole() { localStorage.removeItem("activeRole"); - } -} + }, +}; diff --git a/frontend/src/services/auth/auth.d.ts b/frontend/src/services/auth/auth.d.ts index f9e7eb3a..8b01e408 100644 --- a/frontend/src/services/auth/auth.d.ts +++ b/frontend/src/services/auth/auth.d.ts @@ -1,22 +1,22 @@ -import {type User, UserManager} from "oidc-client-ts"; +import { type User, UserManager } from "oidc-client-ts"; export type AuthState = { - user: User | null, - accessToken: string | null, - activeRole: Role | null + user: User | null; + accessToken: string | null; + activeRole: Role | null; }; export type FrontendAuthConfig = { - student: FrontendIdpConfig, - teacher: FrontendIdpConfig + student: FrontendIdpConfig; + teacher: FrontendIdpConfig; }; export type FrontendIdpConfig = { - authority: string, - clientId: string, - scope: string, - responseType: string + authority: string; + clientId: string; + scope: string; + responseType: string; }; export type Role = "student" | "teacher"; -export type UserManagersForRoles = {student: UserManager, teacher: UserManager}; +export type UserManagersForRoles = { student: UserManager; teacher: UserManager }; diff --git a/frontend/src/views/CallbackPage.vue b/frontend/src/views/CallbackPage.vue index dd0f42c0..306dfe10 100644 --- a/frontend/src/views/CallbackPage.vue +++ b/frontend/src/views/CallbackPage.vue @@ -1,7 +1,7 @@ @@ -15,8 +15,10 @@ Welcome to the dwengo homepage
-

Hello {{auth.authState.user?.profile.name}}!

-

Your access token for the backend is: {{auth.authState.user?.access_token}}

+

Hello {{ auth.authState.user?.profile.name }}!

+

+ Your access token for the backend is: {{ auth.authState.user?.access_token }} +

Send test request diff --git a/frontend/src/views/LoginPage.vue b/frontend/src/views/LoginPage.vue index bdd851f7..1cee79fb 100644 --- a/frontend/src/views/LoginPage.vue +++ b/frontend/src/views/LoginPage.vue @@ -23,7 +23,9 @@ Login as teacher
-

You are currently logged in as {{ auth.authState.user!.profile.name }} ({{ auth.authState.activeRole }})

+

+ You are currently logged in as {{ auth.authState.user!.profile.name }} ({{ auth.authState.activeRole }}) +

Logout
diff --git a/idp/README.md b/idp/README.md index 3f3fd4ff..f67d0462 100644 --- a/idp/README.md +++ b/idp/README.md @@ -1,7 +1,9 @@ # Testdata in de IDP + De IDP in `docker-compose.yml` is zo geconfigureerd dat hij automatisch bij het starten een testconfiguratie inlaadt. Deze houdt in: -* Een realm `student` die de IDP voor leerlingen representeert. - * Hierin de gebruiker met username `testleerling1`, wachtwoord `password`. -* Een realm `teacher` die de IDP voor leerkrachten representeert. - * Hierin de gebruiker met username `testleerkracht1`, wachtwoord `password`. -* De admin-account (in de realm `master`) heeft username `admin` en wachtwoord `admin`. + +- Een realm `student` die de IDP voor leerlingen representeert. + - Hierin de gebruiker met username `testleerling1`, wachtwoord `password`. +- Een realm `teacher` die de IDP voor leerkrachten representeert. + - Hierin de gebruiker met username `testleerkracht1`, wachtwoord `password`. +- De admin-account (in de realm `master`) heeft username `admin` en wachtwoord `admin`. diff --git a/idp/student-realm.json b/idp/student-realm.json index 15cbc666..e10f6982 100644 --- a/idp/student-realm.json +++ b/idp/student-realm.json @@ -1,2062 +1,2462 @@ { - "id" : "08a7ab0a-d483-4103-a781-76013864bf50", - "realm" : "student", - "notBefore" : 0, - "defaultSignatureAlgorithm" : "RS256", - "revokeRefreshToken" : false, - "refreshTokenMaxReuse" : 0, - "accessTokenLifespan" : 300, - "accessTokenLifespanForImplicitFlow" : 900, - "ssoSessionIdleTimeout" : 1800, - "ssoSessionMaxLifespan" : 36000, - "ssoSessionIdleTimeoutRememberMe" : 0, - "ssoSessionMaxLifespanRememberMe" : 0, - "offlineSessionIdleTimeout" : 2592000, - "offlineSessionMaxLifespanEnabled" : false, - "offlineSessionMaxLifespan" : 5184000, - "clientSessionIdleTimeout" : 0, - "clientSessionMaxLifespan" : 0, - "clientOfflineSessionIdleTimeout" : 0, - "clientOfflineSessionMaxLifespan" : 0, - "accessCodeLifespan" : 60, - "accessCodeLifespanUserAction" : 300, - "accessCodeLifespanLogin" : 1800, - "actionTokenGeneratedByAdminLifespan" : 43200, - "actionTokenGeneratedByUserLifespan" : 300, - "oauth2DeviceCodeLifespan" : 600, - "oauth2DevicePollingInterval" : 5, - "enabled" : true, - "sslRequired" : "external", - "registrationAllowed" : false, - "registrationEmailAsUsername" : false, - "rememberMe" : false, - "verifyEmail" : false, - "loginWithEmailAllowed" : true, - "duplicateEmailsAllowed" : false, - "resetPasswordAllowed" : false, - "editUsernameAllowed" : false, - "bruteForceProtected" : false, - "permanentLockout" : false, - "maxTemporaryLockouts" : 0, - "bruteForceStrategy" : "MULTIPLE", - "maxFailureWaitSeconds" : 900, - "minimumQuickLoginWaitSeconds" : 60, - "waitIncrementSeconds" : 60, - "quickLoginCheckMilliSeconds" : 1000, - "maxDeltaTimeSeconds" : 43200, - "failureFactor" : 30, - "roles" : { - "realm" : [ { - "id" : "a0bb00f5-0b3a-4d57-a3fc-a3f93cbe3427", - "name" : "offline_access", - "description" : "${role_offline-access}", - "composite" : false, - "clientRole" : false, - "containerId" : "08a7ab0a-d483-4103-a781-76013864bf50", - "attributes" : { } - }, { - "id" : "b3bf9566-098c-4167-9cce-f64c720ca511", - "name" : "default-roles-student", - "description" : "${role_default-roles}", - "composite" : true, - "composites" : { - "realm" : [ "offline_access", "uma_authorization" ], - "client" : { - "account" : [ "manage-account", "view-profile" ] + "id": "08a7ab0a-d483-4103-a781-76013864bf50", + "realm": "student", + "notBefore": 0, + "defaultSignatureAlgorithm": "RS256", + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 300, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "ssoSessionIdleTimeoutRememberMe": 0, + "ssoSessionMaxLifespanRememberMe": 0, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, + "clientOfflineSessionIdleTimeout": 0, + "clientOfflineSessionMaxLifespan": 0, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "oauth2DeviceCodeLifespan": 600, + "oauth2DevicePollingInterval": 5, + "enabled": true, + "sslRequired": "external", + "registrationAllowed": false, + "registrationEmailAsUsername": false, + "rememberMe": false, + "verifyEmail": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": false, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxTemporaryLockouts": 0, + "bruteForceStrategy": "MULTIPLE", + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "roles": { + "realm": [ + { + "id": "a0bb00f5-0b3a-4d57-a3fc-a3f93cbe3427", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "08a7ab0a-d483-4103-a781-76013864bf50", + "attributes": {} + }, + { + "id": "b3bf9566-098c-4167-9cce-f64c720ca511", + "name": "default-roles-student", + "description": "${role_default-roles}", + "composite": true, + "composites": { + "realm": ["offline_access", "uma_authorization"], + "client": { + "account": ["manage-account", "view-profile"] + } + }, + "clientRole": false, + "containerId": "08a7ab0a-d483-4103-a781-76013864bf50", + "attributes": {} + }, + { + "id": "6d044f54-8ff3-4223-9e8c-771882da7a3f", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "08a7ab0a-d483-4103-a781-76013864bf50", + "attributes": {} + } + ], + "client": { + "realm-management": [ + { + "id": "f125e557-2427-4eeb-95c5-b3dadf35f9c7", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "0b06aaa3-717d-4a52-ab46-295a6571b642", + "attributes": {} + }, + { + "id": "33c7285a-7308-4752-acad-1fe59bf1c81a", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "0b06aaa3-717d-4a52-ab46-295a6571b642", + "attributes": {} + }, + { + "id": "31fb3621-62c7-43c8-af98-a4add3470fcc", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "0b06aaa3-717d-4a52-ab46-295a6571b642", + "attributes": {} + }, + { + "id": "e077c3c3-d573-494f-9cf8-34eca6603fc6", + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "manage-authorization", + "query-clients", + "manage-identity-providers", + "create-client", + "view-users", + "view-authorization", + "query-users", + "manage-users", + "view-identity-providers", + "impersonation", + "manage-realm", + "view-events", + "view-clients", + "manage-events", + "manage-clients", + "view-realm", + "query-groups", + "query-realms" + ] + } + }, + "clientRole": true, + "containerId": "0b06aaa3-717d-4a52-ab46-295a6571b642", + "attributes": {} + }, + { + "id": "8bbe59b1-7693-4274-bdde-c08f94ec3187", + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "0b06aaa3-717d-4a52-ab46-295a6571b642", + "attributes": {} + }, + { + "id": "0533162d-7dac-4ebf-87a2-7f72dad79d53", + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "realm-management": ["query-groups", "query-users"] + } + }, + "clientRole": true, + "containerId": "0b06aaa3-717d-4a52-ab46-295a6571b642", + "attributes": {} + }, + { + "id": "d4b32078-67b4-4aa8-8ddf-01a820e7b64a", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "0b06aaa3-717d-4a52-ab46-295a6571b642", + "attributes": {} + }, + { + "id": "2a48ab18-b710-41e7-8b8c-67a5cd6af685", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "0b06aaa3-717d-4a52-ab46-295a6571b642", + "attributes": {} + }, + { + "id": "d71d575f-3f21-4f4a-b9e0-2628352aac8d", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "0b06aaa3-717d-4a52-ab46-295a6571b642", + "attributes": {} + }, + { + "id": "7d3cd659-4ddd-45cd-8186-210431a25bbd", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "0b06aaa3-717d-4a52-ab46-295a6571b642", + "attributes": {} + }, + { + "id": "3dbd18ca-11dc-463d-bf8e-e7d80928a90d", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "0b06aaa3-717d-4a52-ab46-295a6571b642", + "attributes": {} + }, + { + "id": "d4a6ef1e-bf84-4bd6-8763-1b0c9997c109", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "0b06aaa3-717d-4a52-ab46-295a6571b642", + "attributes": {} + }, + { + "id": "f0eab8d7-0570-44d3-94d0-2a43906d9f09", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "0b06aaa3-717d-4a52-ab46-295a6571b642", + "attributes": {} + }, + { + "id": "0a24b91f-ef4a-4f4b-a753-1286dd59df2b", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": ["query-clients"] + } + }, + "clientRole": true, + "containerId": "0b06aaa3-717d-4a52-ab46-295a6571b642", + "attributes": {} + }, + { + "id": "b307485c-8840-4c39-ba81-fb840fa404d1", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "0b06aaa3-717d-4a52-ab46-295a6571b642", + "attributes": {} + }, + { + "id": "3719a5ed-be30-4d2c-93f5-cc6e6c0e792e", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "0b06aaa3-717d-4a52-ab46-295a6571b642", + "attributes": {} + }, + { + "id": "d4b13416-9f5e-42fb-bfdd-6489093922da", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "0b06aaa3-717d-4a52-ab46-295a6571b642", + "attributes": {} + }, + { + "id": "15ac861b-5440-4fe8-9f7d-857d75ec481d", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "0b06aaa3-717d-4a52-ab46-295a6571b642", + "attributes": {} + }, + { + "id": "f05a8e4d-90ea-41f6-887b-0b6b1ecb9cd9", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "0b06aaa3-717d-4a52-ab46-295a6571b642", + "attributes": {} + } + ], + "dwengo": [], + "security-admin-console": [], + "admin-cli": [], + "account-console": [], + "broker": [ + { + "id": "da1edd82-7479-4e9d-ad66-9a4cf739e828", + "name": "read-token", + "description": "${role_read-token}", + "composite": false, + "clientRole": true, + "containerId": "befe3d72-8102-49a6-8268-bce6def58159", + "attributes": {} + } + ], + "account": [ + { + "id": "5a3da53d-235b-4d12-b8ec-1573b13ebafc", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "b3a22454-d780-4093-8333-9be6f6cd5855", + "attributes": {} + }, + { + "id": "cbc0c1d4-487b-488c-8566-1d4537212de8", + "name": "manage-account-links", + "description": "${role_manage-account-links}", + "composite": false, + "clientRole": true, + "containerId": "b3a22454-d780-4093-8333-9be6f6cd5855", + "attributes": {} + }, + { + "id": "79b0ed8f-bf10-4b01-bb2c-e7a58d57c798", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "b3a22454-d780-4093-8333-9be6f6cd5855", + "attributes": {} + }, + { + "id": "b6aa748e-0fb0-4fa6-a0d1-3ea37c870467", + "name": "manage-account", + "description": "${role_manage-account}", + "composite": true, + "composites": { + "client": { + "account": ["manage-account-links"] + } + }, + "clientRole": true, + "containerId": "b3a22454-d780-4093-8333-9be6f6cd5855", + "attributes": {} + }, + { + "id": "ddaea6cd-ede8-49f7-9746-3a3a02fdeca5", + "name": "view-profile", + "description": "${role_view-profile}", + "composite": false, + "clientRole": true, + "containerId": "b3a22454-d780-4093-8333-9be6f6cd5855", + "attributes": {} + }, + { + "id": "061b2038-b415-4a45-89ec-7141004c0151", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "b3a22454-d780-4093-8333-9be6f6cd5855", + "attributes": {} + }, + { + "id": "95972aa1-6666-421c-8596-a91eee54b0e8", + "name": "view-groups", + "description": "${role_view-groups}", + "composite": false, + "clientRole": true, + "containerId": "b3a22454-d780-4093-8333-9be6f6cd5855", + "attributes": {} + }, + { + "id": "1cf27d94-d88d-42d3-b8f3-ede1f127ac45", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": ["view-consent"] + } + }, + "clientRole": true, + "containerId": "b3a22454-d780-4093-8333-9be6f6cd5855", + "attributes": {} + } + ] } - }, - "clientRole" : false, - "containerId" : "08a7ab0a-d483-4103-a781-76013864bf50", - "attributes" : { } - }, { - "id" : "6d044f54-8ff3-4223-9e8c-771882da7a3f", - "name" : "uma_authorization", - "description" : "${role_uma_authorization}", - "composite" : false, - "clientRole" : false, - "containerId" : "08a7ab0a-d483-4103-a781-76013864bf50", - "attributes" : { } - } ], - "client" : { - "realm-management" : [ { - "id" : "f125e557-2427-4eeb-95c5-b3dadf35f9c7", - "name" : "manage-authorization", - "description" : "${role_manage-authorization}", - "composite" : false, - "clientRole" : true, - "containerId" : "0b06aaa3-717d-4a52-ab46-295a6571b642", - "attributes" : { } - }, { - "id" : "33c7285a-7308-4752-acad-1fe59bf1c81a", - "name" : "manage-identity-providers", - "description" : "${role_manage-identity-providers}", - "composite" : false, - "clientRole" : true, - "containerId" : "0b06aaa3-717d-4a52-ab46-295a6571b642", - "attributes" : { } - }, { - "id" : "31fb3621-62c7-43c8-af98-a4add3470fcc", - "name" : "query-clients", - "description" : "${role_query-clients}", - "composite" : false, - "clientRole" : true, - "containerId" : "0b06aaa3-717d-4a52-ab46-295a6571b642", - "attributes" : { } - }, { - "id" : "e077c3c3-d573-494f-9cf8-34eca6603fc6", - "name" : "realm-admin", - "description" : "${role_realm-admin}", - "composite" : true, - "composites" : { - "client" : { - "realm-management" : [ "manage-authorization", "query-clients", "manage-identity-providers", "create-client", "view-users", "view-authorization", "query-users", "manage-users", "view-identity-providers", "impersonation", "manage-realm", "view-events", "view-clients", "manage-events", "manage-clients", "view-realm", "query-groups", "query-realms" ] - } + }, + "groups": [], + "defaultRole": { + "id": "b3bf9566-098c-4167-9cce-f64c720ca511", + "name": "default-roles-student", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "08a7ab0a-d483-4103-a781-76013864bf50" + }, + "requiredCredentials": ["password"], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpPolicyCodeReusable": false, + "otpSupportedApplications": [ + "totpAppFreeOTPName", + "totpAppGoogleName", + "totpAppMicrosoftAuthenticatorName" + ], + "localizationTexts": {}, + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": ["ES256", "RS256"], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyExtraOrigins": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": ["ES256", "RS256"], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "webAuthnPolicyPasswordlessExtraOrigins": [], + "users": [ + { + "id": "79e9a395-d7e4-48c9-a06e-702435bae290", + "username": "testleerling1", + "firstName": "Gerald", + "lastName": "Schmittinger", + "email": "Gerald.Schmittinger@UGent.be", + "emailVerified": false, + "createdTimestamp": 1740858528405, + "enabled": true, + "totp": false, + "credentials": [ + { + "id": "c31a708f-8614-4144-a25f-3e976c9035ce", + "type": "password", + "userLabel": "My password", + "createdDate": 1740858548515, + "secretData": "{\"value\":\"yDKIAbZPuVXBGk4zjiqE/YFcPDm1vjXLwTrPUrvMhXY=\",\"salt\":\"tYvjd4mhV2UWeOUssK01Cw==\",\"additionalParameters\":{}}", + "credentialData": "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" + } + ], + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": ["default-roles-student"], + "notBefore": 0, + "groups": [] + } + ], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": ["offline_access"] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": ["manage-account", "view-groups"] + } + ] + }, + "clients": [ + { + "id": "b3a22454-d780-4093-8333-9be6f6cd5855", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/student/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": ["/realms/student/account/*"], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "realm_client": "false", + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "organization", + "microprofile-jwt" + ] }, - "clientRole" : true, - "containerId" : "0b06aaa3-717d-4a52-ab46-295a6571b642", - "attributes" : { } - }, { - "id" : "8bbe59b1-7693-4274-bdde-c08f94ec3187", - "name" : "create-client", - "description" : "${role_create-client}", - "composite" : false, - "clientRole" : true, - "containerId" : "0b06aaa3-717d-4a52-ab46-295a6571b642", - "attributes" : { } - }, { - "id" : "0533162d-7dac-4ebf-87a2-7f72dad79d53", - "name" : "view-users", - "description" : "${role_view-users}", - "composite" : true, - "composites" : { - "client" : { - "realm-management" : [ "query-groups", "query-users" ] - } + { + "id": "854c221b-630c-4cc3-9365-bd254246dd69", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/student/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": ["/realms/student/account/*"], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "realm_client": "false", + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "f33b40fe-bb9e-4254-ada9-f98dd203641b", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "organization", + "microprofile-jwt" + ] }, - "clientRole" : true, - "containerId" : "0b06aaa3-717d-4a52-ab46-295a6571b642", - "attributes" : { } - }, { - "id" : "d4b32078-67b4-4aa8-8ddf-01a820e7b64a", - "name" : "view-authorization", - "description" : "${role_view-authorization}", - "composite" : false, - "clientRole" : true, - "containerId" : "0b06aaa3-717d-4a52-ab46-295a6571b642", - "attributes" : { } - }, { - "id" : "2a48ab18-b710-41e7-8b8c-67a5cd6af685", - "name" : "query-users", - "description" : "${role_query-users}", - "composite" : false, - "clientRole" : true, - "containerId" : "0b06aaa3-717d-4a52-ab46-295a6571b642", - "attributes" : { } - }, { - "id" : "d71d575f-3f21-4f4a-b9e0-2628352aac8d", - "name" : "manage-users", - "description" : "${role_manage-users}", - "composite" : false, - "clientRole" : true, - "containerId" : "0b06aaa3-717d-4a52-ab46-295a6571b642", - "attributes" : { } - }, { - "id" : "7d3cd659-4ddd-45cd-8186-210431a25bbd", - "name" : "impersonation", - "description" : "${role_impersonation}", - "composite" : false, - "clientRole" : true, - "containerId" : "0b06aaa3-717d-4a52-ab46-295a6571b642", - "attributes" : { } - }, { - "id" : "3dbd18ca-11dc-463d-bf8e-e7d80928a90d", - "name" : "view-identity-providers", - "description" : "${role_view-identity-providers}", - "composite" : false, - "clientRole" : true, - "containerId" : "0b06aaa3-717d-4a52-ab46-295a6571b642", - "attributes" : { } - }, { - "id" : "d4a6ef1e-bf84-4bd6-8763-1b0c9997c109", - "name" : "manage-realm", - "description" : "${role_manage-realm}", - "composite" : false, - "clientRole" : true, - "containerId" : "0b06aaa3-717d-4a52-ab46-295a6571b642", - "attributes" : { } - }, { - "id" : "f0eab8d7-0570-44d3-94d0-2a43906d9f09", - "name" : "view-events", - "description" : "${role_view-events}", - "composite" : false, - "clientRole" : true, - "containerId" : "0b06aaa3-717d-4a52-ab46-295a6571b642", - "attributes" : { } - }, { - "id" : "0a24b91f-ef4a-4f4b-a753-1286dd59df2b", - "name" : "view-clients", - "description" : "${role_view-clients}", - "composite" : true, - "composites" : { - "client" : { - "realm-management" : [ "query-clients" ] - } + { + "id": "9449aa8b-d5cc-4b9f-bb01-be1e5a896f2f", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "realm_client": "false", + "client.use.lightweight.access.token.enabled": "true" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "organization", + "microprofile-jwt" + ] }, - "clientRole" : true, - "containerId" : "0b06aaa3-717d-4a52-ab46-295a6571b642", - "attributes" : { } - }, { - "id" : "b307485c-8840-4c39-ba81-fb840fa404d1", - "name" : "manage-events", - "description" : "${role_manage-events}", - "composite" : false, - "clientRole" : true, - "containerId" : "0b06aaa3-717d-4a52-ab46-295a6571b642", - "attributes" : { } - }, { - "id" : "3719a5ed-be30-4d2c-93f5-cc6e6c0e792e", - "name" : "manage-clients", - "description" : "${role_manage-clients}", - "composite" : false, - "clientRole" : true, - "containerId" : "0b06aaa3-717d-4a52-ab46-295a6571b642", - "attributes" : { } - }, { - "id" : "d4b13416-9f5e-42fb-bfdd-6489093922da", - "name" : "view-realm", - "description" : "${role_view-realm}", - "composite" : false, - "clientRole" : true, - "containerId" : "0b06aaa3-717d-4a52-ab46-295a6571b642", - "attributes" : { } - }, { - "id" : "15ac861b-5440-4fe8-9f7d-857d75ec481d", - "name" : "query-groups", - "description" : "${role_query-groups}", - "composite" : false, - "clientRole" : true, - "containerId" : "0b06aaa3-717d-4a52-ab46-295a6571b642", - "attributes" : { } - }, { - "id" : "f05a8e4d-90ea-41f6-887b-0b6b1ecb9cd9", - "name" : "query-realms", - "description" : "${role_query-realms}", - "composite" : false, - "clientRole" : true, - "containerId" : "0b06aaa3-717d-4a52-ab46-295a6571b642", - "attributes" : { } - } ], - "dwengo" : [ ], - "security-admin-console" : [ ], - "admin-cli" : [ ], - "account-console" : [ ], - "broker" : [ { - "id" : "da1edd82-7479-4e9d-ad66-9a4cf739e828", - "name" : "read-token", - "description" : "${role_read-token}", - "composite" : false, - "clientRole" : true, - "containerId" : "befe3d72-8102-49a6-8268-bce6def58159", - "attributes" : { } - } ], - "account" : [ { - "id" : "5a3da53d-235b-4d12-b8ec-1573b13ebafc", - "name" : "view-consent", - "description" : "${role_view-consent}", - "composite" : false, - "clientRole" : true, - "containerId" : "b3a22454-d780-4093-8333-9be6f6cd5855", - "attributes" : { } - }, { - "id" : "cbc0c1d4-487b-488c-8566-1d4537212de8", - "name" : "manage-account-links", - "description" : "${role_manage-account-links}", - "composite" : false, - "clientRole" : true, - "containerId" : "b3a22454-d780-4093-8333-9be6f6cd5855", - "attributes" : { } - }, { - "id" : "79b0ed8f-bf10-4b01-bb2c-e7a58d57c798", - "name" : "delete-account", - "description" : "${role_delete-account}", - "composite" : false, - "clientRole" : true, - "containerId" : "b3a22454-d780-4093-8333-9be6f6cd5855", - "attributes" : { } - }, { - "id" : "b6aa748e-0fb0-4fa6-a0d1-3ea37c870467", - "name" : "manage-account", - "description" : "${role_manage-account}", - "composite" : true, - "composites" : { - "client" : { - "account" : [ "manage-account-links" ] - } + { + "id": "befe3d72-8102-49a6-8268-bce6def58159", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "realm_client": "true" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "organization", + "microprofile-jwt" + ] }, - "clientRole" : true, - "containerId" : "b3a22454-d780-4093-8333-9be6f6cd5855", - "attributes" : { } - }, { - "id" : "ddaea6cd-ede8-49f7-9746-3a3a02fdeca5", - "name" : "view-profile", - "description" : "${role_view-profile}", - "composite" : false, - "clientRole" : true, - "containerId" : "b3a22454-d780-4093-8333-9be6f6cd5855", - "attributes" : { } - }, { - "id" : "061b2038-b415-4a45-89ec-7141004c0151", - "name" : "view-applications", - "description" : "${role_view-applications}", - "composite" : false, - "clientRole" : true, - "containerId" : "b3a22454-d780-4093-8333-9be6f6cd5855", - "attributes" : { } - }, { - "id" : "95972aa1-6666-421c-8596-a91eee54b0e8", - "name" : "view-groups", - "description" : "${role_view-groups}", - "composite" : false, - "clientRole" : true, - "containerId" : "b3a22454-d780-4093-8333-9be6f6cd5855", - "attributes" : { } - }, { - "id" : "1cf27d94-d88d-42d3-b8f3-ede1f127ac45", - "name" : "manage-consent", - "description" : "${role_manage-consent}", - "composite" : true, - "composites" : { - "client" : { - "account" : [ "view-consent" ] - } + { + "id": "714243ae-72cc-4c26-842a-047357b5919a", + "clientId": "dwengo", + "name": "Dwengo", + "description": "", + "rootUrl": "http://localhost:5173", + "adminUrl": "http://localhost:5173", + "baseUrl": "/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-jwt", + "redirectUris": [ + "urn:ietf:wg:oauth:2.0:oob", + "http://localhost:5173/*", + "http://localhost:5173", + "http://localhost:3000/api-docs/oauth2-redirect.html" + ], + "webOrigins": ["+"], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": true, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": true, + "protocol": "openid-connect", + "attributes": { + "realm_client": "false", + "oidc.ciba.grant.enabled": "false", + "client.secret.creation.time": "1740860818", + "backchannel.logout.session.required": "true", + "token.endpoint.auth.signing.alg": "RS256", + "post.logout.redirect.uris": "+", + "frontchannel.logout.session.required": "true", + "oauth2.device.authorization.grant.enabled": "false", + "display.on.consent.screen": "false", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "organization", + "microprofile-jwt" + ] }, - "clientRole" : true, - "containerId" : "b3a22454-d780-4093-8333-9be6f6cd5855", - "attributes" : { } - } ] + { + "id": "0b06aaa3-717d-4a52-ab46-295a6571b642", + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "realm_client": "true" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "organization", + "microprofile-jwt" + ] + }, + { + "id": "dfc7248c-3794-4e3b-aed2-3ee553cd0feb", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/student/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": ["/admin/student/console/*"], + "webOrigins": ["+"], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "realm_client": "false", + "client.use.lightweight.access.token.enabled": "true", + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "9e9ff295-30c9-43f1-a11a-773724709c07", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "organization", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "0721b27a-284f-4e6d-af70-b6f190ebdcd4", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${emailScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "d256bdc1-8983-41e0-b8fa-fcf45653045e", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "651c2415-db30-40ed-bdef-745b6ea744ed", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "573f6eea-7626-44fe-9855-50f15c3939ba", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "consent.screen.text": "", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "3489c748-3cc7-4350-9351-2955fc7084ba", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "00afe548-c677-4595-8478-16f752c2713a", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "1448ed2b-ec1d-4bf4-a8b7-00cb85459289", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${addressScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "12d491b6-5d74-4168-ac5c-517ebc2f1de4", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "introspection.token.claim": "true", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "id": "52223fb1-9651-4cdf-8317-a1301d4042f7", + "name": "organization", + "description": "Additional claims about the organization a subject belongs to", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${organizationScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "dccc4214-ece6-4235-8119-ee8cb954c29a", + "name": "organization", + "protocol": "openid-connect", + "protocolMapper": "oidc-organization-membership-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "organization", + "jsonType.label": "String", + "multivalued": "true" + } + } + ] + }, + { + "id": "8be22542-e327-4a25-8265-a34a29607d1b", + "name": "service_account", + "description": "Specific scope for a client enabled for service accounts", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "cf89064a-0af3-4a4b-a838-3528a8f4d780", + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String" + } + }, + { + "id": "dc0f77e6-cc20-4c0a-baf3-f45046d749d1", + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "client_id", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "client_id", + "jsonType.label": "String" + } + }, + { + "id": "d63fd29a-3613-4529-a8e4-3a7d7e9f5802", + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "d9079603-62b7-4680-9d01-950daae75d6b", + "name": "saml_organization", + "description": "Organization Membership", + "protocol": "saml", + "attributes": { + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "d826fc58-b006-49ad-93dc-a76700e800df", + "name": "organization", + "protocol": "saml", + "protocolMapper": "saml-organization-membership-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "171d8267-87da-4a4b-9346-d901d470248b", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${phoneScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "f8bb18d4-af9d-49b0-a61f-cc81887870cd", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + }, + { + "id": "88a2c658-9b61-40a2-abd5-69c501286031", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + } + ] + }, + { + "id": "ea3b84ac-a91f-4a3d-be4e-893e11eaf4a1", + "name": "acr", + "description": "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "152d66d4-524f-47f1-a592-be3a0c043a4f", + "name": "acr loa level", + "protocol": "openid-connect", + "protocolMapper": "oidc-acr-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "2fc1ad0d-1065-4196-8d1b-c61525c9425d", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "9d537486-f6bf-4856-91fc-ca3acaa78814", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + }, + { + "id": "55425438-4111-47a0-9a36-fec9dbbc6a8a", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "multivalued": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "0d186f4e-ef6d-4fbc-9593-081e0d5ad171", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${profileScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "bb8bb550-2db6-4631-97dc-1d115d0e3034", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "id": "c942089b-2898-4052-a64d-85b61e27aaa4", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "5ff3a9ca-7036-458c-b0dc-41216292d210", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "long" + } + }, + { + "id": "41f93d62-4074-4373-a270-9bdf1e298cb5", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "ffec7d63-0f78-41ea-8023-6c7c64661b34", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "id": "4a514ae7-d29f-4979-8df9-a97b36a81a96", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "286e349b-cb9f-41b1-b9dc-d787f13e9d99", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "f5177603-55b1-4abe-aee6-b1e5a05e37f6", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "a31114d7-05fc-40c1-9ea8-6977f6f0bec5", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "8884be77-648d-4083-b0cf-57130162c8dc", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + }, + { + "id": "61840434-c79f-455a-a914-117977197304", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "id": "1f40ff0b-1664-4259-846b-ab707c76d33b", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "8534d400-8a81-4ae3-b51f-78b93e5a2045", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "82a0e240-0824-41b9-b6e8-856a72d1e930", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "a5cedc85-d9e9-42e1-9ea3-ff37d21d5e27", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "19009128-590f-4bc9-80de-c9ba4aae822d", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "3b6bb88b-c833-4bb5-9bd0-95831aa2ad0d", + "name": "basic", + "description": "OpenID Connect scope for add all basic claims to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "ce925803-aec2-47cb-a3b9-4bef12c80367", + "name": "sub", + "protocol": "openid-connect", + "protocolMapper": "oidc-sub-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + }, + { + "id": "58729b3a-3816-460e-bf2e-d0d2206c1830", + "name": "auth_time", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "AUTH_TIME", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "auth_time", + "jsonType.label": "long" + } + } + ] + }, + { + "id": "7aa2d936-3edb-45e5-bae0-b4a618d06371", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "consent.screen.text": "${rolesScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "a9d1e8e2-ca10-4904-8a42-7708b0bfdefa", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "1f217073-ad43-483b-b0d5-f3ca4c74282f", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "61b0a069-8b67-4692-bcca-66a197b230eb", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "saml_organization", + "profile", + "email", + "roles", + "web-origins", + "acr", + "basic" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone", + "microprofile-jwt", + "organization" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "referrerPolicy": "no-referrer", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": {}, + "eventsEnabled": false, + "eventsListeners": ["jboss-logging"], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [], + "identityProviderMappers": [], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "9eac5531-7f25-493f-a721-6c5e65cd34c2", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": ["200"] + } + }, + { + "id": "d9319a22-4c67-4b08-822f-4162a1ee01bc", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": ["true"] + } + }, + { + "id": "21456c8e-7f6b-4e49-a3e1-bea7f900e2fb", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "4872e99b-b55b-4e13-8a93-63e853289cac", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "a118a194-09f5-435d-9d4b-363813413167", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": ["true"], + "client-uris-must-match": ["true"] + } + }, + { + "id": "e32b1e26-6571-4b0c-a205-0fbb3de44384", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": ["true"] + } + }, + { + "id": "9dbe6752-9978-42a3-9210-9ec166140de2", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-usermodel-attribute-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-address-mapper", + "saml-role-list-mapper", + "saml-user-property-mapper", + "oidc-usermodel-property-mapper", + "oidc-full-name-mapper", + "saml-user-attribute-mapper" + ] + } + }, + { + "id": "7027b3f4-d877-4814-ac78-f1edb8eb89b0", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-role-list-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-address-mapper", + "saml-user-property-mapper", + "oidc-usermodel-attribute-mapper", + "saml-user-attribute-mapper", + "oidc-usermodel-property-mapper", + "oidc-full-name-mapper" + ] + } + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "819cfc66-a997-4747-9d90-a7f0c09774bf", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "kid": ["eb74df73-3f34-457d-95c7-5ad909107703"], + "secret": ["1K8IJiDODmotHJPStrXhtA"], + "priority": ["100"] + } + }, + { + "id": "299857cd-52a4-4981-8171-02e7d8f12960", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "privateKey": [ + "MIIEpAIBAAKCAQEA1MRmAT/yImkVfPMBxC0QHdC4DQfuWUTjKeEku+gMI9jX5ChUzzzVugcvZWmxBNcvOz7p6R8EdPllZKIwFSH5WvQ1w1VIgQwIlYfpi/pknfftLd66MI2fXrQK19dRTeQRivEf39GTfBQ2Xc7y1q7zbMo5TVxATJ3DgPi13dYO7zVPpGTiQQeYiezlcBedyGe4cS1g6oBoaVif1QPY1Ni2vEjJhczNMGI408tIFws8G04Tlno814nT0ysdflUSGcRUku41NtfM9hr57LQ459sGYho8Pn11lDuiUWkomJv0y3GJ1wFBvQbDvI+6QvEdFu0GxShrlcORrNmj3BwOOLhB7wIDAQABAoIBAA6zrXq7oO8YxMfYANC97mWpBPa9jA42EN5VdNTZIXGeq7hTwxx4zynmEjPXPEih190nqUEXCBdPHl74SAtFyDWtN0PSkkp8euFePViTSj2SIpzvTX1KY+9G0JL+iVsw/bdUlwe/swm5WdJcmPIVr7NeO9xpGfZRVm+EgAieoHSN4Z7g20wLbVz1fya+6O5Hy+IGezamIA4tchk+4hyiVpSh2TcdjkJJZWOlHKPkwWU/MYQbJibuea5jLoWA39NIqV2l5GT0SoCbffGJNb9CMTTGmXoK5zNwHhG+M0a4eP1vbFDLaoDne86JySmTdv/WrTFFa3veelw2K8PHDybuB70CgYEA/gbxqLZYkJcEpqsjM/XcISFJ09icJLKl5r2l/Dm4Qq587QniQYribX/PPLfDhgVwPByQe3rccq9FoiILycTdIwgSMTsg5fzvbLJTqMAcl2r0zJgHVIDc6iXnytuE0FffKN0kSKL1C4d6n6vKoCGvOcZoXK5jxgzpY8lasvKxhCsCgYEA1mtr7CDYY3qPmTu4/Uz6cFgX8RDMZZ11AQQXNMsKHIu5C4xLeYmJMlpt0y4h52/NWRzh2svdw3SEZTCfP1WVC7StfP8KD8QdwVkQlY5EGkiz9uRtEgwk8chkOTIm2JedeRL6YWlTgnH9PIuGq84OOnEbFjVN3Lbx3N1QuQfVA00CgYEAybA1uuBcXSCqfrIuVxkD2AIYHe1DvBdjhVpaKXKii78CTSmlzKg6svnhTrIQuZ4jyHZdeMzJrvzeaqZheaemdCP6XcA2lKRIbKMBrWAq00YGa1LhrwRJYlcKPJQiVVEPS+CY6FsJ+Edu4suBK7bS6ypOvhdv/FVQEPxT2PS8YNUCgYEAxwJ+8XNuw63ud9+Zi+gVjY4F8qWPwESLYz0DuOk2YlZAknpNVumTYBvUUSxBJYh8RFhtO+D53D5Z331oYKUzJ+EzII+qLAXvRBRBMz4O8YJHHkDXBugkphBDDV8B9QeLjeNSZnUWoDziOH6bqPwf8pgl9s/Ui6V1CHSVRpcBWwUCgYA2kMgu7qS5kLtUWySPzW4nTKwhN+HFTIbRrNrECxXmxroigTEyfBFuNR5QaeYYrAtqgY1m5Lev//2GnWM7dAr7hewj6qfGszrvegHsqMs4cakVqEOtbrWxL+WtWPaIdjJ+x7ZoMnZxZDg3ysemybNHHwSyBsp1TDc+glzmMtJtLA==" + ], + "keyUse": ["SIG"], + "certificate": [ + "MIICnTCCAYUCBgGVUbFIeTANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdzdHVkZW50MB4XDTI1MDMwMTEyMzAyN1oXDTM1MDMwMTEyMzIwN1owEjEQMA4GA1UEAwwHc3R1ZGVudDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANTEZgE/8iJpFXzzAcQtEB3QuA0H7llE4ynhJLvoDCPY1+QoVM881boHL2VpsQTXLzs+6ekfBHT5ZWSiMBUh+Vr0NcNVSIEMCJWH6Yv6ZJ337S3eujCNn160CtfXUU3kEYrxH9/Rk3wUNl3O8tau82zKOU1cQEydw4D4td3WDu81T6Rk4kEHmIns5XAXnchnuHEtYOqAaGlYn9UD2NTYtrxIyYXMzTBiONPLSBcLPBtOE5Z6PNeJ09MrHX5VEhnEVJLuNTbXzPYa+ey0OOfbBmIaPD59dZQ7olFpKJib9MtxidcBQb0Gw7yPukLxHRbtBsUoa5XDkazZo9wcDji4Qe8CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAedqvKOBsz4IKKzkWHIQnN5H8dQKnuPUMdWewOwmMGIUdBU9k6aS+y+BB7mugF/Hnr8Lw5d2AHwVLj2VyP4Pq0d2My3Ihxi0vr6sSfxVHuD9y/a7FxDGVTkCvmy5DOmpF/kdNnL9xG5ZivHaucnrIHHGMcQCdbWAaac0qPZihv9pdMZFMtI3aiBO5jVJ7KP8iLNKsshg60mxCOPzauMVXi+rqqqhGAgMKAL4hjjvdIKTLWwmthnmAlGqlTk/7H82hS9aKygufXszXWdFAYhX/r8/hjyc+6zJUvkG20uRWnkR35gya7jQoZ2O6OvkQf0mgSvzgIP3xoYV2uKYD03wINg==" + ], + "priority": ["100"] + } + }, + { + "id": "3d6bfeeb-fa86-435e-8c39-6f547a0f4a38", + "name": "hmac-generated-hs512", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "kid": ["176e970f-5915-4d27-8233-8fab6d7ad947"], + "secret": [ + "sXeOdtyIPpH_kcZWikHFjTur9yWok0QUwKi95l8wHp6kTVX9vhoZL2siNHRoFnn8tFgT4JZbR0bMsD57qAXlmVjA830Ny_GZdhL_PFWQh7JYMEJrl-1nyLy_SReQXRtq_q9tKUafUZqeYSKBlUYZ7D4jNRJ4-uniq80Ger-4ee0" + ], + "priority": ["100"], + "algorithm": ["HS512"] + } + }, + { + "id": "df1247b5-041e-4ae8-b7fc-26c4b6f5ff67", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "privateKey": [ + "MIIEpAIBAAKCAQEAq0piY/PaZh1IX46e0G6tCPtfRx7Q8zGslOFSBLR9PNAdSlBpYiV9kOpN1kTK2Sca5j4yyO9HAFK5j+zh/cy8SsP1iyuI0sCPG7NMKV1pP5Y753wTC0lTq8z16bvXPvPYrPRKgGDmU7Ww0/WD6/P+z9Li/+ujFHzzzfoPuQvbBhma6A4oadsC+zun/mWCyiD14mB+X00BeAIsxKJZ/Sd+U4lMkkvmpoXyx61xK+j48EAZ18u7FprlvUjgGzzAmm/K6O/fHPIw5eViVly7aj5gjh67ntSuZArVtrxy/Py5V4hkSO8guXKqNz3liJvLbFCqqpfTR/0duArZR0xcnaGc6wIDAQABAoIBAFRuu5YaXxbDq2eS5RzH2Vpakin7+jJOU4wljujL0QnXagC2J2QeJ8l1fT23tieZO4yvrxfVvnFd1aMouHMC5vORqWja4jxEd6ZHWKzxIw6ZbtjZk4eWMvy18KewlFavGyiR2GF0okQ0BMBOPqNhp8JoaMWOsNnKB+GJuBNWUTWtPWNQlbaeI+uIgFywrvZOcQuWqU+9Y5rQa7oKZisufu+z0vd9XyvjXQ/Thnuu9/k1m88EMAMS63zwfIOZm35PPqh1/6aBBcRWquT1X2S8g2hwmMLZJgU91yKtQcIujHXAcvxeK72/dcm8NU6AxM+8aj+821TvcNzJi7he5SGcin0CgYEA4cuxwqRixXz6yDcTv9GpJMULbJGjA+Qf/iSfT+ftBeKbnKgZGzHwOCTu5DMkag3BPjclut4sEt3QPf1cFv5vZvdkOnPeaFxrtoMhSz8ssh8qaOsObCwicel1zdPVTmMw7YzEZV14fdIq3lkHsLy2uWa0imRH0l4xTccmsJiPtU8CgYEAwjQtspxOejCyME+M+hcU3RelD6kaMjICuWGJj8g0OpqdHM7iNVJq78fOlWjntt/ydzfOXVMMVh4AG8dAvlc86iwwsBRsJPVrrrRoSNuAwFbjKisbjlnPbqyclHfUsyQitj19tp//ExH7JaBibzKd6KhqFuQTE1iYLs2mFQAz76UCgYEAsNLu64oGm7frQP345mAPgO8aqjRHIBX3g/Q0GsR61wAGcyElQCnUgHNT7burSa5p5goT7wpsI343xUPzaUJqBY25nRj+VGYEKFL6sM3Rd9B2SuHBUq8hbmmwyraYtiFxwKZbazJO2OHMloHMRvkSc5Dd0/8CS9ld7RYH04Y2DHsCgYEAoKXTK44baP7BWC9mOjc/vgjiNQs4rU8ra7igt7zwX44o63zEKUHNTh7l6DiIfYHRrAcRAahCazaT9makSxAVRs1ZVT7/mq8d7b41Chfx8KmvbuGMAPyQGEhXmoVqAOqigEhrptfBhD/6lkyPQNcJQz2VzOvMT9OYyBa8DWFGlTUCgYAfModz6g2HsYYr37/0ByXHKL0WQQtuAlZzCY9GuDLEok7QLFI/E+bdOHos3goW72Iswo/SO7inlW4S3gojuy+zZhwCO31T9p2Z0Yn0tDK3fkUO32flOLwxCZA99pKkIul3svl6643GqSD1feybmbYRtqoPCTSKSE9vI9T9DkBTvA==" + ], + "keyUse": ["ENC"], + "certificate": [ + "MIICnTCCAYUCBgGVUbFItzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdzdHVkZW50MB4XDTI1MDMwMTEyMzAyN1oXDTM1MDMwMTEyMzIwN1owEjEQMA4GA1UEAwwHc3R1ZGVudDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKtKYmPz2mYdSF+OntBurQj7X0ce0PMxrJThUgS0fTzQHUpQaWIlfZDqTdZEytknGuY+MsjvRwBSuY/s4f3MvErD9YsriNLAjxuzTCldaT+WO+d8EwtJU6vM9em71z7z2Kz0SoBg5lO1sNP1g+vz/s/S4v/roxR88836D7kL2wYZmugOKGnbAvs7p/5lgsog9eJgfl9NAXgCLMSiWf0nflOJTJJL5qaF8setcSvo+PBAGdfLuxaa5b1I4Bs8wJpvyujv3xzyMOXlYlZcu2o+YI4eu57UrmQK1ba8cvz8uVeIZEjvILlyqjc95Yiby2xQqqqX00f9HbgK2UdMXJ2hnOsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAHfum0Ymw/qHTpjNeJjJyh5A1N4Z58m/PjuCXafIdDDjuAYpB3/M4bTGZRUvEvv2RuBNv3rONvMR8dRrZwio0/T0aEXnHrEAaCSfVcMy1To8TGGOzgtPMub4YCqXLMCwW5cwIbqT3P58HEqsqEbv7Zp4LtLYZBYXWDF8vM4zEn3CPYxuxRPKlrBUynRKYcwN7+/dbhJKiARpPMIZ5viGbjaTnNE/d/VFdv1q5xm3ItYnShDyJ0REGN18sWleLI6qkW0X22Gcjn38fWjiXDnF0HQYzC2UzMcEo/iLfPxTKbJnc+PPmnszfmCh7mWs5xVGfMOz/Oy8HI121x1ZSriRktA==" + ], + "priority": ["100"], + "algorithm": ["RSA-OAEP"] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "f7d1108f-7994-47e5-81e9-1a88cdbe545c", + "alias": "Account verification options", + "description": "Method with which to verity the existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-email-verification", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false + } + ] + }, + { + "id": "cf40a5d3-bec8-4aef-9658-1b88c6cec561", + "alias": "Browser - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "6820625f-5bb5-4fa2-8539-26a8568265c1", + "alias": "Browser - Conditional Organization", + "description": "Flow to determine if the organization identity-first login is to be used", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "organization", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "66d5e52e-592e-4cef-bfa0-512e90b609ec", + "alias": "Direct Grant - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "b5bed405-b5f2-4839-861c-612501e4c412", + "alias": "First Broker Login - Conditional Organization", + "description": "Flow to determine if the authenticator that adds organization members is to be used", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "idp-add-organization-member", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "dd786e24-e822-43ec-be03-29874eb73737", + "alias": "First broker login - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "8751572f-623e-4bdc-a02c-e92c15a91143", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Account verification options", + "userSetupAllowed": false + } + ] + }, + { + "id": "61efadf2-a54e-4071-b8c9-83e094525051", + "alias": "Organization", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 10, + "autheticatorFlow": true, + "flowAlias": "Browser - Conditional Organization", + "userSetupAllowed": false + } + ] + }, + { + "id": "b99c3a7a-8ef7-46b1-b8a1-cb51f8a6e725", + "alias": "Reset - Conditional OTP", + "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "a3bfc2e4-af67-4d3e-851f-3c58bf32be83", + "alias": "User creation or linking", + "description": "Flow for the existing/non-existing user alternatives", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false + } + ] + }, + { + "id": "4cc3bf25-d1b7-43a6-8619-5ed5f2d65aed", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "First broker login - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "4e5564ce-87da-4b25-8dcb-062216ceaa8d", + "alias": "browser", + "description": "Browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "identity-provider-redirector", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 25, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 26, + "autheticatorFlow": true, + "flowAlias": "Organization", + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "forms", + "userSetupAllowed": false + } + ] + }, + { + "id": "def90462-5831-4856-b186-05df9e640bbb", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-secret-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-x509", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 40, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "f8c9010d-f197-417b-bda1-2993e1a73a21", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "Direct Grant - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "0fb9e2a4-ea0d-453f-a1fe-f000c849fd66", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "79a9efc4-1279-4093-8914-92f4e0b02bb4", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "User creation or linking", + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 50, + "autheticatorFlow": true, + "flowAlias": "First Broker Login - Conditional Organization", + "userSetupAllowed": false + } + ] + }, + { + "id": "f855b3a1-6612-4528-94bc-d0793bfda561", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Browser - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "fb84970b-6f04-4849-a385-792e17c1b8ce", + "alias": "registration", + "description": "Registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": true, + "flowAlias": "registration form", + "userSetupAllowed": false + } + ] + }, + { + "id": "fcdfd4d4-1c04-487d-aa7c-85e136814274", + "alias": "registration form", + "description": "Registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-password-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 50, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-recaptcha-action", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 60, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-terms-and-conditions", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 70, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "306d8f7d-c12a-46cb-9a68-c6c3f1622f57", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-credential-email", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 40, + "autheticatorFlow": true, + "flowAlias": "Reset - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "35a54b09-ff8c-46c4-9f04-1efbb153276c", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "fc1b82d7-593d-4906-a4d9-13220b66b7ce", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "a90543f4-7da7-43bc-8737-7e58dd190014", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "priority": 10, + "config": {} + }, + { + "alias": "TERMS_AND_CONDITIONS", + "name": "Terms and Conditions", + "providerId": "TERMS_AND_CONDITIONS", + "enabled": false, + "defaultAction": false, + "priority": 20, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "priority": 30, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 40, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 50, + "config": {} + }, + { + "alias": "delete_account", + "name": "Delete Account", + "providerId": "delete_account", + "enabled": false, + "defaultAction": false, + "priority": 60, + "config": {} + }, + { + "alias": "webauthn-register", + "name": "Webauthn Register", + "providerId": "webauthn-register", + "enabled": true, + "defaultAction": false, + "priority": 70, + "config": {} + }, + { + "alias": "webauthn-register-passwordless", + "name": "Webauthn Register Passwordless", + "providerId": "webauthn-register-passwordless", + "enabled": true, + "defaultAction": false, + "priority": 80, + "config": {} + }, + { + "alias": "VERIFY_PROFILE", + "name": "Verify Profile", + "providerId": "VERIFY_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 90, + "config": {} + }, + { + "alias": "delete_credential", + "name": "Delete Credential", + "providerId": "delete_credential", + "enabled": true, + "defaultAction": false, + "priority": 100, + "config": {} + }, + { + "alias": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, + "defaultAction": false, + "priority": 1000, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "firstBrokerLoginFlow": "first broker login", + "attributes": { + "cibaBackchannelTokenDeliveryMode": "poll", + "cibaExpiresIn": "120", + "cibaAuthRequestedUserHint": "login_hint", + "oauth2DeviceCodeLifespan": "600", + "oauth2DevicePollingInterval": "5", + "parRequestUriLifespan": "60", + "cibaInterval": "5", + "realmReusableOtpCode": "false" + }, + "keycloakVersion": "26.1.3", + "userManagedAccessAllowed": false, + "organizationsEnabled": false, + "verifiableCredentialsEnabled": false, + "adminPermissionsEnabled": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] } - }, - "groups" : [ ], - "defaultRole" : { - "id" : "b3bf9566-098c-4167-9cce-f64c720ca511", - "name" : "default-roles-student", - "description" : "${role_default-roles}", - "composite" : true, - "clientRole" : false, - "containerId" : "08a7ab0a-d483-4103-a781-76013864bf50" - }, - "requiredCredentials" : [ "password" ], - "otpPolicyType" : "totp", - "otpPolicyAlgorithm" : "HmacSHA1", - "otpPolicyInitialCounter" : 0, - "otpPolicyDigits" : 6, - "otpPolicyLookAheadWindow" : 1, - "otpPolicyPeriod" : 30, - "otpPolicyCodeReusable" : false, - "otpSupportedApplications" : [ "totpAppFreeOTPName", "totpAppGoogleName", "totpAppMicrosoftAuthenticatorName" ], - "localizationTexts" : { }, - "webAuthnPolicyRpEntityName" : "keycloak", - "webAuthnPolicySignatureAlgorithms" : [ "ES256", "RS256" ], - "webAuthnPolicyRpId" : "", - "webAuthnPolicyAttestationConveyancePreference" : "not specified", - "webAuthnPolicyAuthenticatorAttachment" : "not specified", - "webAuthnPolicyRequireResidentKey" : "not specified", - "webAuthnPolicyUserVerificationRequirement" : "not specified", - "webAuthnPolicyCreateTimeout" : 0, - "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, - "webAuthnPolicyAcceptableAaguids" : [ ], - "webAuthnPolicyExtraOrigins" : [ ], - "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", - "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256", "RS256" ], - "webAuthnPolicyPasswordlessRpId" : "", - "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", - "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", - "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified", - "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified", - "webAuthnPolicyPasswordlessCreateTimeout" : 0, - "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, - "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], - "webAuthnPolicyPasswordlessExtraOrigins" : [ ], - "users" : [ { - "id" : "79e9a395-d7e4-48c9-a06e-702435bae290", - "username" : "testleerling1", - "firstName" : "Gerald", - "lastName" : "Schmittinger", - "email" : "Gerald.Schmittinger@UGent.be", - "emailVerified" : false, - "createdTimestamp" : 1740858528405, - "enabled" : true, - "totp" : false, - "credentials" : [ { - "id" : "c31a708f-8614-4144-a25f-3e976c9035ce", - "type" : "password", - "userLabel" : "My password", - "createdDate" : 1740858548515, - "secretData" : "{\"value\":\"yDKIAbZPuVXBGk4zjiqE/YFcPDm1vjXLwTrPUrvMhXY=\",\"salt\":\"tYvjd4mhV2UWeOUssK01Cw==\",\"additionalParameters\":{}}", - "credentialData" : "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" - } ], - "disableableCredentialTypes" : [ ], - "requiredActions" : [ ], - "realmRoles" : [ "default-roles-student" ], - "notBefore" : 0, - "groups" : [ ] - } ], - "scopeMappings" : [ { - "clientScope" : "offline_access", - "roles" : [ "offline_access" ] - } ], - "clientScopeMappings" : { - "account" : [ { - "client" : "account-console", - "roles" : [ "manage-account", "view-groups" ] - } ] - }, - "clients" : [ { - "id" : "b3a22454-d780-4093-8333-9be6f6cd5855", - "clientId" : "account", - "name" : "${client_account}", - "rootUrl" : "${authBaseUrl}", - "baseUrl" : "/realms/student/account/", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ "/realms/student/account/*" ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "standardFlowEnabled" : true, - "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : false, - "serviceAccountsEnabled" : false, - "publicClient" : true, - "frontchannelLogout" : false, - "protocol" : "openid-connect", - "attributes" : { - "realm_client" : "false", - "post.logout.redirect.uris" : "+" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "organization", "microprofile-jwt" ] - }, { - "id" : "854c221b-630c-4cc3-9365-bd254246dd69", - "clientId" : "account-console", - "name" : "${client_account-console}", - "rootUrl" : "${authBaseUrl}", - "baseUrl" : "/realms/student/account/", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ "/realms/student/account/*" ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "standardFlowEnabled" : true, - "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : false, - "serviceAccountsEnabled" : false, - "publicClient" : true, - "frontchannelLogout" : false, - "protocol" : "openid-connect", - "attributes" : { - "realm_client" : "false", - "post.logout.redirect.uris" : "+", - "pkce.code.challenge.method" : "S256" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "f33b40fe-bb9e-4254-ada9-f98dd203641b", - "name" : "audience resolve", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-audience-resolve-mapper", - "consentRequired" : false, - "config" : { } - } ], - "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "organization", "microprofile-jwt" ] - }, { - "id" : "9449aa8b-d5cc-4b9f-bb01-be1e5a896f2f", - "clientId" : "admin-cli", - "name" : "${client_admin-cli}", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "standardFlowEnabled" : false, - "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : true, - "serviceAccountsEnabled" : false, - "publicClient" : true, - "frontchannelLogout" : false, - "protocol" : "openid-connect", - "attributes" : { - "realm_client" : "false", - "client.use.lightweight.access.token.enabled" : "true" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : true, - "nodeReRegistrationTimeout" : 0, - "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "organization", "microprofile-jwt" ] - }, { - "id" : "befe3d72-8102-49a6-8268-bce6def58159", - "clientId" : "broker", - "name" : "${client_broker}", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : true, - "consentRequired" : false, - "standardFlowEnabled" : true, - "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : false, - "serviceAccountsEnabled" : false, - "publicClient" : false, - "frontchannelLogout" : false, - "protocol" : "openid-connect", - "attributes" : { - "realm_client" : "true" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "organization", "microprofile-jwt" ] - }, { - "id" : "714243ae-72cc-4c26-842a-047357b5919a", - "clientId" : "dwengo", - "name" : "Dwengo", - "description" : "", - "rootUrl" : "http://localhost:5173", - "adminUrl" : "http://localhost:5173", - "baseUrl" : "/", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-jwt", - "redirectUris" : [ "urn:ietf:wg:oauth:2.0:oob", "http://localhost:5173/*", "http://localhost:5173", "http://localhost:3000/api-docs/oauth2-redirect.html" ], - "webOrigins" : [ "+" ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "standardFlowEnabled" : true, - "implicitFlowEnabled" : true, - "directAccessGrantsEnabled" : false, - "serviceAccountsEnabled" : false, - "publicClient" : true, - "frontchannelLogout" : true, - "protocol" : "openid-connect", - "attributes" : { - "realm_client" : "false", - "oidc.ciba.grant.enabled" : "false", - "client.secret.creation.time" : "1740860818", - "backchannel.logout.session.required" : "true", - "token.endpoint.auth.signing.alg" : "RS256", - "post.logout.redirect.uris" : "+", - "frontchannel.logout.session.required" : "true", - "oauth2.device.authorization.grant.enabled" : "false", - "display.on.consent.screen" : "false", - "backchannel.logout.revoke.offline.tokens" : "false" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : true, - "nodeReRegistrationTimeout" : -1, - "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "organization", "microprofile-jwt" ] - }, { - "id" : "0b06aaa3-717d-4a52-ab46-295a6571b642", - "clientId" : "realm-management", - "name" : "${client_realm-management}", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : true, - "consentRequired" : false, - "standardFlowEnabled" : true, - "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : false, - "serviceAccountsEnabled" : false, - "publicClient" : false, - "frontchannelLogout" : false, - "protocol" : "openid-connect", - "attributes" : { - "realm_client" : "true" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "organization", "microprofile-jwt" ] - }, { - "id" : "dfc7248c-3794-4e3b-aed2-3ee553cd0feb", - "clientId" : "security-admin-console", - "name" : "${client_security-admin-console}", - "rootUrl" : "${authAdminUrl}", - "baseUrl" : "/admin/student/console/", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ "/admin/student/console/*" ], - "webOrigins" : [ "+" ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "standardFlowEnabled" : true, - "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : false, - "serviceAccountsEnabled" : false, - "publicClient" : true, - "frontchannelLogout" : false, - "protocol" : "openid-connect", - "attributes" : { - "realm_client" : "false", - "client.use.lightweight.access.token.enabled" : "true", - "post.logout.redirect.uris" : "+", - "pkce.code.challenge.method" : "S256" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : true, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "9e9ff295-30c9-43f1-a11a-773724709c07", - "name" : "locale", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "locale", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "locale", - "jsonType.label" : "String" - } - } ], - "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "organization", "microprofile-jwt" ] - } ], - "clientScopes" : [ { - "id" : "0721b27a-284f-4e6d-af70-b6f190ebdcd4", - "name" : "email", - "description" : "OpenID Connect built-in scope: email", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "consent.screen.text" : "${emailScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "d256bdc1-8983-41e0-b8fa-fcf45653045e", - "name" : "email verified", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "emailVerified", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email_verified", - "jsonType.label" : "boolean" - } - }, { - "id" : "651c2415-db30-40ed-bdef-745b6ea744ed", - "name" : "email", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "email", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email", - "jsonType.label" : "String" - } - } ] - }, { - "id" : "573f6eea-7626-44fe-9855-50f15c3939ba", - "name" : "web-origins", - "description" : "OpenID Connect scope for add allowed web origins to the access token", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "false", - "consent.screen.text" : "", - "display.on.consent.screen" : "false" - }, - "protocolMappers" : [ { - "id" : "3489c748-3cc7-4350-9351-2955fc7084ba", - "name" : "allowed web origins", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-allowed-origins-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "access.token.claim" : "true" - } - } ] - }, { - "id" : "00afe548-c677-4595-8478-16f752c2713a", - "name" : "offline_access", - "description" : "OpenID Connect built-in scope: offline_access", - "protocol" : "openid-connect", - "attributes" : { - "consent.screen.text" : "${offlineAccessScopeConsentText}", - "display.on.consent.screen" : "true" - } - }, { - "id" : "1448ed2b-ec1d-4bf4-a8b7-00cb85459289", - "name" : "address", - "description" : "OpenID Connect built-in scope: address", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "consent.screen.text" : "${addressScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "12d491b6-5d74-4168-ac5c-517ebc2f1de4", - "name" : "address", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-address-mapper", - "consentRequired" : false, - "config" : { - "user.attribute.formatted" : "formatted", - "user.attribute.country" : "country", - "introspection.token.claim" : "true", - "user.attribute.postal_code" : "postal_code", - "userinfo.token.claim" : "true", - "user.attribute.street" : "street", - "id.token.claim" : "true", - "user.attribute.region" : "region", - "access.token.claim" : "true", - "user.attribute.locality" : "locality" - } - } ] - }, { - "id" : "52223fb1-9651-4cdf-8317-a1301d4042f7", - "name" : "organization", - "description" : "Additional claims about the organization a subject belongs to", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "consent.screen.text" : "${organizationScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "dccc4214-ece6-4235-8119-ee8cb954c29a", - "name" : "organization", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-organization-membership-mapper", - "consentRequired" : false, - "config" : { - "id.token.claim" : "true", - "introspection.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "organization", - "jsonType.label" : "String", - "multivalued" : "true" - } - } ] - }, { - "id" : "8be22542-e327-4a25-8265-a34a29607d1b", - "name" : "service_account", - "description" : "Specific scope for a client enabled for service accounts", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "false", - "display.on.consent.screen" : "false" - }, - "protocolMappers" : [ { - "id" : "cf89064a-0af3-4a4b-a838-3528a8f4d780", - "name" : "Client IP Address", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usersessionmodel-note-mapper", - "consentRequired" : false, - "config" : { - "user.session.note" : "clientAddress", - "id.token.claim" : "true", - "introspection.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "clientAddress", - "jsonType.label" : "String" - } - }, { - "id" : "dc0f77e6-cc20-4c0a-baf3-f45046d749d1", - "name" : "Client ID", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usersessionmodel-note-mapper", - "consentRequired" : false, - "config" : { - "user.session.note" : "client_id", - "id.token.claim" : "true", - "introspection.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "client_id", - "jsonType.label" : "String" - } - }, { - "id" : "d63fd29a-3613-4529-a8e4-3a7d7e9f5802", - "name" : "Client Host", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usersessionmodel-note-mapper", - "consentRequired" : false, - "config" : { - "user.session.note" : "clientHost", - "id.token.claim" : "true", - "introspection.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "clientHost", - "jsonType.label" : "String" - } - } ] - }, { - "id" : "d9079603-62b7-4680-9d01-950daae75d6b", - "name" : "saml_organization", - "description" : "Organization Membership", - "protocol" : "saml", - "attributes" : { - "display.on.consent.screen" : "false" - }, - "protocolMappers" : [ { - "id" : "d826fc58-b006-49ad-93dc-a76700e800df", - "name" : "organization", - "protocol" : "saml", - "protocolMapper" : "saml-organization-membership-mapper", - "consentRequired" : false, - "config" : { } - } ] - }, { - "id" : "171d8267-87da-4a4b-9346-d901d470248b", - "name" : "phone", - "description" : "OpenID Connect built-in scope: phone", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "consent.screen.text" : "${phoneScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "f8bb18d4-af9d-49b0-a61f-cc81887870cd", - "name" : "phone number", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "phoneNumber", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "phone_number", - "jsonType.label" : "String" - } - }, { - "id" : "88a2c658-9b61-40a2-abd5-69c501286031", - "name" : "phone number verified", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "phoneNumberVerified", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "phone_number_verified", - "jsonType.label" : "boolean" - } - } ] - }, { - "id" : "ea3b84ac-a91f-4a3d-be4e-893e11eaf4a1", - "name" : "acr", - "description" : "OpenID Connect scope for add acr (authentication context class reference) to the token", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "false", - "display.on.consent.screen" : "false" - }, - "protocolMappers" : [ { - "id" : "152d66d4-524f-47f1-a592-be3a0c043a4f", - "name" : "acr loa level", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-acr-mapper", - "consentRequired" : false, - "config" : { - "id.token.claim" : "true", - "introspection.token.claim" : "true", - "access.token.claim" : "true" - } - } ] - }, { - "id" : "2fc1ad0d-1065-4196-8d1b-c61525c9425d", - "name" : "microprofile-jwt", - "description" : "Microprofile - JWT built-in scope", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "display.on.consent.screen" : "false" - }, - "protocolMappers" : [ { - "id" : "9d537486-f6bf-4856-91fc-ca3acaa78814", - "name" : "upn", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "upn", - "jsonType.label" : "String" - } - }, { - "id" : "55425438-4111-47a0-9a36-fec9dbbc6a8a", - "name" : "groups", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-realm-role-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "multivalued" : "true", - "user.attribute" : "foo", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "groups", - "jsonType.label" : "String" - } - } ] - }, { - "id" : "0d186f4e-ef6d-4fbc-9593-081e0d5ad171", - "name" : "profile", - "description" : "OpenID Connect built-in scope: profile", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "consent.screen.text" : "${profileScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "bb8bb550-2db6-4631-97dc-1d115d0e3034", - "name" : "given name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "firstName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "given_name", - "jsonType.label" : "String" - } - }, { - "id" : "c942089b-2898-4052-a64d-85b61e27aaa4", - "name" : "username", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "preferred_username", - "jsonType.label" : "String" - } - }, { - "id" : "5ff3a9ca-7036-458c-b0dc-41216292d210", - "name" : "updated at", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "updatedAt", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "updated_at", - "jsonType.label" : "long" - } - }, { - "id" : "41f93d62-4074-4373-a270-9bdf1e298cb5", - "name" : "website", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "website", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "website", - "jsonType.label" : "String" - } - }, { - "id" : "ffec7d63-0f78-41ea-8023-6c7c64661b34", - "name" : "locale", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "locale", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "locale", - "jsonType.label" : "String" - } - }, { - "id" : "4a514ae7-d29f-4979-8df9-a97b36a81a96", - "name" : "profile", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "profile", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "profile", - "jsonType.label" : "String" - } - }, { - "id" : "286e349b-cb9f-41b1-b9dc-d787f13e9d99", - "name" : "nickname", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "nickname", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "nickname", - "jsonType.label" : "String" - } - }, { - "id" : "f5177603-55b1-4abe-aee6-b1e5a05e37f6", - "name" : "full name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-full-name-mapper", - "consentRequired" : false, - "config" : { - "id.token.claim" : "true", - "introspection.token.claim" : "true", - "access.token.claim" : "true", - "userinfo.token.claim" : "true" - } - }, { - "id" : "a31114d7-05fc-40c1-9ea8-6977f6f0bec5", - "name" : "zoneinfo", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "zoneinfo", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "zoneinfo", - "jsonType.label" : "String" - } - }, { - "id" : "8884be77-648d-4083-b0cf-57130162c8dc", - "name" : "gender", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "gender", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "gender", - "jsonType.label" : "String" - } - }, { - "id" : "61840434-c79f-455a-a914-117977197304", - "name" : "family name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "lastName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "family_name", - "jsonType.label" : "String" - } - }, { - "id" : "1f40ff0b-1664-4259-846b-ab707c76d33b", - "name" : "middle name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "middleName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "middle_name", - "jsonType.label" : "String" - } - }, { - "id" : "8534d400-8a81-4ae3-b51f-78b93e5a2045", - "name" : "picture", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "picture", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "picture", - "jsonType.label" : "String" - } - }, { - "id" : "82a0e240-0824-41b9-b6e8-856a72d1e930", - "name" : "birthdate", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "birthdate", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "birthdate", - "jsonType.label" : "String" - } - } ] - }, { - "id" : "a5cedc85-d9e9-42e1-9ea3-ff37d21d5e27", - "name" : "role_list", - "description" : "SAML role list", - "protocol" : "saml", - "attributes" : { - "consent.screen.text" : "${samlRoleListScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "19009128-590f-4bc9-80de-c9ba4aae822d", - "name" : "role list", - "protocol" : "saml", - "protocolMapper" : "saml-role-list-mapper", - "consentRequired" : false, - "config" : { - "single" : "false", - "attribute.nameformat" : "Basic", - "attribute.name" : "Role" - } - } ] - }, { - "id" : "3b6bb88b-c833-4bb5-9bd0-95831aa2ad0d", - "name" : "basic", - "description" : "OpenID Connect scope for add all basic claims to the token", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "false", - "display.on.consent.screen" : "false" - }, - "protocolMappers" : [ { - "id" : "ce925803-aec2-47cb-a3b9-4bef12c80367", - "name" : "sub", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-sub-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "access.token.claim" : "true" - } - }, { - "id" : "58729b3a-3816-460e-bf2e-d0d2206c1830", - "name" : "auth_time", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usersessionmodel-note-mapper", - "consentRequired" : false, - "config" : { - "user.session.note" : "AUTH_TIME", - "id.token.claim" : "true", - "introspection.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "auth_time", - "jsonType.label" : "long" - } - } ] - }, { - "id" : "7aa2d936-3edb-45e5-bae0-b4a618d06371", - "name" : "roles", - "description" : "OpenID Connect scope for add user roles to the access token", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "false", - "consent.screen.text" : "${rolesScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "a9d1e8e2-ca10-4904-8a42-7708b0bfdefa", - "name" : "client roles", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-client-role-mapper", - "consentRequired" : false, - "config" : { - "user.attribute" : "foo", - "introspection.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "resource_access.${client_id}.roles", - "jsonType.label" : "String", - "multivalued" : "true" - } - }, { - "id" : "1f217073-ad43-483b-b0d5-f3ca4c74282f", - "name" : "realm roles", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-realm-role-mapper", - "consentRequired" : false, - "config" : { - "user.attribute" : "foo", - "introspection.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "realm_access.roles", - "jsonType.label" : "String", - "multivalued" : "true" - } - }, { - "id" : "61b0a069-8b67-4692-bcca-66a197b230eb", - "name" : "audience resolve", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-audience-resolve-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "access.token.claim" : "true" - } - } ] - } ], - "defaultDefaultClientScopes" : [ "role_list", "saml_organization", "profile", "email", "roles", "web-origins", "acr", "basic" ], - "defaultOptionalClientScopes" : [ "offline_access", "address", "phone", "microprofile-jwt", "organization" ], - "browserSecurityHeaders" : { - "contentSecurityPolicyReportOnly" : "", - "xContentTypeOptions" : "nosniff", - "referrerPolicy" : "no-referrer", - "xRobotsTag" : "none", - "xFrameOptions" : "SAMEORIGIN", - "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", - "xXSSProtection" : "1; mode=block", - "strictTransportSecurity" : "max-age=31536000; includeSubDomains" - }, - "smtpServer" : { }, - "eventsEnabled" : false, - "eventsListeners" : [ "jboss-logging" ], - "enabledEventTypes" : [ ], - "adminEventsEnabled" : false, - "adminEventsDetailsEnabled" : false, - "identityProviders" : [ ], - "identityProviderMappers" : [ ], - "components" : { - "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { - "id" : "9eac5531-7f25-493f-a721-6c5e65cd34c2", - "name" : "Max Clients Limit", - "providerId" : "max-clients", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "max-clients" : [ "200" ] - } - }, { - "id" : "d9319a22-4c67-4b08-822f-4162a1ee01bc", - "name" : "Allowed Client Scopes", - "providerId" : "allowed-client-templates", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "allow-default-scopes" : [ "true" ] - } - }, { - "id" : "21456c8e-7f6b-4e49-a3e1-bea7f900e2fb", - "name" : "Consent Required", - "providerId" : "consent-required", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { } - }, { - "id" : "4872e99b-b55b-4e13-8a93-63e853289cac", - "name" : "Full Scope Disabled", - "providerId" : "scope", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { } - }, { - "id" : "a118a194-09f5-435d-9d4b-363813413167", - "name" : "Trusted Hosts", - "providerId" : "trusted-hosts", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "host-sending-registration-request-must-match" : [ "true" ], - "client-uris-must-match" : [ "true" ] - } - }, { - "id" : "e32b1e26-6571-4b0c-a205-0fbb3de44384", - "name" : "Allowed Client Scopes", - "providerId" : "allowed-client-templates", - "subType" : "authenticated", - "subComponents" : { }, - "config" : { - "allow-default-scopes" : [ "true" ] - } - }, { - "id" : "9dbe6752-9978-42a3-9210-9ec166140de2", - "name" : "Allowed Protocol Mapper Types", - "providerId" : "allowed-protocol-mappers", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "allowed-protocol-mapper-types" : [ "oidc-usermodel-attribute-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-address-mapper", "saml-role-list-mapper", "saml-user-property-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper", "saml-user-attribute-mapper" ] - } - }, { - "id" : "7027b3f4-d877-4814-ac78-f1edb8eb89b0", - "name" : "Allowed Protocol Mapper Types", - "providerId" : "allowed-protocol-mappers", - "subType" : "authenticated", - "subComponents" : { }, - "config" : { - "allowed-protocol-mapper-types" : [ "saml-role-list-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-address-mapper", "saml-user-property-mapper", "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper" ] - } - } ], - "org.keycloak.keys.KeyProvider" : [ { - "id" : "819cfc66-a997-4747-9d90-a7f0c09774bf", - "name" : "aes-generated", - "providerId" : "aes-generated", - "subComponents" : { }, - "config" : { - "kid" : [ "eb74df73-3f34-457d-95c7-5ad909107703" ], - "secret" : [ "1K8IJiDODmotHJPStrXhtA" ], - "priority" : [ "100" ] - } - }, { - "id" : "299857cd-52a4-4981-8171-02e7d8f12960", - "name" : "rsa-generated", - "providerId" : "rsa-generated", - "subComponents" : { }, - "config" : { - "privateKey" : [ "MIIEpAIBAAKCAQEA1MRmAT/yImkVfPMBxC0QHdC4DQfuWUTjKeEku+gMI9jX5ChUzzzVugcvZWmxBNcvOz7p6R8EdPllZKIwFSH5WvQ1w1VIgQwIlYfpi/pknfftLd66MI2fXrQK19dRTeQRivEf39GTfBQ2Xc7y1q7zbMo5TVxATJ3DgPi13dYO7zVPpGTiQQeYiezlcBedyGe4cS1g6oBoaVif1QPY1Ni2vEjJhczNMGI408tIFws8G04Tlno814nT0ysdflUSGcRUku41NtfM9hr57LQ459sGYho8Pn11lDuiUWkomJv0y3GJ1wFBvQbDvI+6QvEdFu0GxShrlcORrNmj3BwOOLhB7wIDAQABAoIBAA6zrXq7oO8YxMfYANC97mWpBPa9jA42EN5VdNTZIXGeq7hTwxx4zynmEjPXPEih190nqUEXCBdPHl74SAtFyDWtN0PSkkp8euFePViTSj2SIpzvTX1KY+9G0JL+iVsw/bdUlwe/swm5WdJcmPIVr7NeO9xpGfZRVm+EgAieoHSN4Z7g20wLbVz1fya+6O5Hy+IGezamIA4tchk+4hyiVpSh2TcdjkJJZWOlHKPkwWU/MYQbJibuea5jLoWA39NIqV2l5GT0SoCbffGJNb9CMTTGmXoK5zNwHhG+M0a4eP1vbFDLaoDne86JySmTdv/WrTFFa3veelw2K8PHDybuB70CgYEA/gbxqLZYkJcEpqsjM/XcISFJ09icJLKl5r2l/Dm4Qq587QniQYribX/PPLfDhgVwPByQe3rccq9FoiILycTdIwgSMTsg5fzvbLJTqMAcl2r0zJgHVIDc6iXnytuE0FffKN0kSKL1C4d6n6vKoCGvOcZoXK5jxgzpY8lasvKxhCsCgYEA1mtr7CDYY3qPmTu4/Uz6cFgX8RDMZZ11AQQXNMsKHIu5C4xLeYmJMlpt0y4h52/NWRzh2svdw3SEZTCfP1WVC7StfP8KD8QdwVkQlY5EGkiz9uRtEgwk8chkOTIm2JedeRL6YWlTgnH9PIuGq84OOnEbFjVN3Lbx3N1QuQfVA00CgYEAybA1uuBcXSCqfrIuVxkD2AIYHe1DvBdjhVpaKXKii78CTSmlzKg6svnhTrIQuZ4jyHZdeMzJrvzeaqZheaemdCP6XcA2lKRIbKMBrWAq00YGa1LhrwRJYlcKPJQiVVEPS+CY6FsJ+Edu4suBK7bS6ypOvhdv/FVQEPxT2PS8YNUCgYEAxwJ+8XNuw63ud9+Zi+gVjY4F8qWPwESLYz0DuOk2YlZAknpNVumTYBvUUSxBJYh8RFhtO+D53D5Z331oYKUzJ+EzII+qLAXvRBRBMz4O8YJHHkDXBugkphBDDV8B9QeLjeNSZnUWoDziOH6bqPwf8pgl9s/Ui6V1CHSVRpcBWwUCgYA2kMgu7qS5kLtUWySPzW4nTKwhN+HFTIbRrNrECxXmxroigTEyfBFuNR5QaeYYrAtqgY1m5Lev//2GnWM7dAr7hewj6qfGszrvegHsqMs4cakVqEOtbrWxL+WtWPaIdjJ+x7ZoMnZxZDg3ysemybNHHwSyBsp1TDc+glzmMtJtLA==" ], - "keyUse" : [ "SIG" ], - "certificate" : [ "MIICnTCCAYUCBgGVUbFIeTANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdzdHVkZW50MB4XDTI1MDMwMTEyMzAyN1oXDTM1MDMwMTEyMzIwN1owEjEQMA4GA1UEAwwHc3R1ZGVudDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANTEZgE/8iJpFXzzAcQtEB3QuA0H7llE4ynhJLvoDCPY1+QoVM881boHL2VpsQTXLzs+6ekfBHT5ZWSiMBUh+Vr0NcNVSIEMCJWH6Yv6ZJ337S3eujCNn160CtfXUU3kEYrxH9/Rk3wUNl3O8tau82zKOU1cQEydw4D4td3WDu81T6Rk4kEHmIns5XAXnchnuHEtYOqAaGlYn9UD2NTYtrxIyYXMzTBiONPLSBcLPBtOE5Z6PNeJ09MrHX5VEhnEVJLuNTbXzPYa+ey0OOfbBmIaPD59dZQ7olFpKJib9MtxidcBQb0Gw7yPukLxHRbtBsUoa5XDkazZo9wcDji4Qe8CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAedqvKOBsz4IKKzkWHIQnN5H8dQKnuPUMdWewOwmMGIUdBU9k6aS+y+BB7mugF/Hnr8Lw5d2AHwVLj2VyP4Pq0d2My3Ihxi0vr6sSfxVHuD9y/a7FxDGVTkCvmy5DOmpF/kdNnL9xG5ZivHaucnrIHHGMcQCdbWAaac0qPZihv9pdMZFMtI3aiBO5jVJ7KP8iLNKsshg60mxCOPzauMVXi+rqqqhGAgMKAL4hjjvdIKTLWwmthnmAlGqlTk/7H82hS9aKygufXszXWdFAYhX/r8/hjyc+6zJUvkG20uRWnkR35gya7jQoZ2O6OvkQf0mgSvzgIP3xoYV2uKYD03wINg==" ], - "priority" : [ "100" ] - } - }, { - "id" : "3d6bfeeb-fa86-435e-8c39-6f547a0f4a38", - "name" : "hmac-generated-hs512", - "providerId" : "hmac-generated", - "subComponents" : { }, - "config" : { - "kid" : [ "176e970f-5915-4d27-8233-8fab6d7ad947" ], - "secret" : [ "sXeOdtyIPpH_kcZWikHFjTur9yWok0QUwKi95l8wHp6kTVX9vhoZL2siNHRoFnn8tFgT4JZbR0bMsD57qAXlmVjA830Ny_GZdhL_PFWQh7JYMEJrl-1nyLy_SReQXRtq_q9tKUafUZqeYSKBlUYZ7D4jNRJ4-uniq80Ger-4ee0" ], - "priority" : [ "100" ], - "algorithm" : [ "HS512" ] - } - }, { - "id" : "df1247b5-041e-4ae8-b7fc-26c4b6f5ff67", - "name" : "rsa-enc-generated", - "providerId" : "rsa-enc-generated", - "subComponents" : { }, - "config" : { - "privateKey" : [ "MIIEpAIBAAKCAQEAq0piY/PaZh1IX46e0G6tCPtfRx7Q8zGslOFSBLR9PNAdSlBpYiV9kOpN1kTK2Sca5j4yyO9HAFK5j+zh/cy8SsP1iyuI0sCPG7NMKV1pP5Y753wTC0lTq8z16bvXPvPYrPRKgGDmU7Ww0/WD6/P+z9Li/+ujFHzzzfoPuQvbBhma6A4oadsC+zun/mWCyiD14mB+X00BeAIsxKJZ/Sd+U4lMkkvmpoXyx61xK+j48EAZ18u7FprlvUjgGzzAmm/K6O/fHPIw5eViVly7aj5gjh67ntSuZArVtrxy/Py5V4hkSO8guXKqNz3liJvLbFCqqpfTR/0duArZR0xcnaGc6wIDAQABAoIBAFRuu5YaXxbDq2eS5RzH2Vpakin7+jJOU4wljujL0QnXagC2J2QeJ8l1fT23tieZO4yvrxfVvnFd1aMouHMC5vORqWja4jxEd6ZHWKzxIw6ZbtjZk4eWMvy18KewlFavGyiR2GF0okQ0BMBOPqNhp8JoaMWOsNnKB+GJuBNWUTWtPWNQlbaeI+uIgFywrvZOcQuWqU+9Y5rQa7oKZisufu+z0vd9XyvjXQ/Thnuu9/k1m88EMAMS63zwfIOZm35PPqh1/6aBBcRWquT1X2S8g2hwmMLZJgU91yKtQcIujHXAcvxeK72/dcm8NU6AxM+8aj+821TvcNzJi7he5SGcin0CgYEA4cuxwqRixXz6yDcTv9GpJMULbJGjA+Qf/iSfT+ftBeKbnKgZGzHwOCTu5DMkag3BPjclut4sEt3QPf1cFv5vZvdkOnPeaFxrtoMhSz8ssh8qaOsObCwicel1zdPVTmMw7YzEZV14fdIq3lkHsLy2uWa0imRH0l4xTccmsJiPtU8CgYEAwjQtspxOejCyME+M+hcU3RelD6kaMjICuWGJj8g0OpqdHM7iNVJq78fOlWjntt/ydzfOXVMMVh4AG8dAvlc86iwwsBRsJPVrrrRoSNuAwFbjKisbjlnPbqyclHfUsyQitj19tp//ExH7JaBibzKd6KhqFuQTE1iYLs2mFQAz76UCgYEAsNLu64oGm7frQP345mAPgO8aqjRHIBX3g/Q0GsR61wAGcyElQCnUgHNT7burSa5p5goT7wpsI343xUPzaUJqBY25nRj+VGYEKFL6sM3Rd9B2SuHBUq8hbmmwyraYtiFxwKZbazJO2OHMloHMRvkSc5Dd0/8CS9ld7RYH04Y2DHsCgYEAoKXTK44baP7BWC9mOjc/vgjiNQs4rU8ra7igt7zwX44o63zEKUHNTh7l6DiIfYHRrAcRAahCazaT9makSxAVRs1ZVT7/mq8d7b41Chfx8KmvbuGMAPyQGEhXmoVqAOqigEhrptfBhD/6lkyPQNcJQz2VzOvMT9OYyBa8DWFGlTUCgYAfModz6g2HsYYr37/0ByXHKL0WQQtuAlZzCY9GuDLEok7QLFI/E+bdOHos3goW72Iswo/SO7inlW4S3gojuy+zZhwCO31T9p2Z0Yn0tDK3fkUO32flOLwxCZA99pKkIul3svl6643GqSD1feybmbYRtqoPCTSKSE9vI9T9DkBTvA==" ], - "keyUse" : [ "ENC" ], - "certificate" : [ "MIICnTCCAYUCBgGVUbFItzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdzdHVkZW50MB4XDTI1MDMwMTEyMzAyN1oXDTM1MDMwMTEyMzIwN1owEjEQMA4GA1UEAwwHc3R1ZGVudDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKtKYmPz2mYdSF+OntBurQj7X0ce0PMxrJThUgS0fTzQHUpQaWIlfZDqTdZEytknGuY+MsjvRwBSuY/s4f3MvErD9YsriNLAjxuzTCldaT+WO+d8EwtJU6vM9em71z7z2Kz0SoBg5lO1sNP1g+vz/s/S4v/roxR88836D7kL2wYZmugOKGnbAvs7p/5lgsog9eJgfl9NAXgCLMSiWf0nflOJTJJL5qaF8setcSvo+PBAGdfLuxaa5b1I4Bs8wJpvyujv3xzyMOXlYlZcu2o+YI4eu57UrmQK1ba8cvz8uVeIZEjvILlyqjc95Yiby2xQqqqX00f9HbgK2UdMXJ2hnOsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAHfum0Ymw/qHTpjNeJjJyh5A1N4Z58m/PjuCXafIdDDjuAYpB3/M4bTGZRUvEvv2RuBNv3rONvMR8dRrZwio0/T0aEXnHrEAaCSfVcMy1To8TGGOzgtPMub4YCqXLMCwW5cwIbqT3P58HEqsqEbv7Zp4LtLYZBYXWDF8vM4zEn3CPYxuxRPKlrBUynRKYcwN7+/dbhJKiARpPMIZ5viGbjaTnNE/d/VFdv1q5xm3ItYnShDyJ0REGN18sWleLI6qkW0X22Gcjn38fWjiXDnF0HQYzC2UzMcEo/iLfPxTKbJnc+PPmnszfmCh7mWs5xVGfMOz/Oy8HI121x1ZSriRktA==" ], - "priority" : [ "100" ], - "algorithm" : [ "RSA-OAEP" ] - } - } ] - }, - "internationalizationEnabled" : false, - "supportedLocales" : [ ], - "authenticationFlows" : [ { - "id" : "f7d1108f-7994-47e5-81e9-1a88cdbe545c", - "alias" : "Account verification options", - "description" : "Method with which to verity the existing account", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "idp-email-verification", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "ALTERNATIVE", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "Verify Existing Account by Re-authentication", - "userSetupAllowed" : false - } ] - }, { - "id" : "cf40a5d3-bec8-4aef-9658-1b88c6cec561", - "alias" : "Browser - Conditional OTP", - "description" : "Flow to determine if the OTP is required for the authentication", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "conditional-user-configured", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "auth-otp-form", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "6820625f-5bb5-4fa2-8539-26a8568265c1", - "alias" : "Browser - Conditional Organization", - "description" : "Flow to determine if the organization identity-first login is to be used", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "conditional-user-configured", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "organization", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "66d5e52e-592e-4cef-bfa0-512e90b609ec", - "alias" : "Direct Grant - Conditional OTP", - "description" : "Flow to determine if the OTP is required for the authentication", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "conditional-user-configured", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "direct-grant-validate-otp", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "b5bed405-b5f2-4839-861c-612501e4c412", - "alias" : "First Broker Login - Conditional Organization", - "description" : "Flow to determine if the authenticator that adds organization members is to be used", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "conditional-user-configured", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "idp-add-organization-member", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "dd786e24-e822-43ec-be03-29874eb73737", - "alias" : "First broker login - Conditional OTP", - "description" : "Flow to determine if the OTP is required for the authentication", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "conditional-user-configured", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "auth-otp-form", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "8751572f-623e-4bdc-a02c-e92c15a91143", - "alias" : "Handle Existing Account", - "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "idp-confirm-link", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "Account verification options", - "userSetupAllowed" : false - } ] - }, { - "id" : "61efadf2-a54e-4071-b8c9-83e094525051", - "alias" : "Organization", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticatorFlow" : true, - "requirement" : "CONDITIONAL", - "priority" : 10, - "autheticatorFlow" : true, - "flowAlias" : "Browser - Conditional Organization", - "userSetupAllowed" : false - } ] - }, { - "id" : "b99c3a7a-8ef7-46b1-b8a1-cb51f8a6e725", - "alias" : "Reset - Conditional OTP", - "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "conditional-user-configured", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "reset-otp", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "a3bfc2e4-af67-4d3e-851f-3c58bf32be83", - "alias" : "User creation or linking", - "description" : "Flow for the existing/non-existing user alternatives", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticatorConfig" : "create unique user config", - "authenticator" : "idp-create-user-if-unique", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "ALTERNATIVE", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "Handle Existing Account", - "userSetupAllowed" : false - } ] - }, { - "id" : "4cc3bf25-d1b7-43a6-8619-5ed5f2d65aed", - "alias" : "Verify Existing Account by Re-authentication", - "description" : "Reauthentication of existing account", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "idp-username-password-form", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "CONDITIONAL", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "First broker login - Conditional OTP", - "userSetupAllowed" : false - } ] - }, { - "id" : "4e5564ce-87da-4b25-8dcb-062216ceaa8d", - "alias" : "browser", - "description" : "Browser based authentication", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "auth-cookie", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "auth-spnego", - "authenticatorFlow" : false, - "requirement" : "DISABLED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "identity-provider-redirector", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 25, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "ALTERNATIVE", - "priority" : 26, - "autheticatorFlow" : true, - "flowAlias" : "Organization", - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "ALTERNATIVE", - "priority" : 30, - "autheticatorFlow" : true, - "flowAlias" : "forms", - "userSetupAllowed" : false - } ] - }, { - "id" : "def90462-5831-4856-b186-05df9e640bbb", - "alias" : "clients", - "description" : "Base authentication for clients", - "providerId" : "client-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "client-secret", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "client-jwt", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "client-secret-jwt", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 30, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "client-x509", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 40, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "f8c9010d-f197-417b-bda1-2993e1a73a21", - "alias" : "direct grant", - "description" : "OpenID Connect Resource Owner Grant", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "direct-grant-validate-username", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "direct-grant-validate-password", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "CONDITIONAL", - "priority" : 30, - "autheticatorFlow" : true, - "flowAlias" : "Direct Grant - Conditional OTP", - "userSetupAllowed" : false - } ] - }, { - "id" : "0fb9e2a4-ea0d-453f-a1fe-f000c849fd66", - "alias" : "docker auth", - "description" : "Used by Docker clients to authenticate against the IDP", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "docker-http-basic-authenticator", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "79a9efc4-1279-4093-8914-92f4e0b02bb4", - "alias" : "first broker login", - "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticatorConfig" : "review profile config", - "authenticator" : "idp-review-profile", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "User creation or linking", - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "CONDITIONAL", - "priority" : 50, - "autheticatorFlow" : true, - "flowAlias" : "First Broker Login - Conditional Organization", - "userSetupAllowed" : false - } ] - }, { - "id" : "f855b3a1-6612-4528-94bc-d0793bfda561", - "alias" : "forms", - "description" : "Username, password, otp and other auth forms.", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "auth-username-password-form", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "CONDITIONAL", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "Browser - Conditional OTP", - "userSetupAllowed" : false - } ] - }, { - "id" : "fb84970b-6f04-4849-a385-792e17c1b8ce", - "alias" : "registration", - "description" : "Registration flow", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "registration-page-form", - "authenticatorFlow" : true, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : true, - "flowAlias" : "registration form", - "userSetupAllowed" : false - } ] - }, { - "id" : "fcdfd4d4-1c04-487d-aa7c-85e136814274", - "alias" : "registration form", - "description" : "Registration form", - "providerId" : "form-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "registration-user-creation", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "registration-password-action", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 50, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "registration-recaptcha-action", - "authenticatorFlow" : false, - "requirement" : "DISABLED", - "priority" : 60, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "registration-terms-and-conditions", - "authenticatorFlow" : false, - "requirement" : "DISABLED", - "priority" : 70, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "306d8f7d-c12a-46cb-9a68-c6c3f1622f57", - "alias" : "reset credentials", - "description" : "Reset credentials for a user if they forgot their password or something", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "reset-credentials-choose-user", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "reset-credential-email", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "reset-password", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 30, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "CONDITIONAL", - "priority" : 40, - "autheticatorFlow" : true, - "flowAlias" : "Reset - Conditional OTP", - "userSetupAllowed" : false - } ] - }, { - "id" : "35a54b09-ff8c-46c4-9f04-1efbb153276c", - "alias" : "saml ecp", - "description" : "SAML ECP Profile Authentication Flow", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "http-basic-authenticator", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - } ], - "authenticatorConfig" : [ { - "id" : "fc1b82d7-593d-4906-a4d9-13220b66b7ce", - "alias" : "create unique user config", - "config" : { - "require.password.update.after.registration" : "false" - } - }, { - "id" : "a90543f4-7da7-43bc-8737-7e58dd190014", - "alias" : "review profile config", - "config" : { - "update.profile.on.first.login" : "missing" - } - } ], - "requiredActions" : [ { - "alias" : "CONFIGURE_TOTP", - "name" : "Configure OTP", - "providerId" : "CONFIGURE_TOTP", - "enabled" : true, - "defaultAction" : false, - "priority" : 10, - "config" : { } - }, { - "alias" : "TERMS_AND_CONDITIONS", - "name" : "Terms and Conditions", - "providerId" : "TERMS_AND_CONDITIONS", - "enabled" : false, - "defaultAction" : false, - "priority" : 20, - "config" : { } - }, { - "alias" : "UPDATE_PASSWORD", - "name" : "Update Password", - "providerId" : "UPDATE_PASSWORD", - "enabled" : true, - "defaultAction" : false, - "priority" : 30, - "config" : { } - }, { - "alias" : "UPDATE_PROFILE", - "name" : "Update Profile", - "providerId" : "UPDATE_PROFILE", - "enabled" : true, - "defaultAction" : false, - "priority" : 40, - "config" : { } - }, { - "alias" : "VERIFY_EMAIL", - "name" : "Verify Email", - "providerId" : "VERIFY_EMAIL", - "enabled" : true, - "defaultAction" : false, - "priority" : 50, - "config" : { } - }, { - "alias" : "delete_account", - "name" : "Delete Account", - "providerId" : "delete_account", - "enabled" : false, - "defaultAction" : false, - "priority" : 60, - "config" : { } - }, { - "alias" : "webauthn-register", - "name" : "Webauthn Register", - "providerId" : "webauthn-register", - "enabled" : true, - "defaultAction" : false, - "priority" : 70, - "config" : { } - }, { - "alias" : "webauthn-register-passwordless", - "name" : "Webauthn Register Passwordless", - "providerId" : "webauthn-register-passwordless", - "enabled" : true, - "defaultAction" : false, - "priority" : 80, - "config" : { } - }, { - "alias" : "VERIFY_PROFILE", - "name" : "Verify Profile", - "providerId" : "VERIFY_PROFILE", - "enabled" : true, - "defaultAction" : false, - "priority" : 90, - "config" : { } - }, { - "alias" : "delete_credential", - "name" : "Delete Credential", - "providerId" : "delete_credential", - "enabled" : true, - "defaultAction" : false, - "priority" : 100, - "config" : { } - }, { - "alias" : "update_user_locale", - "name" : "Update User Locale", - "providerId" : "update_user_locale", - "enabled" : true, - "defaultAction" : false, - "priority" : 1000, - "config" : { } - } ], - "browserFlow" : "browser", - "registrationFlow" : "registration", - "directGrantFlow" : "direct grant", - "resetCredentialsFlow" : "reset credentials", - "clientAuthenticationFlow" : "clients", - "dockerAuthenticationFlow" : "docker auth", - "firstBrokerLoginFlow" : "first broker login", - "attributes" : { - "cibaBackchannelTokenDeliveryMode" : "poll", - "cibaExpiresIn" : "120", - "cibaAuthRequestedUserHint" : "login_hint", - "oauth2DeviceCodeLifespan" : "600", - "oauth2DevicePollingInterval" : "5", - "parRequestUriLifespan" : "60", - "cibaInterval" : "5", - "realmReusableOtpCode" : "false" - }, - "keycloakVersion" : "26.1.3", - "userManagedAccessAllowed" : false, - "organizationsEnabled" : false, - "verifiableCredentialsEnabled" : false, - "adminPermissionsEnabled" : false, - "clientProfiles" : { - "profiles" : [ ] - }, - "clientPolicies" : { - "policies" : [ ] - } } diff --git a/idp/teacher-realm.json b/idp/teacher-realm.json index b5e88c22..5786187c 100644 --- a/idp/teacher-realm.json +++ b/idp/teacher-realm.json @@ -1,2060 +1,2460 @@ { - "id" : "02ba6887-22f5-4de4-ad9b-cb2a2060bce1", - "realm" : "teacher", - "notBefore" : 0, - "defaultSignatureAlgorithm" : "RS256", - "revokeRefreshToken" : false, - "refreshTokenMaxReuse" : 0, - "accessTokenLifespan" : 300, - "accessTokenLifespanForImplicitFlow" : 900, - "ssoSessionIdleTimeout" : 1800, - "ssoSessionMaxLifespan" : 36000, - "ssoSessionIdleTimeoutRememberMe" : 0, - "ssoSessionMaxLifespanRememberMe" : 0, - "offlineSessionIdleTimeout" : 2592000, - "offlineSessionMaxLifespanEnabled" : false, - "offlineSessionMaxLifespan" : 5184000, - "clientSessionIdleTimeout" : 0, - "clientSessionMaxLifespan" : 0, - "clientOfflineSessionIdleTimeout" : 0, - "clientOfflineSessionMaxLifespan" : 0, - "accessCodeLifespan" : 60, - "accessCodeLifespanUserAction" : 300, - "accessCodeLifespanLogin" : 1800, - "actionTokenGeneratedByAdminLifespan" : 43200, - "actionTokenGeneratedByUserLifespan" : 300, - "oauth2DeviceCodeLifespan" : 600, - "oauth2DevicePollingInterval" : 5, - "enabled" : true, - "sslRequired" : "external", - "registrationAllowed" : false, - "registrationEmailAsUsername" : false, - "rememberMe" : false, - "verifyEmail" : false, - "loginWithEmailAllowed" : true, - "duplicateEmailsAllowed" : false, - "resetPasswordAllowed" : false, - "editUsernameAllowed" : false, - "bruteForceProtected" : false, - "permanentLockout" : false, - "maxTemporaryLockouts" : 0, - "bruteForceStrategy" : "MULTIPLE", - "maxFailureWaitSeconds" : 900, - "minimumQuickLoginWaitSeconds" : 60, - "waitIncrementSeconds" : 60, - "quickLoginCheckMilliSeconds" : 1000, - "maxDeltaTimeSeconds" : 43200, - "failureFactor" : 30, - "roles" : { - "realm" : [ { - "id" : "e7f1e366-0bfc-4469-bcde-92bcd1ed5ce7", - "name" : "uma_authorization", - "description" : "${role_uma_authorization}", - "composite" : false, - "clientRole" : false, - "containerId" : "02ba6887-22f5-4de4-ad9b-cb2a2060bce1", - "attributes" : { } - }, { - "id" : "6b546a34-4ebe-4c09-b274-fc1f6bebdf93", - "name" : "default-roles-teacher", - "description" : "${role_default-roles}", - "composite" : true, - "composites" : { - "realm" : [ "offline_access", "uma_authorization" ], - "client" : { - "account" : [ "manage-account", "view-profile" ] + "id": "02ba6887-22f5-4de4-ad9b-cb2a2060bce1", + "realm": "teacher", + "notBefore": 0, + "defaultSignatureAlgorithm": "RS256", + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 300, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "ssoSessionIdleTimeoutRememberMe": 0, + "ssoSessionMaxLifespanRememberMe": 0, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, + "clientOfflineSessionIdleTimeout": 0, + "clientOfflineSessionMaxLifespan": 0, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "oauth2DeviceCodeLifespan": 600, + "oauth2DevicePollingInterval": 5, + "enabled": true, + "sslRequired": "external", + "registrationAllowed": false, + "registrationEmailAsUsername": false, + "rememberMe": false, + "verifyEmail": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": false, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxTemporaryLockouts": 0, + "bruteForceStrategy": "MULTIPLE", + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "roles": { + "realm": [ + { + "id": "e7f1e366-0bfc-4469-bcde-92bcd1ed5ce7", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "02ba6887-22f5-4de4-ad9b-cb2a2060bce1", + "attributes": {} + }, + { + "id": "6b546a34-4ebe-4c09-b274-fc1f6bebdf93", + "name": "default-roles-teacher", + "description": "${role_default-roles}", + "composite": true, + "composites": { + "realm": ["offline_access", "uma_authorization"], + "client": { + "account": ["manage-account", "view-profile"] + } + }, + "clientRole": false, + "containerId": "02ba6887-22f5-4de4-ad9b-cb2a2060bce1", + "attributes": {} + }, + { + "id": "747c4433-f128-4f72-b56f-315e7779d4fd", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "02ba6887-22f5-4de4-ad9b-cb2a2060bce1", + "attributes": {} + } + ], + "client": { + "realm-management": [ + { + "id": "4c8243b1-b576-4cb2-a4f7-3ce25e408fe5", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", + "attributes": {} + }, + { + "id": "71fd672b-024b-4d44-b058-03320aeb1842", + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "realm-management": ["query-groups", "query-users"] + } + }, + "clientRole": true, + "containerId": "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", + "attributes": {} + }, + { + "id": "fea88d42-3065-4600-a5b6-c4e2589e1304", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", + "attributes": {} + }, + { + "id": "6247b5b0-4d41-4fda-900c-3dfc725e03a2", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", + "attributes": {} + }, + { + "id": "a3b55a4b-b7f9-4db3-a64f-6ddf80bf74e7", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", + "attributes": {} + }, + { + "id": "d6714bc8-ff2d-4da0-98b4-2a6479e67954", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", + "attributes": {} + }, + { + "id": "d389da82-1730-4c66-9b43-34ac3c8d7f6c", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", + "attributes": {} + }, + { + "id": "4dc3905f-311b-4de0-b2e6-a3de50a078a3", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", + "attributes": {} + }, + { + "id": "28ea5d84-4e7d-484e-82fa-c9adcea4ffc0", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", + "attributes": {} + }, + { + "id": "e020bc9c-f2c9-4023-82eb-b62266749334", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", + "attributes": {} + }, + { + "id": "e7373af5-924a-4f01-b34d-55a09aac6c74", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", + "attributes": {} + }, + { + "id": "0879b6d5-7db6-4c83-8b99-e889028cb13e", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", + "attributes": {} + }, + { + "id": "ff2c82f3-7f04-4ced-9127-65097e2c16b9", + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "impersonation", + "view-users", + "view-events", + "manage-users", + "view-authorization", + "query-users", + "query-realms", + "manage-events", + "manage-identity-providers", + "query-clients", + "manage-realm", + "view-clients", + "manage-clients", + "query-groups", + "create-client", + "view-realm", + "manage-authorization", + "view-identity-providers" + ] + } + }, + "clientRole": true, + "containerId": "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", + "attributes": {} + }, + { + "id": "156a28de-00d8-4828-9dc9-e09e7841312f", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": ["query-clients"] + } + }, + "clientRole": true, + "containerId": "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", + "attributes": {} + }, + { + "id": "a241d7dd-b028-474a-bdf8-4d33e00c1b90", + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", + "attributes": {} + }, + { + "id": "681e3f7e-bb8c-4e09-a49e-ba8c21f916ff", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", + "attributes": {} + }, + { + "id": "1c5886ad-b354-4246-b288-13ea7635db58", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", + "attributes": {} + }, + { + "id": "7dedf6ff-b715-4f14-85ac-40d0652f153d", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", + "attributes": {} + }, + { + "id": "694721e8-3bf3-47b5-ae38-874db0dc7740", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", + "attributes": {} + } + ], + "dwengo": [], + "security-admin-console": [], + "admin-cli": [], + "account-console": [], + "broker": [ + { + "id": "0cb1b2b5-a751-4f09-ac2f-ea26c398a857", + "name": "read-token", + "description": "${role_read-token}", + "composite": false, + "clientRole": true, + "containerId": "cfd0202e-a6b9-4c5e-9f49-2ef17df9089b", + "attributes": {} + } + ], + "account": [ + { + "id": "d21c51c5-353c-4d78-8c8d-8b8e9f37efa8", + "name": "manage-account-links", + "description": "${role_manage-account-links}", + "composite": false, + "clientRole": true, + "containerId": "7ceb65eb-30da-4dc3-95bc-f06863362fd6", + "attributes": {} + }, + { + "id": "49c8ac02-defa-41af-9e63-2fd24cfc103f", + "name": "view-groups", + "description": "${role_view-groups}", + "composite": false, + "clientRole": true, + "containerId": "7ceb65eb-30da-4dc3-95bc-f06863362fd6", + "attributes": {} + }, + { + "id": "3850c5cc-510a-417b-9976-a1d1d6650804", + "name": "manage-account", + "description": "${role_manage-account}", + "composite": true, + "composites": { + "client": { + "account": ["manage-account-links"] + } + }, + "clientRole": true, + "containerId": "7ceb65eb-30da-4dc3-95bc-f06863362fd6", + "attributes": {} + }, + { + "id": "6554709e-304a-428f-8665-970aacd1dae8", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "7ceb65eb-30da-4dc3-95bc-f06863362fd6", + "attributes": {} + }, + { + "id": "7a0c9d85-daea-4b80-93b5-095e21e5d569", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "7ceb65eb-30da-4dc3-95bc-f06863362fd6", + "attributes": {} + }, + { + "id": "ee2c5cff-1b05-417f-ab3a-a796be754299", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": ["view-consent"] + } + }, + "clientRole": true, + "containerId": "7ceb65eb-30da-4dc3-95bc-f06863362fd6", + "attributes": {} + }, + { + "id": "128fb31d-0784-4b4e-9aa5-82ceb2824fa0", + "name": "view-profile", + "description": "${role_view-profile}", + "composite": false, + "clientRole": true, + "containerId": "7ceb65eb-30da-4dc3-95bc-f06863362fd6", + "attributes": {} + }, + { + "id": "ca850b8d-b75b-4b04-9e42-1e4cc8ab2179", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "7ceb65eb-30da-4dc3-95bc-f06863362fd6", + "attributes": {} + } + ] } - }, - "clientRole" : false, - "containerId" : "02ba6887-22f5-4de4-ad9b-cb2a2060bce1", - "attributes" : { } - }, { - "id" : "747c4433-f128-4f72-b56f-315e7779d4fd", - "name" : "offline_access", - "description" : "${role_offline-access}", - "composite" : false, - "clientRole" : false, - "containerId" : "02ba6887-22f5-4de4-ad9b-cb2a2060bce1", - "attributes" : { } - } ], - "client" : { - "realm-management" : [ { - "id" : "4c8243b1-b576-4cb2-a4f7-3ce25e408fe5", - "name" : "impersonation", - "description" : "${role_impersonation}", - "composite" : false, - "clientRole" : true, - "containerId" : "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", - "attributes" : { } - }, { - "id" : "71fd672b-024b-4d44-b058-03320aeb1842", - "name" : "view-users", - "description" : "${role_view-users}", - "composite" : true, - "composites" : { - "client" : { - "realm-management" : [ "query-groups", "query-users" ] - } + }, + "groups": [], + "defaultRole": { + "id": "6b546a34-4ebe-4c09-b274-fc1f6bebdf93", + "name": "default-roles-teacher", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "02ba6887-22f5-4de4-ad9b-cb2a2060bce1" + }, + "requiredCredentials": ["password"], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpPolicyCodeReusable": false, + "otpSupportedApplications": [ + "totpAppFreeOTPName", + "totpAppGoogleName", + "totpAppMicrosoftAuthenticatorName" + ], + "localizationTexts": {}, + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": ["ES256", "RS256"], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyExtraOrigins": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": ["ES256", "RS256"], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "webAuthnPolicyPasswordlessExtraOrigins": [], + "users": [ + { + "id": "63dbbb64-c09f-4e4e-9cbf-af9e557dbb09", + "username": "testleerkracht1", + "firstName": "Kris", + "lastName": "Coolsaet", + "email": "kris.coolsaet@ugent.be", + "emailVerified": false, + "createdTimestamp": 1740866530658, + "enabled": true, + "totp": false, + "credentials": [ + { + "id": "c5382bf7-ccc6-47de-93b9-2c11ea7b6862", + "type": "password", + "userLabel": "My password", + "createdDate": 1740866544032, + "secretData": "{\"value\":\"H2vKyHF3j/alz6CNap2uaKSRb+/wrWImVecj7dcHe1w=\",\"salt\":\"32WjW1KzFaR5RJqU0Pfq9w==\",\"additionalParameters\":{}}", + "credentialData": "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" + } + ], + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": ["default-roles-teacher"], + "notBefore": 0, + "groups": [] + } + ], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": ["offline_access"] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": ["manage-account", "view-groups"] + } + ] + }, + "clients": [ + { + "id": "7ceb65eb-30da-4dc3-95bc-f06863362fd6", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/teacher/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": ["/realms/teacher/account/*"], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "realm_client": "false", + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "organization", + "microprofile-jwt" + ] }, - "clientRole" : true, - "containerId" : "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", - "attributes" : { } - }, { - "id" : "fea88d42-3065-4600-a5b6-c4e2589e1304", - "name" : "view-events", - "description" : "${role_view-events}", - "composite" : false, - "clientRole" : true, - "containerId" : "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", - "attributes" : { } - }, { - "id" : "6247b5b0-4d41-4fda-900c-3dfc725e03a2", - "name" : "manage-users", - "description" : "${role_manage-users}", - "composite" : false, - "clientRole" : true, - "containerId" : "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", - "attributes" : { } - }, { - "id" : "a3b55a4b-b7f9-4db3-a64f-6ddf80bf74e7", - "name" : "view-authorization", - "description" : "${role_view-authorization}", - "composite" : false, - "clientRole" : true, - "containerId" : "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", - "attributes" : { } - }, { - "id" : "d6714bc8-ff2d-4da0-98b4-2a6479e67954", - "name" : "manage-events", - "description" : "${role_manage-events}", - "composite" : false, - "clientRole" : true, - "containerId" : "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", - "attributes" : { } - }, { - "id" : "d389da82-1730-4c66-9b43-34ac3c8d7f6c", - "name" : "query-realms", - "description" : "${role_query-realms}", - "composite" : false, - "clientRole" : true, - "containerId" : "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", - "attributes" : { } - }, { - "id" : "4dc3905f-311b-4de0-b2e6-a3de50a078a3", - "name" : "query-users", - "description" : "${role_query-users}", - "composite" : false, - "clientRole" : true, - "containerId" : "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", - "attributes" : { } - }, { - "id" : "28ea5d84-4e7d-484e-82fa-c9adcea4ffc0", - "name" : "manage-identity-providers", - "description" : "${role_manage-identity-providers}", - "composite" : false, - "clientRole" : true, - "containerId" : "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", - "attributes" : { } - }, { - "id" : "e020bc9c-f2c9-4023-82eb-b62266749334", - "name" : "query-clients", - "description" : "${role_query-clients}", - "composite" : false, - "clientRole" : true, - "containerId" : "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", - "attributes" : { } - }, { - "id" : "e7373af5-924a-4f01-b34d-55a09aac6c74", - "name" : "manage-clients", - "description" : "${role_manage-clients}", - "composite" : false, - "clientRole" : true, - "containerId" : "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", - "attributes" : { } - }, { - "id" : "0879b6d5-7db6-4c83-8b99-e889028cb13e", - "name" : "manage-realm", - "description" : "${role_manage-realm}", - "composite" : false, - "clientRole" : true, - "containerId" : "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", - "attributes" : { } - }, { - "id" : "ff2c82f3-7f04-4ced-9127-65097e2c16b9", - "name" : "realm-admin", - "description" : "${role_realm-admin}", - "composite" : true, - "composites" : { - "client" : { - "realm-management" : [ "impersonation", "view-users", "view-events", "manage-users", "view-authorization", "query-users", "query-realms", "manage-events", "manage-identity-providers", "query-clients", "manage-realm", "view-clients", "manage-clients", "query-groups", "create-client", "view-realm", "manage-authorization", "view-identity-providers" ] - } + { + "id": "920e8621-36b5-4046-b1cd-4b293668f64b", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/teacher/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": ["/realms/teacher/account/*"], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "realm_client": "false", + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "cd3f4ae0-3008-488b-88c5-b6d640a9edd3", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "organization", + "microprofile-jwt" + ] }, - "clientRole" : true, - "containerId" : "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", - "attributes" : { } - }, { - "id" : "156a28de-00d8-4828-9dc9-e09e7841312f", - "name" : "view-clients", - "description" : "${role_view-clients}", - "composite" : true, - "composites" : { - "client" : { - "realm-management" : [ "query-clients" ] - } + { + "id": "9d7b2827-b7bb-451e-ad38-8f55a69f7c9c", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "realm_client": "false", + "client.use.lightweight.access.token.enabled": "true" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "organization", + "microprofile-jwt" + ] }, - "clientRole" : true, - "containerId" : "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", - "attributes" : { } - }, { - "id" : "a241d7dd-b028-474a-bdf8-4d33e00c1b90", - "name" : "create-client", - "description" : "${role_create-client}", - "composite" : false, - "clientRole" : true, - "containerId" : "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", - "attributes" : { } - }, { - "id" : "681e3f7e-bb8c-4e09-a49e-ba8c21f916ff", - "name" : "query-groups", - "description" : "${role_query-groups}", - "composite" : false, - "clientRole" : true, - "containerId" : "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", - "attributes" : { } - }, { - "id" : "1c5886ad-b354-4246-b288-13ea7635db58", - "name" : "view-realm", - "description" : "${role_view-realm}", - "composite" : false, - "clientRole" : true, - "containerId" : "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", - "attributes" : { } - }, { - "id" : "7dedf6ff-b715-4f14-85ac-40d0652f153d", - "name" : "manage-authorization", - "description" : "${role_manage-authorization}", - "composite" : false, - "clientRole" : true, - "containerId" : "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", - "attributes" : { } - }, { - "id" : "694721e8-3bf3-47b5-ae38-874db0dc7740", - "name" : "view-identity-providers", - "description" : "${role_view-identity-providers}", - "composite" : false, - "clientRole" : true, - "containerId" : "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", - "attributes" : { } - } ], - "dwengo" : [ ], - "security-admin-console" : [ ], - "admin-cli" : [ ], - "account-console" : [ ], - "broker" : [ { - "id" : "0cb1b2b5-a751-4f09-ac2f-ea26c398a857", - "name" : "read-token", - "description" : "${role_read-token}", - "composite" : false, - "clientRole" : true, - "containerId" : "cfd0202e-a6b9-4c5e-9f49-2ef17df9089b", - "attributes" : { } - } ], - "account" : [ { - "id" : "d21c51c5-353c-4d78-8c8d-8b8e9f37efa8", - "name" : "manage-account-links", - "description" : "${role_manage-account-links}", - "composite" : false, - "clientRole" : true, - "containerId" : "7ceb65eb-30da-4dc3-95bc-f06863362fd6", - "attributes" : { } - }, { - "id" : "49c8ac02-defa-41af-9e63-2fd24cfc103f", - "name" : "view-groups", - "description" : "${role_view-groups}", - "composite" : false, - "clientRole" : true, - "containerId" : "7ceb65eb-30da-4dc3-95bc-f06863362fd6", - "attributes" : { } - }, { - "id" : "3850c5cc-510a-417b-9976-a1d1d6650804", - "name" : "manage-account", - "description" : "${role_manage-account}", - "composite" : true, - "composites" : { - "client" : { - "account" : [ "manage-account-links" ] - } + { + "id": "cfd0202e-a6b9-4c5e-9f49-2ef17df9089b", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "realm_client": "true" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "organization", + "microprofile-jwt" + ] }, - "clientRole" : true, - "containerId" : "7ceb65eb-30da-4dc3-95bc-f06863362fd6", - "attributes" : { } - }, { - "id" : "6554709e-304a-428f-8665-970aacd1dae8", - "name" : "view-consent", - "description" : "${role_view-consent}", - "composite" : false, - "clientRole" : true, - "containerId" : "7ceb65eb-30da-4dc3-95bc-f06863362fd6", - "attributes" : { } - }, { - "id" : "7a0c9d85-daea-4b80-93b5-095e21e5d569", - "name" : "delete-account", - "description" : "${role_delete-account}", - "composite" : false, - "clientRole" : true, - "containerId" : "7ceb65eb-30da-4dc3-95bc-f06863362fd6", - "attributes" : { } - }, { - "id" : "ee2c5cff-1b05-417f-ab3a-a796be754299", - "name" : "manage-consent", - "description" : "${role_manage-consent}", - "composite" : true, - "composites" : { - "client" : { - "account" : [ "view-consent" ] - } + { + "id": "abdee18a-4549-48b5-b976-4c1a42820ef9", + "clientId": "dwengo", + "name": "Dwengo", + "description": "", + "rootUrl": "http://localhost:5173", + "adminUrl": "http://localhost:5173", + "baseUrl": "http://localhost:5173", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "urn:ietf:wg:oauth:2.0:oob", + "http://localhost:5173/*", + "http://localhost:5173", + "http://localhost:3000/api-docs/oauth2-redirect.html" + ], + "webOrigins": ["+"], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": true, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": true, + "protocol": "openid-connect", + "attributes": { + "realm_client": "false", + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "true", + "post.logout.redirect.uris": "+", + "frontchannel.logout.session.required": "true", + "oauth2.device.authorization.grant.enabled": "false", + "display.on.consent.screen": "false", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "organization", + "microprofile-jwt" + ] }, - "clientRole" : true, - "containerId" : "7ceb65eb-30da-4dc3-95bc-f06863362fd6", - "attributes" : { } - }, { - "id" : "128fb31d-0784-4b4e-9aa5-82ceb2824fa0", - "name" : "view-profile", - "description" : "${role_view-profile}", - "composite" : false, - "clientRole" : true, - "containerId" : "7ceb65eb-30da-4dc3-95bc-f06863362fd6", - "attributes" : { } - }, { - "id" : "ca850b8d-b75b-4b04-9e42-1e4cc8ab2179", - "name" : "view-applications", - "description" : "${role_view-applications}", - "composite" : false, - "clientRole" : true, - "containerId" : "7ceb65eb-30da-4dc3-95bc-f06863362fd6", - "attributes" : { } - } ] + { + "id": "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "realm_client": "true" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "organization", + "microprofile-jwt" + ] + }, + { + "id": "c421853c-5bdf-4ea9-ae97-51f5ad7b8df8", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/teacher/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": ["/admin/teacher/console/*"], + "webOrigins": ["+"], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "realm_client": "false", + "client.use.lightweight.access.token.enabled": "true", + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "a9a893af-925e-46c9-ba33-47b06101ce5f", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "organization", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "fef4fbeb-d7e6-4474-b802-6c63df0dc9a3", + "name": "saml_organization", + "description": "Organization Membership", + "protocol": "saml", + "attributes": { + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "2384b79b-5cc3-4e1c-b4b2-4bee2ceeed72", + "name": "organization", + "protocol": "saml", + "protocolMapper": "saml-organization-membership-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "a097893c-7eed-4556-b2ed-3751c7fc3c51", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "ffc38cb2-eb10-47cf-a2d6-6647fdd4da65", + "name": "service_account", + "description": "Specific scope for a client enabled for service accounts", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "06ed3629-1c3d-48d9-80c6-98fcd3958c48", + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String" + } + }, + { + "id": "04eeb81e-05c0-484a-91df-9a79138bcd66", + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String" + } + }, + { + "id": "6e673f49-ce38-4583-8040-8a2e7ec5e7c8", + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "client_id", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "client_id", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "ee188d9c-ab26-4e53-a16c-c9f77094f854", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${profileScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "05ff270b-6a50-4bbb-903d-9546a59f20bf", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "394f808d-bc7b-476e-a372-7cfece5c6db0", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + }, + { + "id": "0371c44f-c6e0-4f88-ac8f-17a56e2b90f8", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "21d66073-42f2-443b-aac4-e49c9038253c", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "5cc6a97f-9d1a-4c72-b682-af6d1bd36883", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "d6a6d46b-80a7-4228-af07-0faae2911fed", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "322b508a-7464-4b0f-90df-3f489975a62e", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "f757ae7a-3005-4899-bb4e-da1ab4b47bb0", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "id": "bab8eb17-0cb0-4275-8456-aa1d65933a35", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "long" + } + }, + { + "id": "6ea1d43c-d4c7-4f2f-93b0-dfdb3bb584eb", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "id": "3a2ebc93-05fb-4904-996b-5e3331b72fcd", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "id": "217b417e-d4f6-4225-bf92-3bd38f6fbefb", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "3dd5da51-5842-4358-a69f-f7ffffe521ac", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "790bda99-1c27-4970-b3b9-4fa1c90c738c", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "e6cf59c7-9390-4f48-ab01-79a0fa138960", + "name": "organization", + "description": "Additional claims about the organization a subject belongs to", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${organizationScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "417ff129-6b95-4e95-9f57-a6699ca18d8d", + "name": "organization", + "protocol": "openid-connect", + "protocolMapper": "oidc-organization-membership-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "organization", + "jsonType.label": "String", + "multivalued": "true" + } + } + ] + }, + { + "id": "43d92ef5-76d8-4df0-84b5-5f833875d345", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${emailScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "74d21718-190a-4c53-b446-b07e5f029394", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "949871a0-d68c-4563-a9b3-945a3148f937", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "b07a2014-d07e-450f-a593-66e9f9cf4799", + "name": "acr", + "description": "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "79efdc37-0f06-43e6-a516-7bc9dc29f04d", + "name": "acr loa level", + "protocol": "openid-connect", + "protocolMapper": "oidc-acr-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "3bbbff21-0446-4813-8bdf-54c35d8fffca", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "0e996cda-fe5b-439d-ba4c-cf2129ae812f", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + }, + { + "id": "ddf1efe2-e765-475c-a4a0-d52f1f597834", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "multivalued": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "93a40d0e-f163-42f7-a9d4-53cc2e17914e", + "name": "basic", + "description": "OpenID Connect scope for add all basic claims to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "41eb9e93-8e04-404b-a12b-40ef5a55f640", + "name": "sub", + "protocol": "openid-connect", + "protocolMapper": "oidc-sub-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + }, + { + "id": "1291062a-10f6-4061-b9ea-f54ff5d8ec54", + "name": "auth_time", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "AUTH_TIME", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "auth_time", + "jsonType.label": "long" + } + } + ] + }, + { + "id": "9ea27173-e54b-42f0-8f6c-5a36c5073ede", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "d10a6975-8aeb-4215-8d6b-23b0286d4abb", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "e8a99a5a-1519-4c7d-a3f0-ac6d34c61a0b", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${phoneScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "b2de087f-169f-44b3-ad46-3a063ac9025f", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + }, + { + "id": "ffb8aebd-0d03-4811-8fd4-aa03bda36b2d", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + } + ] + }, + { + "id": "30e06d84-f610-4f17-8820-6f785a510357", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${addressScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "de707a09-a895-4b67-9ac5-0ff4e69715ea", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "introspection.token.claim": "true", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "id": "1762c903-9f07-451c-915d-855488e4aa42", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "consent.screen.text": "", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "0164bdc3-c79d-4467-b6bf-ca9a6889d04c", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "91301d6d-0bb9-4da6-b8db-ee2480e25fee", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "consent.screen.text": "${rolesScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "2880d772-b0da-4ee8-bf1e-3f729a945db9", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "535042c5-58c5-4225-94b8-0b5b3411968e", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "a88432f1-565f-480d-958d-a5cea1dbcf0a", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "saml_organization", + "profile", + "email", + "roles", + "web-origins", + "acr", + "basic" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone", + "microprofile-jwt", + "organization" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "referrerPolicy": "no-referrer", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": {}, + "eventsEnabled": false, + "eventsListeners": ["jboss-logging"], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [], + "identityProviderMappers": [], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "a689e06a-e440-4d94-ba54-692fba5a5486", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": ["200"] + } + }, + { + "id": "2778fda5-0a9f-40ab-ab4b-054ff8ce38e9", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": ["true"] + } + }, + { + "id": "36dc0167-9c9a-4b4a-9f04-29129aecac4d", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-sha256-pairwise-sub-mapper", + "oidc-usermodel-property-mapper", + "saml-user-attribute-mapper", + "oidc-full-name-mapper", + "saml-user-property-mapper", + "oidc-address-mapper", + "saml-role-list-mapper", + "oidc-usermodel-attribute-mapper" + ] + } + }, + { + "id": "4b79c6fd-5166-4bc2-ab0b-bff0018452f6", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "2003600a-89fb-421e-9dfe-d5096ee7fd4e", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-full-name-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-address-mapper", + "oidc-sha256-pairwise-sub-mapper", + "saml-role-list-mapper", + "saml-user-property-mapper", + "saml-user-attribute-mapper", + "oidc-usermodel-property-mapper" + ] + } + }, + { + "id": "d62a2e93-f877-462a-bad3-93dcf91d49d2", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": ["true"], + "client-uris-must-match": ["true"] + } + }, + { + "id": "6e659a80-a638-4504-b507-21b9f77586ed", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "9ef67c59-5c3e-40cf-90ee-516b2e35ed3d", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": ["true"] + } + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "b5365a56-e00d-4612-80bf-262a9c8dba7c", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "privateKey": [ + "MIIEogIBAAKCAQEAudnbEbQij+g9JWYMuyJjF/sKe4fVEd9SrCmkFeAHZ7dEAmEKQcAlvJn1aL99pAm8KV0w9PAZugQx7ZzG6eUm4JLc+LYGJ8G8JDiVR6hIWRQ3k9HGcUwNacHKFlDj4XOeMykwLEo7jrQHAUx82vJO8bKZr2ixZqGc1UUUQNbEVYv0HzxhPKPoFYh9qQ8U6P0r/K9xIusLL6ZzmXx6uXqQuqtse05e5G9xwLjQDDrOKju4s1PJ1GZLt5Db9PGypMeA9J34tGL3O0rQom2WbO7R2GZGX044ZoNw6UnraGTmCxin1ywmwTq3JTb1IZ4DPLH/rucnPuKUb60B5ByPmXW/lQIDAQABAoIBAAlyYrGLbb9RX3xNa+O+O3m+U7nUPXcfWikwo6vN+6pgtScGzjnp3bEwxTnqE+WY7hTPLRwiMTiUmoIYuD6u3HNJW8yTmgv+y8SukJ34FpdakPmlTdg39K2VwWMxgOfWk+nHU/DIZC8chQeinu0VKICeIrQ5Ft1f5SQtEvq5v/iWIql+v2ipxdJ2dSl1NIO0/2S2Lyd7Slab50gbJ3kP0uZggN5IMtNd5GBvAbV+jaT4QWKuyXyHqOnyU/+2WU+XhmVPrX6c29sQg2CilqWf4RzEIeO/FgAiANEPyaAgW6mGtf17K1xsSrusyGMUsNGsGJSd7Q8K2o7g/Jv9V8160iECgYEA2xgKIoZ6+fT7UIDr+5insRr6PIht2WYl+JqibjzNRh+rvp1fjKkmjMOA39V6cvLaAHieSOUerSOD7nQONCedpHe6To1zOG9z5yYGgwa/2c/9eNORJq8vNw/4wXaARVf0mCNaexugPTdYvsSaqwW4+azIbB21xXUfKykLp0SjNfUCgYEA2ShJSZyTIqJ64IHi+Kj/E22ajK2DYBiFgNDmzKrW/ANO5ADumgMhahCcxW28Nw68n25vBWCK4o+6eVg1ldEdB1LxoKOYZAaA+zAiMsGI1/ndxdnlFopuJZguKhYDTmxzT0KcD9mApLKZBnCadGjG3FcdC8i14OK6S9lUIIpCvyECgYBXqWC0u7YMuPatGUhSXJwMAs1I1xWMvJBIziZbkTxY6GchV3pZn3xrKfYwmQvrXjvXoGtEo1gI0oMBL7JXL9qlabpDn9kQJZfsToygdFzi25OBerVDEykDEQLo9W8RT8Xv8YVMaJtOowyBF80CzMFcNMPkbmbCYMBd1ohxHsdm2QKBgGB9RhMvPzFkgLTBAdj7Plujl8hqULWiL6/NIsBOKLhRv/wPbfWA7pfySbZvy/Gq2qT8rNf2zb9dnb3NNAIdqIhYkoSOLGhFe4ohGRD0bZmJrMD80I3zdH2/4MNShKWUCqhtMGraeg60TMpPvlF7POEq0/0ocag7FgwdxQOwa3gBAoGASvjvVtfXyzWA9MycWLvlTPEGW5bjXrroYwulF8DkKfIaKWHfEiulTwQe4SHgS7CzWSg8KgvKIhJC/yTwfOtxZ894G9LWivwjjZCottIE+/qs6ioYSXouQr6IsWxs7U0i3gP36tsePjuSjR06kpBGfcFdynypAAq+mVBCV0Mxk9A=" + ], + "keyUse": ["ENC"], + "certificate": [ + "MIICnTCCAYUCBgGVU7avyzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAd0ZWFjaGVyMB4XDTI1MDMwMTIxNTUzNloXDTM1MDMwMTIxNTcxNlowEjEQMA4GA1UEAwwHdGVhY2hlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALnZ2xG0Io/oPSVmDLsiYxf7CnuH1RHfUqwppBXgB2e3RAJhCkHAJbyZ9Wi/faQJvCldMPTwGboEMe2cxunlJuCS3Pi2BifBvCQ4lUeoSFkUN5PRxnFMDWnByhZQ4+FznjMpMCxKO460BwFMfNryTvGyma9osWahnNVFFEDWxFWL9B88YTyj6BWIfakPFOj9K/yvcSLrCy+mc5l8erl6kLqrbHtOXuRvccC40Aw6zio7uLNTydRmS7eQ2/TxsqTHgPSd+LRi9ztK0KJtlmzu0dhmRl9OOGaDcOlJ62hk5gsYp9csJsE6tyU29SGeAzyx/67nJz7ilG+tAeQcj5l1v5UCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAf3HTufrfWb0uqsEwfUETl4S4IbJOjqDdzFhUkMVtiq5I9LLUlJ7StZ6eoDCEoKUzF2lPy0qR2Om7IKC8BA7J5qUio0NSNh9j/t1Ipcjzx6SQI2cD6AjJFZndnF+OBTxdm9c6J+KMho6ZSMQEGwn2osgRBeItauxUshogQJPY/GzWMHlZyCAJcYtuflzgyw1VIQ0OiWCpCiSGeWpojxh19KR9qSBU1rETZMLokmdp84muq8aqEnNIFY5XRyUdH4gjNBx3TGsammZbvzuZdZIDvFNE19SXl/J9QcWJlRw0DuOblLcLKiamcJkQj35T9DgwtYRc/2zM3u8jNwQXKwrUWA==" + ], + "priority": ["100"], + "algorithm": ["RSA-OAEP"] + } + }, + { + "id": "ce5dcd75-614d-453a-868c-4413b4a10c39", + "name": "hmac-generated-hs512", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "kid": ["a58f2df5-d24b-4aae-9e38-d42736883c7d"], + "secret": [ + "4sDZ4TC6Cuo0-A5Wa42n_HLCxFj6ir4enL6OmdllOTtR7f5YJN5bsPOJXOFGHeuNPe5jgNq2GfOaeqyQ19PnJMd3Ctsj7vQlx57hywXNvQ1FNuKL1uoxF2Szvw65Y4gIM7xoZpQglVhg2Zh7kA3HJEVhDvnmjNdjtm1QgdlFYws" + ], + "priority": ["100"], + "algorithm": ["HS512"] + } + }, + { + "id": "972a70cc-5e9d-4435-8423-f4d32e18d1e7", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "privateKey": [ + "MIIEogIBAAKCAQEAy3EHaNAy6udZMC3A2KIZhMDg0SfGN+FVOKRJfh6aJwtNPuP6EXUhCMRSCXj3EHvFYrBJwKllC8li8MC5rw0vF/P9OY1zdbNkg8Rpwa1D55AS/MlYqiKHazKIV55SXVt5MKtjGATk9D4P/UZ8iP3viBnT0Kws4lWrAv1Uk12CkeLzojeTdCr4I8xgsj5U6dWu2f9DJlsieBhefgJpIXgdWXzVyGC3dOQOrCMHxYdyL4dbBlJPk13QJGkmgH65PnRB0Zu4CtlNHWN1jbXWcRNs8iMLZ4R36F8OzbMcv51lv7+0UTP7HJ4iRjw+sSlUH1AB+3sklyoNgRnW6sisEA+UFQIDAQABAoIBAA9kbWGWQwv31g0poQoi9ZhQOZJJlps6vsZq066ppRMoLT+BYzW37Xhq1iQmVVcXbj9BxErB5kXGhmhdxI7EihgfWzzkAWTZ3lSD41aGg/k8stsSZtV0iFdpetxaO7QZjClNBlHWaPY7zdzlXN3GjL146shChqDXR3mR7ji6HftolGVnmzUXRK+gZG9IirlC+qCJ+sd6m9h83x31X5PRT6yiJ/jeNN4XpuMh61xHFckFOFCGfV2isWM9qL5kLllN1+m8nMjt0HOeEB0GRrHTMSp7QC9RI0z1C/uxdAdSyMhCUtva8jAfjtqYAo7yc63zlOvlkFuYeOQ9X8UmnavBbL8CgYEA7FjyjVr3OK139528/FJMmLk2xOCDQ08pS+ADX5Eib7R62k1ZzXiKnv/8whfdQFeJwIunSYn+y2JCaMFjARrh04SELeH6/CvQ5uCknfIkLeNBij1ye5Ruy7JpaV3oe36h8sJYv1+p5RcrxxINBxbEeKM/YQWRwcXVE54MBCB4dKsCgYEA3FufmasbB4Pna2Txlgo+XCKpf+1U+gcN6lRzsKzqtFVT7+ofUndnqTKgPrLHYytYOFIZIIP8YZBno5gUvK7bFjgAGWacayYNWAXSiEhRQ3ah95Ii/b4lcU095GN/Xu68yqlGQc0GDVD9xRejBNyYgHc2GPQ9bigjsb6pQLy0mj8CgYAN5SjVcKyqM2CjOS3cM8Z3ECSNLJnrAiNuZ4wrOTAqGxVB8lw+PUEBGhG1I4wJdVwO6ub55tgJAwzedcgpT3hJZDgVLn0ACF9uw3RKKOtBm2PGCdjKNS7SYPnbjP7XC9nfmNd44NnvMw6K1J/Zc9g3M3nNbXNlTgk57wfL0lDiowKBgEIF4cf1EGAsEUaINCo0X4LTj92YioFvY6f2LcOdy6TEfCXCDCh1RkXXuVOP1VXNQt19G7I2WYQR9Dt78Zqm+VWq6byyleM0v4LEG9Rhdpe0D8tRqdJFCorsDcNEXIFhHofKOBa3Cz0qKx7Gej2Wqsqy7S6E33MF68vxyFxxLduZAoGAcHhDa8r7EMFTEt3rZIqmblqOYvhfMKJ+Ruom11mUSHWLgyQGzK8mVPhB59J7gt0DKU6XRIwby/7c7x2wFWQ+dsy03PN49PDtewLcGtrsicJlY2mofFZpsFsYhOpyhPg4/zFiX77Ev3UEYiJJ4qXnlV5Yb+ae5D8ZNlmhIP1HQY4=" + ], + "keyUse": ["SIG"], + "certificate": [ + "MIICnTCCAYUCBgGVU7avAzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAd0ZWFjaGVyMB4XDTI1MDMwMTIxNTUzNloXDTM1MDMwMTIxNTcxNlowEjEQMA4GA1UEAwwHdGVhY2hlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMtxB2jQMurnWTAtwNiiGYTA4NEnxjfhVTikSX4emicLTT7j+hF1IQjEUgl49xB7xWKwScCpZQvJYvDAua8NLxfz/TmNc3WzZIPEacGtQ+eQEvzJWKoih2syiFeeUl1beTCrYxgE5PQ+D/1GfIj974gZ09CsLOJVqwL9VJNdgpHi86I3k3Qq+CPMYLI+VOnVrtn/QyZbIngYXn4CaSF4HVl81chgt3TkDqwjB8WHci+HWwZST5Nd0CRpJoB+uT50QdGbuArZTR1jdY211nETbPIjC2eEd+hfDs2zHL+dZb+/tFEz+xyeIkY8PrEpVB9QAft7JJcqDYEZ1urIrBAPlBUCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAHkOqsY1iHqqCMDFvTh/XCNZRwdLdGY6ev+5zShrgb8MPJNgM/HIqQtwQ9wuKf5RfMF1+FQdSU1eavTeuHXp4IuMgv97DAjdZ/pBGHz5tCWMdlaf+Au/1zDoqCV91CbGuV6WHaUhDJLZfp9/phiq2BzPZO6LeWhFJLMzH+N6rPZ7Om72rjTN31TlLLgmLuKlOhMp2QpyaQB16g4ksLGIYq7IXIbCqPRuB33k3gO/+ZMYRpU2U4DQ3FZyIe4LzLXQQ7VSFz/x/rvnbF+hHBdcbszUvsQYCS21aZ6nAq4CGinU2iAOLXHmFotKs+01KZT1N3ZGlGQmHM8ywYyb9qbcfPA==" + ], + "priority": ["100"] + } + }, + { + "id": "24e3094f-f962-49bd-b355-ff3096bfefe8", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "kid": ["52ac32c1-f589-4e04-9667-16d2e7bd707a"], + "secret": ["ZEiWoUCZ30PSKa2rx8UXTQ"], + "priority": ["100"] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "2ac7aebb-c1ac-4fdf-9687-cedd34665024", + "alias": "Account verification options", + "description": "Method with which to verity the existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-email-verification", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false + } + ] + }, + { + "id": "2505f3dc-719b-43a1-9631-585302dd449e", + "alias": "Browser - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "5a07c120-c34b-4cf2-b38d-2e558af6853a", + "alias": "Browser - Conditional Organization", + "description": "Flow to determine if the organization identity-first login is to be used", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "organization", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "a3317f52-b2bc-4b4c-af14-53901d253fca", + "alias": "Direct Grant - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "2281818c-fb40-4997-a1ad-fc9ad2c3cacc", + "alias": "First Broker Login - Conditional Organization", + "description": "Flow to determine if the authenticator that adds organization members is to be used", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "idp-add-organization-member", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "fcab0380-ca38-4f66-aaf2-ec741ef8be8e", + "alias": "First broker login - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "ae2e214a-82b6-4d78-a7d0-f80d454e5083", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Account verification options", + "userSetupAllowed": false + } + ] + }, + { + "id": "ad2add46-e1bb-47bf-a125-d76c517f66a4", + "alias": "Organization", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 10, + "autheticatorFlow": true, + "flowAlias": "Browser - Conditional Organization", + "userSetupAllowed": false + } + ] + }, + { + "id": "74e5d429-4db2-4323-b504-005c03e530fc", + "alias": "Reset - Conditional OTP", + "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "d11dbfe7-2472-4cda-a7f5-e9a536154028", + "alias": "User creation or linking", + "description": "Flow for the existing/non-existing user alternatives", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false + } + ] + }, + { + "id": "f1131dc8-ea34-48e1-9363-438c15f985a4", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "First broker login - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "f2880986-ef01-4199-ac31-35e0b16c989b", + "alias": "browser", + "description": "Browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "identity-provider-redirector", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 25, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 26, + "autheticatorFlow": true, + "flowAlias": "Organization", + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "forms", + "userSetupAllowed": false + } + ] + }, + { + "id": "a08dca2e-d491-483f-a310-25bcfa2d89b3", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-secret-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-x509", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 40, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "4742ab83-03c9-417d-ba61-017d9f02afb3", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "Direct Grant - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "458f78fd-84e5-4e4d-8198-200f25942134", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "8cbdd82f-3794-4fce-9494-70279a3d1fcb", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "User creation or linking", + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 50, + "autheticatorFlow": true, + "flowAlias": "First Broker Login - Conditional Organization", + "userSetupAllowed": false + } + ] + }, + { + "id": "b64919c6-da2b-4e66-bcc6-0112d9e3132b", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Browser - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "3c8979fe-c98c-4911-b16c-510dba8fb8e3", + "alias": "registration", + "description": "Registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": true, + "flowAlias": "registration form", + "userSetupAllowed": false + } + ] + }, + { + "id": "6f598384-bb66-485e-8ed5-7da83c1deba1", + "alias": "registration form", + "description": "Registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-password-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 50, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-recaptcha-action", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 60, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-terms-and-conditions", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 70, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "086acb80-23bb-496d-a982-0d8886b2e844", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-credential-email", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 40, + "autheticatorFlow": true, + "flowAlias": "Reset - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "2b5042d2-f5e2-456c-bd94-1f23ea0bfb20", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "3007c3b0-cdd5-4464-93f4-23e439b15253", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "ce14faa0-34fe-496f-bcb5-a7e72fcf3fbb", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "priority": 10, + "config": {} + }, + { + "alias": "TERMS_AND_CONDITIONS", + "name": "Terms and Conditions", + "providerId": "TERMS_AND_CONDITIONS", + "enabled": false, + "defaultAction": false, + "priority": 20, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "priority": 30, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 40, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 50, + "config": {} + }, + { + "alias": "delete_account", + "name": "Delete Account", + "providerId": "delete_account", + "enabled": false, + "defaultAction": false, + "priority": 60, + "config": {} + }, + { + "alias": "webauthn-register", + "name": "Webauthn Register", + "providerId": "webauthn-register", + "enabled": true, + "defaultAction": false, + "priority": 70, + "config": {} + }, + { + "alias": "webauthn-register-passwordless", + "name": "Webauthn Register Passwordless", + "providerId": "webauthn-register-passwordless", + "enabled": true, + "defaultAction": false, + "priority": 80, + "config": {} + }, + { + "alias": "VERIFY_PROFILE", + "name": "Verify Profile", + "providerId": "VERIFY_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 90, + "config": {} + }, + { + "alias": "delete_credential", + "name": "Delete Credential", + "providerId": "delete_credential", + "enabled": true, + "defaultAction": false, + "priority": 100, + "config": {} + }, + { + "alias": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, + "defaultAction": false, + "priority": 1000, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "firstBrokerLoginFlow": "first broker login", + "attributes": { + "cibaBackchannelTokenDeliveryMode": "poll", + "cibaExpiresIn": "120", + "cibaAuthRequestedUserHint": "login_hint", + "oauth2DeviceCodeLifespan": "600", + "oauth2DevicePollingInterval": "5", + "parRequestUriLifespan": "60", + "cibaInterval": "5", + "realmReusableOtpCode": "false" + }, + "keycloakVersion": "26.1.3", + "userManagedAccessAllowed": false, + "organizationsEnabled": false, + "verifiableCredentialsEnabled": false, + "adminPermissionsEnabled": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] } - }, - "groups" : [ ], - "defaultRole" : { - "id" : "6b546a34-4ebe-4c09-b274-fc1f6bebdf93", - "name" : "default-roles-teacher", - "description" : "${role_default-roles}", - "composite" : true, - "clientRole" : false, - "containerId" : "02ba6887-22f5-4de4-ad9b-cb2a2060bce1" - }, - "requiredCredentials" : [ "password" ], - "otpPolicyType" : "totp", - "otpPolicyAlgorithm" : "HmacSHA1", - "otpPolicyInitialCounter" : 0, - "otpPolicyDigits" : 6, - "otpPolicyLookAheadWindow" : 1, - "otpPolicyPeriod" : 30, - "otpPolicyCodeReusable" : false, - "otpSupportedApplications" : [ "totpAppFreeOTPName", "totpAppGoogleName", "totpAppMicrosoftAuthenticatorName" ], - "localizationTexts" : { }, - "webAuthnPolicyRpEntityName" : "keycloak", - "webAuthnPolicySignatureAlgorithms" : [ "ES256", "RS256" ], - "webAuthnPolicyRpId" : "", - "webAuthnPolicyAttestationConveyancePreference" : "not specified", - "webAuthnPolicyAuthenticatorAttachment" : "not specified", - "webAuthnPolicyRequireResidentKey" : "not specified", - "webAuthnPolicyUserVerificationRequirement" : "not specified", - "webAuthnPolicyCreateTimeout" : 0, - "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, - "webAuthnPolicyAcceptableAaguids" : [ ], - "webAuthnPolicyExtraOrigins" : [ ], - "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", - "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256", "RS256" ], - "webAuthnPolicyPasswordlessRpId" : "", - "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", - "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", - "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified", - "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified", - "webAuthnPolicyPasswordlessCreateTimeout" : 0, - "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, - "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], - "webAuthnPolicyPasswordlessExtraOrigins" : [ ], - "users" : [ { - "id" : "63dbbb64-c09f-4e4e-9cbf-af9e557dbb09", - "username" : "testleerkracht1", - "firstName" : "Kris", - "lastName" : "Coolsaet", - "email" : "kris.coolsaet@ugent.be", - "emailVerified" : false, - "createdTimestamp" : 1740866530658, - "enabled" : true, - "totp" : false, - "credentials" : [ { - "id" : "c5382bf7-ccc6-47de-93b9-2c11ea7b6862", - "type" : "password", - "userLabel" : "My password", - "createdDate" : 1740866544032, - "secretData" : "{\"value\":\"H2vKyHF3j/alz6CNap2uaKSRb+/wrWImVecj7dcHe1w=\",\"salt\":\"32WjW1KzFaR5RJqU0Pfq9w==\",\"additionalParameters\":{}}", - "credentialData" : "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" - } ], - "disableableCredentialTypes" : [ ], - "requiredActions" : [ ], - "realmRoles" : [ "default-roles-teacher" ], - "notBefore" : 0, - "groups" : [ ] - } ], - "scopeMappings" : [ { - "clientScope" : "offline_access", - "roles" : [ "offline_access" ] - } ], - "clientScopeMappings" : { - "account" : [ { - "client" : "account-console", - "roles" : [ "manage-account", "view-groups" ] - } ] - }, - "clients" : [ { - "id" : "7ceb65eb-30da-4dc3-95bc-f06863362fd6", - "clientId" : "account", - "name" : "${client_account}", - "rootUrl" : "${authBaseUrl}", - "baseUrl" : "/realms/teacher/account/", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ "/realms/teacher/account/*" ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "standardFlowEnabled" : true, - "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : false, - "serviceAccountsEnabled" : false, - "publicClient" : true, - "frontchannelLogout" : false, - "protocol" : "openid-connect", - "attributes" : { - "realm_client" : "false", - "post.logout.redirect.uris" : "+" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "organization", "microprofile-jwt" ] - }, { - "id" : "920e8621-36b5-4046-b1cd-4b293668f64b", - "clientId" : "account-console", - "name" : "${client_account-console}", - "rootUrl" : "${authBaseUrl}", - "baseUrl" : "/realms/teacher/account/", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ "/realms/teacher/account/*" ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "standardFlowEnabled" : true, - "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : false, - "serviceAccountsEnabled" : false, - "publicClient" : true, - "frontchannelLogout" : false, - "protocol" : "openid-connect", - "attributes" : { - "realm_client" : "false", - "post.logout.redirect.uris" : "+", - "pkce.code.challenge.method" : "S256" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "cd3f4ae0-3008-488b-88c5-b6d640a9edd3", - "name" : "audience resolve", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-audience-resolve-mapper", - "consentRequired" : false, - "config" : { } - } ], - "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "organization", "microprofile-jwt" ] - }, { - "id" : "9d7b2827-b7bb-451e-ad38-8f55a69f7c9c", - "clientId" : "admin-cli", - "name" : "${client_admin-cli}", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "standardFlowEnabled" : false, - "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : true, - "serviceAccountsEnabled" : false, - "publicClient" : true, - "frontchannelLogout" : false, - "protocol" : "openid-connect", - "attributes" : { - "realm_client" : "false", - "client.use.lightweight.access.token.enabled" : "true" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : true, - "nodeReRegistrationTimeout" : 0, - "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "organization", "microprofile-jwt" ] - }, { - "id" : "cfd0202e-a6b9-4c5e-9f49-2ef17df9089b", - "clientId" : "broker", - "name" : "${client_broker}", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : true, - "consentRequired" : false, - "standardFlowEnabled" : true, - "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : false, - "serviceAccountsEnabled" : false, - "publicClient" : false, - "frontchannelLogout" : false, - "protocol" : "openid-connect", - "attributes" : { - "realm_client" : "true" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "organization", "microprofile-jwt" ] - }, { - "id" : "abdee18a-4549-48b5-b976-4c1a42820ef9", - "clientId" : "dwengo", - "name" : "Dwengo", - "description" : "", - "rootUrl" : "http://localhost:5173", - "adminUrl" : "http://localhost:5173", - "baseUrl" : "http://localhost:5173", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ "urn:ietf:wg:oauth:2.0:oob", "http://localhost:5173/*", "http://localhost:5173", "http://localhost:3000/api-docs/oauth2-redirect.html" ], - "webOrigins" : [ "+" ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "standardFlowEnabled" : true, - "implicitFlowEnabled" : true, - "directAccessGrantsEnabled" : false, - "serviceAccountsEnabled" : false, - "publicClient" : true, - "frontchannelLogout" : true, - "protocol" : "openid-connect", - "attributes" : { - "realm_client" : "false", - "oidc.ciba.grant.enabled" : "false", - "backchannel.logout.session.required" : "true", - "post.logout.redirect.uris" : "+", - "frontchannel.logout.session.required" : "true", - "oauth2.device.authorization.grant.enabled" : "false", - "display.on.consent.screen" : "false", - "backchannel.logout.revoke.offline.tokens" : "false" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : true, - "nodeReRegistrationTimeout" : -1, - "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "organization", "microprofile-jwt" ] - }, { - "id" : "112e0e97-df75-4ed7-a35f-03b7c5f9d36a", - "clientId" : "realm-management", - "name" : "${client_realm-management}", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : true, - "consentRequired" : false, - "standardFlowEnabled" : true, - "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : false, - "serviceAccountsEnabled" : false, - "publicClient" : false, - "frontchannelLogout" : false, - "protocol" : "openid-connect", - "attributes" : { - "realm_client" : "true" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "organization", "microprofile-jwt" ] - }, { - "id" : "c421853c-5bdf-4ea9-ae97-51f5ad7b8df8", - "clientId" : "security-admin-console", - "name" : "${client_security-admin-console}", - "rootUrl" : "${authAdminUrl}", - "baseUrl" : "/admin/teacher/console/", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ "/admin/teacher/console/*" ], - "webOrigins" : [ "+" ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "standardFlowEnabled" : true, - "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : false, - "serviceAccountsEnabled" : false, - "publicClient" : true, - "frontchannelLogout" : false, - "protocol" : "openid-connect", - "attributes" : { - "realm_client" : "false", - "client.use.lightweight.access.token.enabled" : "true", - "post.logout.redirect.uris" : "+", - "pkce.code.challenge.method" : "S256" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : true, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "a9a893af-925e-46c9-ba33-47b06101ce5f", - "name" : "locale", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "locale", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "locale", - "jsonType.label" : "String" - } - } ], - "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "organization", "microprofile-jwt" ] - } ], - "clientScopes" : [ { - "id" : "fef4fbeb-d7e6-4474-b802-6c63df0dc9a3", - "name" : "saml_organization", - "description" : "Organization Membership", - "protocol" : "saml", - "attributes" : { - "display.on.consent.screen" : "false" - }, - "protocolMappers" : [ { - "id" : "2384b79b-5cc3-4e1c-b4b2-4bee2ceeed72", - "name" : "organization", - "protocol" : "saml", - "protocolMapper" : "saml-organization-membership-mapper", - "consentRequired" : false, - "config" : { } - } ] - }, { - "id" : "a097893c-7eed-4556-b2ed-3751c7fc3c51", - "name" : "offline_access", - "description" : "OpenID Connect built-in scope: offline_access", - "protocol" : "openid-connect", - "attributes" : { - "consent.screen.text" : "${offlineAccessScopeConsentText}", - "display.on.consent.screen" : "true" - } - }, { - "id" : "ffc38cb2-eb10-47cf-a2d6-6647fdd4da65", - "name" : "service_account", - "description" : "Specific scope for a client enabled for service accounts", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "false", - "display.on.consent.screen" : "false" - }, - "protocolMappers" : [ { - "id" : "06ed3629-1c3d-48d9-80c6-98fcd3958c48", - "name" : "Client Host", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usersessionmodel-note-mapper", - "consentRequired" : false, - "config" : { - "user.session.note" : "clientHost", - "id.token.claim" : "true", - "introspection.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "clientHost", - "jsonType.label" : "String" - } - }, { - "id" : "04eeb81e-05c0-484a-91df-9a79138bcd66", - "name" : "Client IP Address", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usersessionmodel-note-mapper", - "consentRequired" : false, - "config" : { - "user.session.note" : "clientAddress", - "id.token.claim" : "true", - "introspection.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "clientAddress", - "jsonType.label" : "String" - } - }, { - "id" : "6e673f49-ce38-4583-8040-8a2e7ec5e7c8", - "name" : "Client ID", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usersessionmodel-note-mapper", - "consentRequired" : false, - "config" : { - "user.session.note" : "client_id", - "id.token.claim" : "true", - "introspection.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "client_id", - "jsonType.label" : "String" - } - } ] - }, { - "id" : "ee188d9c-ab26-4e53-a16c-c9f77094f854", - "name" : "profile", - "description" : "OpenID Connect built-in scope: profile", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "consent.screen.text" : "${profileScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "05ff270b-6a50-4bbb-903d-9546a59f20bf", - "name" : "picture", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "picture", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "picture", - "jsonType.label" : "String" - } - }, { - "id" : "394f808d-bc7b-476e-a372-7cfece5c6db0", - "name" : "gender", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "gender", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "gender", - "jsonType.label" : "String" - } - }, { - "id" : "0371c44f-c6e0-4f88-ac8f-17a56e2b90f8", - "name" : "profile", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "profile", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "profile", - "jsonType.label" : "String" - } - }, { - "id" : "21d66073-42f2-443b-aac4-e49c9038253c", - "name" : "birthdate", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "birthdate", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "birthdate", - "jsonType.label" : "String" - } - }, { - "id" : "5cc6a97f-9d1a-4c72-b682-af6d1bd36883", - "name" : "full name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-full-name-mapper", - "consentRequired" : false, - "config" : { - "id.token.claim" : "true", - "introspection.token.claim" : "true", - "access.token.claim" : "true", - "userinfo.token.claim" : "true" - } - }, { - "id" : "d6a6d46b-80a7-4228-af07-0faae2911fed", - "name" : "nickname", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "nickname", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "nickname", - "jsonType.label" : "String" - } - }, { - "id" : "322b508a-7464-4b0f-90df-3f489975a62e", - "name" : "zoneinfo", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "zoneinfo", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "zoneinfo", - "jsonType.label" : "String" - } - }, { - "id" : "f757ae7a-3005-4899-bb4e-da1ab4b47bb0", - "name" : "locale", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "locale", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "locale", - "jsonType.label" : "String" - } - }, { - "id" : "bab8eb17-0cb0-4275-8456-aa1d65933a35", - "name" : "updated at", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "updatedAt", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "updated_at", - "jsonType.label" : "long" - } - }, { - "id" : "6ea1d43c-d4c7-4f2f-93b0-dfdb3bb584eb", - "name" : "given name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "firstName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "given_name", - "jsonType.label" : "String" - } - }, { - "id" : "3a2ebc93-05fb-4904-996b-5e3331b72fcd", - "name" : "family name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "lastName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "family_name", - "jsonType.label" : "String" - } - }, { - "id" : "217b417e-d4f6-4225-bf92-3bd38f6fbefb", - "name" : "username", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "preferred_username", - "jsonType.label" : "String" - } - }, { - "id" : "3dd5da51-5842-4358-a69f-f7ffffe521ac", - "name" : "website", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "website", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "website", - "jsonType.label" : "String" - } - }, { - "id" : "790bda99-1c27-4970-b3b9-4fa1c90c738c", - "name" : "middle name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "middleName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "middle_name", - "jsonType.label" : "String" - } - } ] - }, { - "id" : "e6cf59c7-9390-4f48-ab01-79a0fa138960", - "name" : "organization", - "description" : "Additional claims about the organization a subject belongs to", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "consent.screen.text" : "${organizationScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "417ff129-6b95-4e95-9f57-a6699ca18d8d", - "name" : "organization", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-organization-membership-mapper", - "consentRequired" : false, - "config" : { - "id.token.claim" : "true", - "introspection.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "organization", - "jsonType.label" : "String", - "multivalued" : "true" - } - } ] - }, { - "id" : "43d92ef5-76d8-4df0-84b5-5f833875d345", - "name" : "email", - "description" : "OpenID Connect built-in scope: email", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "consent.screen.text" : "${emailScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "74d21718-190a-4c53-b446-b07e5f029394", - "name" : "email verified", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "emailVerified", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email_verified", - "jsonType.label" : "boolean" - } - }, { - "id" : "949871a0-d68c-4563-a9b3-945a3148f937", - "name" : "email", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "email", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email", - "jsonType.label" : "String" - } - } ] - }, { - "id" : "b07a2014-d07e-450f-a593-66e9f9cf4799", - "name" : "acr", - "description" : "OpenID Connect scope for add acr (authentication context class reference) to the token", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "false", - "display.on.consent.screen" : "false" - }, - "protocolMappers" : [ { - "id" : "79efdc37-0f06-43e6-a516-7bc9dc29f04d", - "name" : "acr loa level", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-acr-mapper", - "consentRequired" : false, - "config" : { - "id.token.claim" : "true", - "introspection.token.claim" : "true", - "access.token.claim" : "true" - } - } ] - }, { - "id" : "3bbbff21-0446-4813-8bdf-54c35d8fffca", - "name" : "microprofile-jwt", - "description" : "Microprofile - JWT built-in scope", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "display.on.consent.screen" : "false" - }, - "protocolMappers" : [ { - "id" : "0e996cda-fe5b-439d-ba4c-cf2129ae812f", - "name" : "upn", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "upn", - "jsonType.label" : "String" - } - }, { - "id" : "ddf1efe2-e765-475c-a4a0-d52f1f597834", - "name" : "groups", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-realm-role-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "multivalued" : "true", - "user.attribute" : "foo", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "groups", - "jsonType.label" : "String" - } - } ] - }, { - "id" : "93a40d0e-f163-42f7-a9d4-53cc2e17914e", - "name" : "basic", - "description" : "OpenID Connect scope for add all basic claims to the token", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "false", - "display.on.consent.screen" : "false" - }, - "protocolMappers" : [ { - "id" : "41eb9e93-8e04-404b-a12b-40ef5a55f640", - "name" : "sub", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-sub-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "access.token.claim" : "true" - } - }, { - "id" : "1291062a-10f6-4061-b9ea-f54ff5d8ec54", - "name" : "auth_time", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usersessionmodel-note-mapper", - "consentRequired" : false, - "config" : { - "user.session.note" : "AUTH_TIME", - "id.token.claim" : "true", - "introspection.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "auth_time", - "jsonType.label" : "long" - } - } ] - }, { - "id" : "9ea27173-e54b-42f0-8f6c-5a36c5073ede", - "name" : "role_list", - "description" : "SAML role list", - "protocol" : "saml", - "attributes" : { - "consent.screen.text" : "${samlRoleListScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "d10a6975-8aeb-4215-8d6b-23b0286d4abb", - "name" : "role list", - "protocol" : "saml", - "protocolMapper" : "saml-role-list-mapper", - "consentRequired" : false, - "config" : { - "single" : "false", - "attribute.nameformat" : "Basic", - "attribute.name" : "Role" - } - } ] - }, { - "id" : "e8a99a5a-1519-4c7d-a3f0-ac6d34c61a0b", - "name" : "phone", - "description" : "OpenID Connect built-in scope: phone", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "consent.screen.text" : "${phoneScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "b2de087f-169f-44b3-ad46-3a063ac9025f", - "name" : "phone number", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "phoneNumber", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "phone_number", - "jsonType.label" : "String" - } - }, { - "id" : "ffb8aebd-0d03-4811-8fd4-aa03bda36b2d", - "name" : "phone number verified", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "phoneNumberVerified", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "phone_number_verified", - "jsonType.label" : "boolean" - } - } ] - }, { - "id" : "30e06d84-f610-4f17-8820-6f785a510357", - "name" : "address", - "description" : "OpenID Connect built-in scope: address", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "consent.screen.text" : "${addressScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "de707a09-a895-4b67-9ac5-0ff4e69715ea", - "name" : "address", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-address-mapper", - "consentRequired" : false, - "config" : { - "user.attribute.formatted" : "formatted", - "user.attribute.country" : "country", - "introspection.token.claim" : "true", - "user.attribute.postal_code" : "postal_code", - "userinfo.token.claim" : "true", - "user.attribute.street" : "street", - "id.token.claim" : "true", - "user.attribute.region" : "region", - "access.token.claim" : "true", - "user.attribute.locality" : "locality" - } - } ] - }, { - "id" : "1762c903-9f07-451c-915d-855488e4aa42", - "name" : "web-origins", - "description" : "OpenID Connect scope for add allowed web origins to the access token", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "false", - "consent.screen.text" : "", - "display.on.consent.screen" : "false" - }, - "protocolMappers" : [ { - "id" : "0164bdc3-c79d-4467-b6bf-ca9a6889d04c", - "name" : "allowed web origins", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-allowed-origins-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "access.token.claim" : "true" - } - } ] - }, { - "id" : "91301d6d-0bb9-4da6-b8db-ee2480e25fee", - "name" : "roles", - "description" : "OpenID Connect scope for add user roles to the access token", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "false", - "consent.screen.text" : "${rolesScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "2880d772-b0da-4ee8-bf1e-3f729a945db9", - "name" : "realm roles", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-realm-role-mapper", - "consentRequired" : false, - "config" : { - "user.attribute" : "foo", - "introspection.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "realm_access.roles", - "jsonType.label" : "String", - "multivalued" : "true" - } - }, { - "id" : "535042c5-58c5-4225-94b8-0b5b3411968e", - "name" : "client roles", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-client-role-mapper", - "consentRequired" : false, - "config" : { - "user.attribute" : "foo", - "introspection.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "resource_access.${client_id}.roles", - "jsonType.label" : "String", - "multivalued" : "true" - } - }, { - "id" : "a88432f1-565f-480d-958d-a5cea1dbcf0a", - "name" : "audience resolve", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-audience-resolve-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "access.token.claim" : "true" - } - } ] - } ], - "defaultDefaultClientScopes" : [ "role_list", "saml_organization", "profile", "email", "roles", "web-origins", "acr", "basic" ], - "defaultOptionalClientScopes" : [ "offline_access", "address", "phone", "microprofile-jwt", "organization" ], - "browserSecurityHeaders" : { - "contentSecurityPolicyReportOnly" : "", - "xContentTypeOptions" : "nosniff", - "referrerPolicy" : "no-referrer", - "xRobotsTag" : "none", - "xFrameOptions" : "SAMEORIGIN", - "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", - "xXSSProtection" : "1; mode=block", - "strictTransportSecurity" : "max-age=31536000; includeSubDomains" - }, - "smtpServer" : { }, - "eventsEnabled" : false, - "eventsListeners" : [ "jboss-logging" ], - "enabledEventTypes" : [ ], - "adminEventsEnabled" : false, - "adminEventsDetailsEnabled" : false, - "identityProviders" : [ ], - "identityProviderMappers" : [ ], - "components" : { - "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { - "id" : "a689e06a-e440-4d94-ba54-692fba5a5486", - "name" : "Max Clients Limit", - "providerId" : "max-clients", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "max-clients" : [ "200" ] - } - }, { - "id" : "2778fda5-0a9f-40ab-ab4b-054ff8ce38e9", - "name" : "Allowed Client Scopes", - "providerId" : "allowed-client-templates", - "subType" : "authenticated", - "subComponents" : { }, - "config" : { - "allow-default-scopes" : [ "true" ] - } - }, { - "id" : "36dc0167-9c9a-4b4a-9f04-29129aecac4d", - "name" : "Allowed Protocol Mapper Types", - "providerId" : "allowed-protocol-mappers", - "subType" : "authenticated", - "subComponents" : { }, - "config" : { - "allowed-protocol-mapper-types" : [ "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-property-mapper", "saml-user-attribute-mapper", "oidc-full-name-mapper", "saml-user-property-mapper", "oidc-address-mapper", "saml-role-list-mapper", "oidc-usermodel-attribute-mapper" ] - } - }, { - "id" : "4b79c6fd-5166-4bc2-ab0b-bff0018452f6", - "name" : "Consent Required", - "providerId" : "consent-required", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { } - }, { - "id" : "2003600a-89fb-421e-9dfe-d5096ee7fd4e", - "name" : "Allowed Protocol Mapper Types", - "providerId" : "allowed-protocol-mappers", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "allowed-protocol-mapper-types" : [ "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-role-list-mapper", "saml-user-property-mapper", "saml-user-attribute-mapper", "oidc-usermodel-property-mapper" ] - } - }, { - "id" : "d62a2e93-f877-462a-bad3-93dcf91d49d2", - "name" : "Trusted Hosts", - "providerId" : "trusted-hosts", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "host-sending-registration-request-must-match" : [ "true" ], - "client-uris-must-match" : [ "true" ] - } - }, { - "id" : "6e659a80-a638-4504-b507-21b9f77586ed", - "name" : "Full Scope Disabled", - "providerId" : "scope", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { } - }, { - "id" : "9ef67c59-5c3e-40cf-90ee-516b2e35ed3d", - "name" : "Allowed Client Scopes", - "providerId" : "allowed-client-templates", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "allow-default-scopes" : [ "true" ] - } - } ], - "org.keycloak.keys.KeyProvider" : [ { - "id" : "b5365a56-e00d-4612-80bf-262a9c8dba7c", - "name" : "rsa-enc-generated", - "providerId" : "rsa-enc-generated", - "subComponents" : { }, - "config" : { - "privateKey" : [ "MIIEogIBAAKCAQEAudnbEbQij+g9JWYMuyJjF/sKe4fVEd9SrCmkFeAHZ7dEAmEKQcAlvJn1aL99pAm8KV0w9PAZugQx7ZzG6eUm4JLc+LYGJ8G8JDiVR6hIWRQ3k9HGcUwNacHKFlDj4XOeMykwLEo7jrQHAUx82vJO8bKZr2ixZqGc1UUUQNbEVYv0HzxhPKPoFYh9qQ8U6P0r/K9xIusLL6ZzmXx6uXqQuqtse05e5G9xwLjQDDrOKju4s1PJ1GZLt5Db9PGypMeA9J34tGL3O0rQom2WbO7R2GZGX044ZoNw6UnraGTmCxin1ywmwTq3JTb1IZ4DPLH/rucnPuKUb60B5ByPmXW/lQIDAQABAoIBAAlyYrGLbb9RX3xNa+O+O3m+U7nUPXcfWikwo6vN+6pgtScGzjnp3bEwxTnqE+WY7hTPLRwiMTiUmoIYuD6u3HNJW8yTmgv+y8SukJ34FpdakPmlTdg39K2VwWMxgOfWk+nHU/DIZC8chQeinu0VKICeIrQ5Ft1f5SQtEvq5v/iWIql+v2ipxdJ2dSl1NIO0/2S2Lyd7Slab50gbJ3kP0uZggN5IMtNd5GBvAbV+jaT4QWKuyXyHqOnyU/+2WU+XhmVPrX6c29sQg2CilqWf4RzEIeO/FgAiANEPyaAgW6mGtf17K1xsSrusyGMUsNGsGJSd7Q8K2o7g/Jv9V8160iECgYEA2xgKIoZ6+fT7UIDr+5insRr6PIht2WYl+JqibjzNRh+rvp1fjKkmjMOA39V6cvLaAHieSOUerSOD7nQONCedpHe6To1zOG9z5yYGgwa/2c/9eNORJq8vNw/4wXaARVf0mCNaexugPTdYvsSaqwW4+azIbB21xXUfKykLp0SjNfUCgYEA2ShJSZyTIqJ64IHi+Kj/E22ajK2DYBiFgNDmzKrW/ANO5ADumgMhahCcxW28Nw68n25vBWCK4o+6eVg1ldEdB1LxoKOYZAaA+zAiMsGI1/ndxdnlFopuJZguKhYDTmxzT0KcD9mApLKZBnCadGjG3FcdC8i14OK6S9lUIIpCvyECgYBXqWC0u7YMuPatGUhSXJwMAs1I1xWMvJBIziZbkTxY6GchV3pZn3xrKfYwmQvrXjvXoGtEo1gI0oMBL7JXL9qlabpDn9kQJZfsToygdFzi25OBerVDEykDEQLo9W8RT8Xv8YVMaJtOowyBF80CzMFcNMPkbmbCYMBd1ohxHsdm2QKBgGB9RhMvPzFkgLTBAdj7Plujl8hqULWiL6/NIsBOKLhRv/wPbfWA7pfySbZvy/Gq2qT8rNf2zb9dnb3NNAIdqIhYkoSOLGhFe4ohGRD0bZmJrMD80I3zdH2/4MNShKWUCqhtMGraeg60TMpPvlF7POEq0/0ocag7FgwdxQOwa3gBAoGASvjvVtfXyzWA9MycWLvlTPEGW5bjXrroYwulF8DkKfIaKWHfEiulTwQe4SHgS7CzWSg8KgvKIhJC/yTwfOtxZ894G9LWivwjjZCottIE+/qs6ioYSXouQr6IsWxs7U0i3gP36tsePjuSjR06kpBGfcFdynypAAq+mVBCV0Mxk9A=" ], - "keyUse" : [ "ENC" ], - "certificate" : [ "MIICnTCCAYUCBgGVU7avyzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAd0ZWFjaGVyMB4XDTI1MDMwMTIxNTUzNloXDTM1MDMwMTIxNTcxNlowEjEQMA4GA1UEAwwHdGVhY2hlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALnZ2xG0Io/oPSVmDLsiYxf7CnuH1RHfUqwppBXgB2e3RAJhCkHAJbyZ9Wi/faQJvCldMPTwGboEMe2cxunlJuCS3Pi2BifBvCQ4lUeoSFkUN5PRxnFMDWnByhZQ4+FznjMpMCxKO460BwFMfNryTvGyma9osWahnNVFFEDWxFWL9B88YTyj6BWIfakPFOj9K/yvcSLrCy+mc5l8erl6kLqrbHtOXuRvccC40Aw6zio7uLNTydRmS7eQ2/TxsqTHgPSd+LRi9ztK0KJtlmzu0dhmRl9OOGaDcOlJ62hk5gsYp9csJsE6tyU29SGeAzyx/67nJz7ilG+tAeQcj5l1v5UCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAf3HTufrfWb0uqsEwfUETl4S4IbJOjqDdzFhUkMVtiq5I9LLUlJ7StZ6eoDCEoKUzF2lPy0qR2Om7IKC8BA7J5qUio0NSNh9j/t1Ipcjzx6SQI2cD6AjJFZndnF+OBTxdm9c6J+KMho6ZSMQEGwn2osgRBeItauxUshogQJPY/GzWMHlZyCAJcYtuflzgyw1VIQ0OiWCpCiSGeWpojxh19KR9qSBU1rETZMLokmdp84muq8aqEnNIFY5XRyUdH4gjNBx3TGsammZbvzuZdZIDvFNE19SXl/J9QcWJlRw0DuOblLcLKiamcJkQj35T9DgwtYRc/2zM3u8jNwQXKwrUWA==" ], - "priority" : [ "100" ], - "algorithm" : [ "RSA-OAEP" ] - } - }, { - "id" : "ce5dcd75-614d-453a-868c-4413b4a10c39", - "name" : "hmac-generated-hs512", - "providerId" : "hmac-generated", - "subComponents" : { }, - "config" : { - "kid" : [ "a58f2df5-d24b-4aae-9e38-d42736883c7d" ], - "secret" : [ "4sDZ4TC6Cuo0-A5Wa42n_HLCxFj6ir4enL6OmdllOTtR7f5YJN5bsPOJXOFGHeuNPe5jgNq2GfOaeqyQ19PnJMd3Ctsj7vQlx57hywXNvQ1FNuKL1uoxF2Szvw65Y4gIM7xoZpQglVhg2Zh7kA3HJEVhDvnmjNdjtm1QgdlFYws" ], - "priority" : [ "100" ], - "algorithm" : [ "HS512" ] - } - }, { - "id" : "972a70cc-5e9d-4435-8423-f4d32e18d1e7", - "name" : "rsa-generated", - "providerId" : "rsa-generated", - "subComponents" : { }, - "config" : { - "privateKey" : [ "MIIEogIBAAKCAQEAy3EHaNAy6udZMC3A2KIZhMDg0SfGN+FVOKRJfh6aJwtNPuP6EXUhCMRSCXj3EHvFYrBJwKllC8li8MC5rw0vF/P9OY1zdbNkg8Rpwa1D55AS/MlYqiKHazKIV55SXVt5MKtjGATk9D4P/UZ8iP3viBnT0Kws4lWrAv1Uk12CkeLzojeTdCr4I8xgsj5U6dWu2f9DJlsieBhefgJpIXgdWXzVyGC3dOQOrCMHxYdyL4dbBlJPk13QJGkmgH65PnRB0Zu4CtlNHWN1jbXWcRNs8iMLZ4R36F8OzbMcv51lv7+0UTP7HJ4iRjw+sSlUH1AB+3sklyoNgRnW6sisEA+UFQIDAQABAoIBAA9kbWGWQwv31g0poQoi9ZhQOZJJlps6vsZq066ppRMoLT+BYzW37Xhq1iQmVVcXbj9BxErB5kXGhmhdxI7EihgfWzzkAWTZ3lSD41aGg/k8stsSZtV0iFdpetxaO7QZjClNBlHWaPY7zdzlXN3GjL146shChqDXR3mR7ji6HftolGVnmzUXRK+gZG9IirlC+qCJ+sd6m9h83x31X5PRT6yiJ/jeNN4XpuMh61xHFckFOFCGfV2isWM9qL5kLllN1+m8nMjt0HOeEB0GRrHTMSp7QC9RI0z1C/uxdAdSyMhCUtva8jAfjtqYAo7yc63zlOvlkFuYeOQ9X8UmnavBbL8CgYEA7FjyjVr3OK139528/FJMmLk2xOCDQ08pS+ADX5Eib7R62k1ZzXiKnv/8whfdQFeJwIunSYn+y2JCaMFjARrh04SELeH6/CvQ5uCknfIkLeNBij1ye5Ruy7JpaV3oe36h8sJYv1+p5RcrxxINBxbEeKM/YQWRwcXVE54MBCB4dKsCgYEA3FufmasbB4Pna2Txlgo+XCKpf+1U+gcN6lRzsKzqtFVT7+ofUndnqTKgPrLHYytYOFIZIIP8YZBno5gUvK7bFjgAGWacayYNWAXSiEhRQ3ah95Ii/b4lcU095GN/Xu68yqlGQc0GDVD9xRejBNyYgHc2GPQ9bigjsb6pQLy0mj8CgYAN5SjVcKyqM2CjOS3cM8Z3ECSNLJnrAiNuZ4wrOTAqGxVB8lw+PUEBGhG1I4wJdVwO6ub55tgJAwzedcgpT3hJZDgVLn0ACF9uw3RKKOtBm2PGCdjKNS7SYPnbjP7XC9nfmNd44NnvMw6K1J/Zc9g3M3nNbXNlTgk57wfL0lDiowKBgEIF4cf1EGAsEUaINCo0X4LTj92YioFvY6f2LcOdy6TEfCXCDCh1RkXXuVOP1VXNQt19G7I2WYQR9Dt78Zqm+VWq6byyleM0v4LEG9Rhdpe0D8tRqdJFCorsDcNEXIFhHofKOBa3Cz0qKx7Gej2Wqsqy7S6E33MF68vxyFxxLduZAoGAcHhDa8r7EMFTEt3rZIqmblqOYvhfMKJ+Ruom11mUSHWLgyQGzK8mVPhB59J7gt0DKU6XRIwby/7c7x2wFWQ+dsy03PN49PDtewLcGtrsicJlY2mofFZpsFsYhOpyhPg4/zFiX77Ev3UEYiJJ4qXnlV5Yb+ae5D8ZNlmhIP1HQY4=" ], - "keyUse" : [ "SIG" ], - "certificate" : [ "MIICnTCCAYUCBgGVU7avAzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAd0ZWFjaGVyMB4XDTI1MDMwMTIxNTUzNloXDTM1MDMwMTIxNTcxNlowEjEQMA4GA1UEAwwHdGVhY2hlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMtxB2jQMurnWTAtwNiiGYTA4NEnxjfhVTikSX4emicLTT7j+hF1IQjEUgl49xB7xWKwScCpZQvJYvDAua8NLxfz/TmNc3WzZIPEacGtQ+eQEvzJWKoih2syiFeeUl1beTCrYxgE5PQ+D/1GfIj974gZ09CsLOJVqwL9VJNdgpHi86I3k3Qq+CPMYLI+VOnVrtn/QyZbIngYXn4CaSF4HVl81chgt3TkDqwjB8WHci+HWwZST5Nd0CRpJoB+uT50QdGbuArZTR1jdY211nETbPIjC2eEd+hfDs2zHL+dZb+/tFEz+xyeIkY8PrEpVB9QAft7JJcqDYEZ1urIrBAPlBUCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAHkOqsY1iHqqCMDFvTh/XCNZRwdLdGY6ev+5zShrgb8MPJNgM/HIqQtwQ9wuKf5RfMF1+FQdSU1eavTeuHXp4IuMgv97DAjdZ/pBGHz5tCWMdlaf+Au/1zDoqCV91CbGuV6WHaUhDJLZfp9/phiq2BzPZO6LeWhFJLMzH+N6rPZ7Om72rjTN31TlLLgmLuKlOhMp2QpyaQB16g4ksLGIYq7IXIbCqPRuB33k3gO/+ZMYRpU2U4DQ3FZyIe4LzLXQQ7VSFz/x/rvnbF+hHBdcbszUvsQYCS21aZ6nAq4CGinU2iAOLXHmFotKs+01KZT1N3ZGlGQmHM8ywYyb9qbcfPA==" ], - "priority" : [ "100" ] - } - }, { - "id" : "24e3094f-f962-49bd-b355-ff3096bfefe8", - "name" : "aes-generated", - "providerId" : "aes-generated", - "subComponents" : { }, - "config" : { - "kid" : [ "52ac32c1-f589-4e04-9667-16d2e7bd707a" ], - "secret" : [ "ZEiWoUCZ30PSKa2rx8UXTQ" ], - "priority" : [ "100" ] - } - } ] - }, - "internationalizationEnabled" : false, - "supportedLocales" : [ ], - "authenticationFlows" : [ { - "id" : "2ac7aebb-c1ac-4fdf-9687-cedd34665024", - "alias" : "Account verification options", - "description" : "Method with which to verity the existing account", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "idp-email-verification", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "ALTERNATIVE", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "Verify Existing Account by Re-authentication", - "userSetupAllowed" : false - } ] - }, { - "id" : "2505f3dc-719b-43a1-9631-585302dd449e", - "alias" : "Browser - Conditional OTP", - "description" : "Flow to determine if the OTP is required for the authentication", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "conditional-user-configured", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "auth-otp-form", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "5a07c120-c34b-4cf2-b38d-2e558af6853a", - "alias" : "Browser - Conditional Organization", - "description" : "Flow to determine if the organization identity-first login is to be used", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "conditional-user-configured", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "organization", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "a3317f52-b2bc-4b4c-af14-53901d253fca", - "alias" : "Direct Grant - Conditional OTP", - "description" : "Flow to determine if the OTP is required for the authentication", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "conditional-user-configured", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "direct-grant-validate-otp", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "2281818c-fb40-4997-a1ad-fc9ad2c3cacc", - "alias" : "First Broker Login - Conditional Organization", - "description" : "Flow to determine if the authenticator that adds organization members is to be used", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "conditional-user-configured", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "idp-add-organization-member", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "fcab0380-ca38-4f66-aaf2-ec741ef8be8e", - "alias" : "First broker login - Conditional OTP", - "description" : "Flow to determine if the OTP is required for the authentication", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "conditional-user-configured", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "auth-otp-form", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "ae2e214a-82b6-4d78-a7d0-f80d454e5083", - "alias" : "Handle Existing Account", - "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "idp-confirm-link", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "Account verification options", - "userSetupAllowed" : false - } ] - }, { - "id" : "ad2add46-e1bb-47bf-a125-d76c517f66a4", - "alias" : "Organization", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticatorFlow" : true, - "requirement" : "CONDITIONAL", - "priority" : 10, - "autheticatorFlow" : true, - "flowAlias" : "Browser - Conditional Organization", - "userSetupAllowed" : false - } ] - }, { - "id" : "74e5d429-4db2-4323-b504-005c03e530fc", - "alias" : "Reset - Conditional OTP", - "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "conditional-user-configured", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "reset-otp", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "d11dbfe7-2472-4cda-a7f5-e9a536154028", - "alias" : "User creation or linking", - "description" : "Flow for the existing/non-existing user alternatives", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticatorConfig" : "create unique user config", - "authenticator" : "idp-create-user-if-unique", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "ALTERNATIVE", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "Handle Existing Account", - "userSetupAllowed" : false - } ] - }, { - "id" : "f1131dc8-ea34-48e1-9363-438c15f985a4", - "alias" : "Verify Existing Account by Re-authentication", - "description" : "Reauthentication of existing account", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "idp-username-password-form", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "CONDITIONAL", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "First broker login - Conditional OTP", - "userSetupAllowed" : false - } ] - }, { - "id" : "f2880986-ef01-4199-ac31-35e0b16c989b", - "alias" : "browser", - "description" : "Browser based authentication", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "auth-cookie", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "auth-spnego", - "authenticatorFlow" : false, - "requirement" : "DISABLED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "identity-provider-redirector", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 25, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "ALTERNATIVE", - "priority" : 26, - "autheticatorFlow" : true, - "flowAlias" : "Organization", - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "ALTERNATIVE", - "priority" : 30, - "autheticatorFlow" : true, - "flowAlias" : "forms", - "userSetupAllowed" : false - } ] - }, { - "id" : "a08dca2e-d491-483f-a310-25bcfa2d89b3", - "alias" : "clients", - "description" : "Base authentication for clients", - "providerId" : "client-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "client-secret", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "client-jwt", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "client-secret-jwt", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 30, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "client-x509", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 40, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "4742ab83-03c9-417d-ba61-017d9f02afb3", - "alias" : "direct grant", - "description" : "OpenID Connect Resource Owner Grant", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "direct-grant-validate-username", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "direct-grant-validate-password", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "CONDITIONAL", - "priority" : 30, - "autheticatorFlow" : true, - "flowAlias" : "Direct Grant - Conditional OTP", - "userSetupAllowed" : false - } ] - }, { - "id" : "458f78fd-84e5-4e4d-8198-200f25942134", - "alias" : "docker auth", - "description" : "Used by Docker clients to authenticate against the IDP", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "docker-http-basic-authenticator", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "8cbdd82f-3794-4fce-9494-70279a3d1fcb", - "alias" : "first broker login", - "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticatorConfig" : "review profile config", - "authenticator" : "idp-review-profile", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "User creation or linking", - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "CONDITIONAL", - "priority" : 50, - "autheticatorFlow" : true, - "flowAlias" : "First Broker Login - Conditional Organization", - "userSetupAllowed" : false - } ] - }, { - "id" : "b64919c6-da2b-4e66-bcc6-0112d9e3132b", - "alias" : "forms", - "description" : "Username, password, otp and other auth forms.", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "auth-username-password-form", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "CONDITIONAL", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "Browser - Conditional OTP", - "userSetupAllowed" : false - } ] - }, { - "id" : "3c8979fe-c98c-4911-b16c-510dba8fb8e3", - "alias" : "registration", - "description" : "Registration flow", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "registration-page-form", - "authenticatorFlow" : true, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : true, - "flowAlias" : "registration form", - "userSetupAllowed" : false - } ] - }, { - "id" : "6f598384-bb66-485e-8ed5-7da83c1deba1", - "alias" : "registration form", - "description" : "Registration form", - "providerId" : "form-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "registration-user-creation", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "registration-password-action", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 50, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "registration-recaptcha-action", - "authenticatorFlow" : false, - "requirement" : "DISABLED", - "priority" : 60, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "registration-terms-and-conditions", - "authenticatorFlow" : false, - "requirement" : "DISABLED", - "priority" : 70, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "086acb80-23bb-496d-a982-0d8886b2e844", - "alias" : "reset credentials", - "description" : "Reset credentials for a user if they forgot their password or something", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "reset-credentials-choose-user", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "reset-credential-email", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "reset-password", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 30, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "CONDITIONAL", - "priority" : 40, - "autheticatorFlow" : true, - "flowAlias" : "Reset - Conditional OTP", - "userSetupAllowed" : false - } ] - }, { - "id" : "2b5042d2-f5e2-456c-bd94-1f23ea0bfb20", - "alias" : "saml ecp", - "description" : "SAML ECP Profile Authentication Flow", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "http-basic-authenticator", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - } ], - "authenticatorConfig" : [ { - "id" : "3007c3b0-cdd5-4464-93f4-23e439b15253", - "alias" : "create unique user config", - "config" : { - "require.password.update.after.registration" : "false" - } - }, { - "id" : "ce14faa0-34fe-496f-bcb5-a7e72fcf3fbb", - "alias" : "review profile config", - "config" : { - "update.profile.on.first.login" : "missing" - } - } ], - "requiredActions" : [ { - "alias" : "CONFIGURE_TOTP", - "name" : "Configure OTP", - "providerId" : "CONFIGURE_TOTP", - "enabled" : true, - "defaultAction" : false, - "priority" : 10, - "config" : { } - }, { - "alias" : "TERMS_AND_CONDITIONS", - "name" : "Terms and Conditions", - "providerId" : "TERMS_AND_CONDITIONS", - "enabled" : false, - "defaultAction" : false, - "priority" : 20, - "config" : { } - }, { - "alias" : "UPDATE_PASSWORD", - "name" : "Update Password", - "providerId" : "UPDATE_PASSWORD", - "enabled" : true, - "defaultAction" : false, - "priority" : 30, - "config" : { } - }, { - "alias" : "UPDATE_PROFILE", - "name" : "Update Profile", - "providerId" : "UPDATE_PROFILE", - "enabled" : true, - "defaultAction" : false, - "priority" : 40, - "config" : { } - }, { - "alias" : "VERIFY_EMAIL", - "name" : "Verify Email", - "providerId" : "VERIFY_EMAIL", - "enabled" : true, - "defaultAction" : false, - "priority" : 50, - "config" : { } - }, { - "alias" : "delete_account", - "name" : "Delete Account", - "providerId" : "delete_account", - "enabled" : false, - "defaultAction" : false, - "priority" : 60, - "config" : { } - }, { - "alias" : "webauthn-register", - "name" : "Webauthn Register", - "providerId" : "webauthn-register", - "enabled" : true, - "defaultAction" : false, - "priority" : 70, - "config" : { } - }, { - "alias" : "webauthn-register-passwordless", - "name" : "Webauthn Register Passwordless", - "providerId" : "webauthn-register-passwordless", - "enabled" : true, - "defaultAction" : false, - "priority" : 80, - "config" : { } - }, { - "alias" : "VERIFY_PROFILE", - "name" : "Verify Profile", - "providerId" : "VERIFY_PROFILE", - "enabled" : true, - "defaultAction" : false, - "priority" : 90, - "config" : { } - }, { - "alias" : "delete_credential", - "name" : "Delete Credential", - "providerId" : "delete_credential", - "enabled" : true, - "defaultAction" : false, - "priority" : 100, - "config" : { } - }, { - "alias" : "update_user_locale", - "name" : "Update User Locale", - "providerId" : "update_user_locale", - "enabled" : true, - "defaultAction" : false, - "priority" : 1000, - "config" : { } - } ], - "browserFlow" : "browser", - "registrationFlow" : "registration", - "directGrantFlow" : "direct grant", - "resetCredentialsFlow" : "reset credentials", - "clientAuthenticationFlow" : "clients", - "dockerAuthenticationFlow" : "docker auth", - "firstBrokerLoginFlow" : "first broker login", - "attributes" : { - "cibaBackchannelTokenDeliveryMode" : "poll", - "cibaExpiresIn" : "120", - "cibaAuthRequestedUserHint" : "login_hint", - "oauth2DeviceCodeLifespan" : "600", - "oauth2DevicePollingInterval" : "5", - "parRequestUriLifespan" : "60", - "cibaInterval" : "5", - "realmReusableOtpCode" : "false" - }, - "keycloakVersion" : "26.1.3", - "userManagedAccessAllowed" : false, - "organizationsEnabled" : false, - "verifiableCredentialsEnabled" : false, - "adminPermissionsEnabled" : false, - "clientProfiles" : { - "profiles" : [ ] - }, - "clientPolicies" : { - "policies" : [ ] - } } From 61d03bc7f2b4c485f16dcc941e2a0bb4d7380f25 Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Tue, 11 Mar 2025 08:25:30 +0100 Subject: [PATCH 044/106] =?UTF-8?q?fix:=20classes=20gedefini=C3=ABerd=20vo?= =?UTF-8?q?ordat=20ze=20gebruikt=20worden?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../content/learning-object.entity.ts | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/backend/src/entities/content/learning-object.entity.ts b/backend/src/entities/content/learning-object.entity.ts index 2b7d7684..9ff12ff0 100644 --- a/backend/src/entities/content/learning-object.entity.ts +++ b/backend/src/entities/content/learning-object.entity.ts @@ -3,6 +3,24 @@ import { Language } from './language.js'; import { Attachment } from './attachment.entity.js'; import { Teacher } from '../users/teacher.entity.js'; +@Embeddable() +export class EducationalGoal { + @Property({ type: 'string' }) + source!: string; + + @Property({ type: 'string' }) + id!: string; +} + +@Embeddable() +export class ReturnValue { + @Property({ type: 'string' }) + callbackUrl!: string; + + @Property({ type: 'json' }) + callbackSchema!: string; +} + @Entity() export class LearningObject { @PrimaryKey({ type: 'string' }) @@ -82,24 +100,6 @@ export class LearningObject { content!: Buffer; } -@Embeddable() -export class EducationalGoal { - @Property({ type: 'string' }) - source!: string; - - @Property({ type: 'string' }) - id!: string; -} - -@Embeddable() -export class ReturnValue { - @Property({ type: 'string' }) - callbackUrl!: string; - - @Property({ type: 'json' }) - callbackSchema!: string; -} - export enum ContentType { Markdown = 'text/markdown', Image = 'image/image', From 2bc59a17d9b7876e120129bbe3aef655136aadde Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Tue, 11 Mar 2025 08:31:43 +0100 Subject: [PATCH 045/106] fix: update package versies @mikro-orm/sqlite moet zelfde versie zijn dan @mikro-orm/core --- backend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/package.json b/backend/package.json index 3267d28f..f94c72cf 100644 --- a/backend/package.json +++ b/backend/package.json @@ -17,7 +17,7 @@ "@mikro-orm/core": "^6.4.6", "@mikro-orm/postgresql": "^6.4.6", "@mikro-orm/reflection": "^6.4.6", - "@mikro-orm/sqlite": "6.4.6", + "@mikro-orm/sqlite": "^6.4.6", "@types/js-yaml": "^4.0.9", "axios": "^1.8.1", "dotenv": "^16.4.7", From a2db3d1191fd27828e9ab81cd3e64620dc817aea Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Tue, 11 Mar 2025 08:45:00 +0100 Subject: [PATCH 046/106] fix: package versies update Alles van mikro-orm is 6.4.6 --- backend/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/package.json b/backend/package.json index f94c72cf..07c3dae2 100644 --- a/backend/package.json +++ b/backend/package.json @@ -14,10 +14,10 @@ "test:unit": "vitest" }, "dependencies": { - "@mikro-orm/core": "^6.4.6", - "@mikro-orm/postgresql": "^6.4.6", - "@mikro-orm/reflection": "^6.4.6", - "@mikro-orm/sqlite": "^6.4.6", + "@mikro-orm/core": "6.4.6", + "@mikro-orm/postgresql": "6.4.6", + "@mikro-orm/reflection": "6.4.6", + "@mikro-orm/sqlite": "6.4.6", "@types/js-yaml": "^4.0.9", "axios": "^1.8.1", "dotenv": "^16.4.7", From d0190cef238c480be177dfe4f0eb9be7c38a626d Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Tue, 11 Mar 2025 08:47:17 +0100 Subject: [PATCH 047/106] fix: package versies update @mikro-orm/cli vergeten bij vorige commit --- backend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/package.json b/backend/package.json index 07c3dae2..4ec84cc0 100644 --- a/backend/package.json +++ b/backend/package.json @@ -30,7 +30,7 @@ "winston-loki": "^6.1.3" }, "devDependencies": { - "@mikro-orm/cli": "^6.4.6", + "@mikro-orm/cli": "6.4.6", "@types/express": "^5.0.0", "@types/node": "^22.13.4", "@types/response-time": "^2.3.8", From 468b371587e490fc2bd8c62e5af7cb87dc28d784 Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Tue, 11 Mar 2025 08:50:34 +0100 Subject: [PATCH 048/106] chore: docker compose update api container depends on db container --- docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 07b88488..72863d00 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,6 +14,8 @@ services: build: context: . dockerfile: ./backend.Dockerfile + depends_on: + - db ports: - '2002:2002' db: From 885021b0b1aac28e70b326313778879e1e643964 Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Tue, 11 Mar 2025 09:22:49 +0100 Subject: [PATCH 049/106] chore: env variables aangepast DB_NAME toegevoegd --- backend/.env.production | 1 + backend/src/util/envvars.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/.env.production b/backend/.env.production index 58694df4..d202a4d9 100644 --- a/backend/.env.production +++ b/backend/.env.production @@ -1,6 +1,7 @@ DWENGO_PORT=3000 DWENGO_DB_HOST=localhost DWENGO_DB_PORT=5431 +DWENGO_DB_NAME=postgres DWENGO_DB_USERNAME=postgres DWENGO_DB_PASSWORD=postgres DWENGO_DB_UPDATE=true diff --git a/backend/src/util/envvars.ts b/backend/src/util/envvars.ts index 6d10e296..d0448bc8 100644 --- a/backend/src/util/envvars.ts +++ b/backend/src/util/envvars.ts @@ -6,7 +6,7 @@ type EnvVar = { key: string; required?: boolean; defaultValue?: any }; 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 }, + DbPort: { key: DB_PREFIX + 'PORT', defaultValue: 5431 }, DbName: { key: DB_PREFIX + 'NAME', defaultValue: 'dwengo' }, DbUsername: { key: DB_PREFIX + 'USERNAME', required: true }, DbPassword: { key: DB_PREFIX + 'PASSWORD', required: true }, From 72cab189a675dc0057ff89eb5cc8db881ce62dff Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Tue, 11 Mar 2025 09:44:38 +0100 Subject: [PATCH 050/106] fix: update env vars DB_PORT aangepast --- backend/.env.production | 2 +- backend/src/util/envvars.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/.env.production b/backend/.env.production index d202a4d9..8ee6113e 100644 --- a/backend/.env.production +++ b/backend/.env.production @@ -1,6 +1,6 @@ DWENGO_PORT=3000 DWENGO_DB_HOST=localhost -DWENGO_DB_PORT=5431 +DWENGO_DB_PORT=5432 DWENGO_DB_NAME=postgres DWENGO_DB_USERNAME=postgres DWENGO_DB_PASSWORD=postgres diff --git a/backend/src/util/envvars.ts b/backend/src/util/envvars.ts index d0448bc8..6d10e296 100644 --- a/backend/src/util/envvars.ts +++ b/backend/src/util/envvars.ts @@ -6,7 +6,7 @@ type EnvVar = { key: string; required?: boolean; defaultValue?: any }; 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: 5431 }, + DbPort: { key: DB_PREFIX + 'PORT', defaultValue: 5432 }, DbName: { key: DB_PREFIX + 'NAME', defaultValue: 'dwengo' }, DbUsername: { key: DB_PREFIX + 'USERNAME', required: true }, DbPassword: { key: DB_PREFIX + 'PASSWORD', required: true }, From edccf4ef623b9c9e023bcb3f51d27e27e254d911 Mon Sep 17 00:00:00 2001 From: Timo De Meyst Date: Tue, 11 Mar 2025 09:45:24 +0100 Subject: [PATCH 051/106] chore: postgresql config toegevoegd postgresql config toegevoegd om voorlopig van alle adressen te luisteren --- config/postgresql/postgresql.conf | 862 ++++++++++++++++++++++++++++++ docker-compose.yml | 1 + 2 files changed, 863 insertions(+) create mode 100644 config/postgresql/postgresql.conf diff --git a/config/postgresql/postgresql.conf b/config/postgresql/postgresql.conf new file mode 100644 index 00000000..25070161 --- /dev/null +++ b/config/postgresql/postgresql.conf @@ -0,0 +1,862 @@ +# ----------------------------- +# PostgreSQL configuration file +# ----------------------------- +# +# This file consists of lines of the form: +# +# name = value +# +# (The "=" is optional.) Whitespace may be used. Comments are introduced with +# "#" anywhere on a line. The complete list of parameter names and allowed +# values can be found in the PostgreSQL documentation. +# +# The commented-out settings shown in this file represent the default values. +# Re-commenting a setting is NOT sufficient to revert it to the default value; +# you need to reload the server. +# +# This file is read on server startup and when the server receives a SIGHUP +# signal. If you edit the file on a running system, you have to SIGHUP the +# server for the changes to take effect, run "pg_ctl reload", or execute +# "SELECT pg_reload_conf()". Some parameters, which are marked below, +# require a server shutdown and restart to take effect. +# +# Any parameter can also be given as a command-line option to the server, e.g., +# "postgres -c log_connections=on". Some parameters can be changed at run time +# with the "SET" SQL command. +# +# Memory units: B = bytes Time units: us = microseconds +# kB = kilobytes ms = milliseconds +# MB = megabytes s = seconds +# GB = gigabytes min = minutes +# TB = terabytes h = hours +# d = days + + +#------------------------------------------------------------------------------ +# FILE LOCATIONS +#------------------------------------------------------------------------------ + +# The default values of these variables are driven from the -D command-line +# option or PGDATA environment variable, represented here as ConfigDir. + +#data_directory = 'ConfigDir' # use data in another directory + # (change requires restart) +#hba_file = 'ConfigDir/pg_hba.conf' # host-based authentication file + # (change requires restart) +#ident_file = 'ConfigDir/pg_ident.conf' # ident configuration file + # (change requires restart) + +# If external_pid_file is not explicitly set, no extra PID file is written. +#external_pid_file = '' # write an extra PID file + # (change requires restart) + + +#------------------------------------------------------------------------------ +# CONNECTIONS AND AUTHENTICATION +#------------------------------------------------------------------------------ + +# - Connection Settings - + +listen_addresses = '*' # what IP address(es) to listen on; + # comma-separated list of addresses; + # defaults to 'localhost'; use '*' for all + # (change requires restart) +port = 5432 # (change requires restart) +#max_connections = 100 # (change requires restart) +#reserved_connections = 0 # (change requires restart) +#superuser_reserved_connections = 3 # (change requires restart) +#unix_socket_directories = '/tmp' # comma-separated list of directories + # (change requires restart) +#unix_socket_group = '' # (change requires restart) +#unix_socket_permissions = 0777 # begin with 0 to use octal notation + # (change requires restart) +#bonjour = off # advertise server via Bonjour + # (change requires restart) +#bonjour_name = '' # defaults to the computer name + # (change requires restart) + +# - TCP settings - +# see "man tcp" for details + +#tcp_keepalives_idle = 0 # TCP_KEEPIDLE, in seconds; + # 0 selects the system default +#tcp_keepalives_interval = 0 # TCP_KEEPINTVL, in seconds; + # 0 selects the system default +#tcp_keepalives_count = 0 # TCP_KEEPCNT; + # 0 selects the system default +#tcp_user_timeout = 0 # TCP_USER_TIMEOUT, in milliseconds; + # 0 selects the system default + +#client_connection_check_interval = 0 # time between checks for client + # disconnection while running queries; + # 0 for never + +# - Authentication - + +#authentication_timeout = 1min # 1s-600s +#password_encryption = scram-sha-256 # scram-sha-256 or md5 +#scram_iterations = 4096 +#md5_password_warnings = on + +# GSSAPI using Kerberos +#krb_server_keyfile = 'FILE:${sysconfdir}/krb5.keytab' +#krb_caseins_users = off +#gss_accept_delegation = off + +# - SSL - + +#ssl = off +#ssl_ca_file = '' +#ssl_cert_file = 'server.crt' +#ssl_crl_file = '' +#ssl_crl_dir = '' +#ssl_key_file = 'server.key' +#ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed TLSv1.2 ciphers +#ssl_tls13_ciphers = '' # allowed TLSv1.3 cipher suites, blank for default +#ssl_prefer_server_ciphers = on +#ssl_groups = 'prime256v1' +#ssl_min_protocol_version = 'TLSv1.2' +#ssl_max_protocol_version = '' +#ssl_dh_params_file = '' +#ssl_passphrase_command = '' +#ssl_passphrase_command_supports_reload = off + +# OAuth +#oauth_validator_libraries = '' # comma-separated list of trusted validator modules + + +#------------------------------------------------------------------------------ +# RESOURCE USAGE (except WAL) +#------------------------------------------------------------------------------ + +# - Memory - + +#shared_buffers = 128MB # min 128kB + # (change requires restart) +#huge_pages = try # on, off, or try + # (change requires restart) +#huge_page_size = 0 # zero for system default + # (change requires restart) +#temp_buffers = 8MB # min 800kB +#max_prepared_transactions = 0 # zero disables the feature + # (change requires restart) +# Caution: it is not advisable to set max_prepared_transactions nonzero unless +# you actively intend to use prepared transactions. +#work_mem = 4MB # min 64kB +#hash_mem_multiplier = 2.0 # 1-1000.0 multiplier on hash table work_mem +#maintenance_work_mem = 64MB # min 64kB +#autovacuum_work_mem = -1 # min 64kB, or -1 to use maintenance_work_mem +#logical_decoding_work_mem = 64MB # min 64kB +#max_stack_depth = 2MB # min 100kB +#shared_memory_type = mmap # the default is the first option + # supported by the operating system: + # mmap + # sysv + # windows + # (change requires restart) +#dynamic_shared_memory_type = posix # the default is usually the first option + # supported by the operating system: + # posix + # sysv + # windows + # mmap + # (change requires restart) +#min_dynamic_shared_memory = 0MB # (change requires restart) +#vacuum_buffer_usage_limit = 2MB # size of vacuum and analyze buffer access strategy ring; + # 0 to disable vacuum buffer access strategy; + # range 128kB to 16GB + +# SLRU buffers (change requires restart) +#commit_timestamp_buffers = 0 # memory for pg_commit_ts (0 = auto) +#multixact_offset_buffers = 16 # memory for pg_multixact/offsets +#multixact_member_buffers = 32 # memory for pg_multixact/members +#notify_buffers = 16 # memory for pg_notify +#serializable_buffers = 32 # memory for pg_serial +#subtransaction_buffers = 0 # memory for pg_subtrans (0 = auto) +#transaction_buffers = 0 # memory for pg_xact (0 = auto) + +# - Disk - + +#temp_file_limit = -1 # limits per-process temp file space + # in kilobytes, or -1 for no limit + +#max_notify_queue_pages = 1048576 # limits the number of SLRU pages allocated + # for NOTIFY / LISTEN queue + +# - Kernel Resources - + +#max_files_per_process = 1000 # min 64 + # (change requires restart) + +# - Background Writer - + +#bgwriter_delay = 200ms # 10-10000ms between rounds +#bgwriter_lru_maxpages = 100 # max buffers written/round, 0 disables +#bgwriter_lru_multiplier = 2.0 # 0-10.0 multiplier on buffers scanned/round +#bgwriter_flush_after = 0 # measured in pages, 0 disables + +# - I/O - + +#backend_flush_after = 0 # measured in pages, 0 disables +#effective_io_concurrency = 1 # 1-1000; 0 disables prefetching +#maintenance_io_concurrency = 10 # 1-1000; 0 disables prefetching +#io_combine_limit = 128kB # usually 1-32 blocks (depends on OS) + +# - Worker Processes - + +#max_worker_processes = 8 # (change requires restart) +#max_parallel_workers_per_gather = 2 # limited by max_parallel_workers +#max_parallel_maintenance_workers = 2 # limited by max_parallel_workers +#max_parallel_workers = 8 # number of max_worker_processes that + # can be used in parallel operations +#parallel_leader_participation = on + + +#------------------------------------------------------------------------------ +# WRITE-AHEAD LOG +#------------------------------------------------------------------------------ + +# - Settings - + +#wal_level = replica # minimal, replica, or logical + # (change requires restart) +#fsync = on # flush data to disk for crash safety + # (turning this off can cause + # unrecoverable data corruption) +#synchronous_commit = on # synchronization level; + # off, local, remote_write, remote_apply, or on +#wal_sync_method = fsync # the default is the first option + # supported by the operating system: + # open_datasync + # fdatasync (default on Linux and FreeBSD) + # fsync + # fsync_writethrough + # open_sync +#full_page_writes = on # recover from partial page writes +#wal_log_hints = off # also do full page writes of non-critical updates + # (change requires restart) +#wal_compression = off # enables compression of full-page writes; + # off, pglz, lz4, zstd, or on +#wal_init_zero = on # zero-fill new WAL files +#wal_recycle = on # recycle WAL files +#wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers + # (change requires restart) +#wal_writer_delay = 200ms # 1-10000 milliseconds +#wal_writer_flush_after = 1MB # measured in pages, 0 disables +#wal_skip_threshold = 2MB + +#commit_delay = 0 # range 0-100000, in microseconds +#commit_siblings = 5 # range 1-1000 + +# - Checkpoints - + +#checkpoint_timeout = 5min # range 30s-1d +#checkpoint_completion_target = 0.9 # checkpoint target duration, 0.0 - 1.0 +#checkpoint_flush_after = 0 # measured in pages, 0 disables +#checkpoint_warning = 30s # 0 disables +#max_wal_size = 1GB +#min_wal_size = 80MB + +# - Prefetching during recovery - + +#recovery_prefetch = try # prefetch pages referenced in the WAL? +#wal_decode_buffer_size = 512kB # lookahead window used for prefetching + # (change requires restart) + +# - Archiving - + +#archive_mode = off # enables archiving; off, on, or always + # (change requires restart) +#archive_library = '' # library to use to archive a WAL file + # (empty string indicates archive_command should + # be used) +#archive_command = '' # command to use to archive a WAL file + # placeholders: %p = path of file to archive + # %f = file name only + # e.g. 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f' +#archive_timeout = 0 # force a WAL file switch after this + # number of seconds; 0 disables + +# - Archive Recovery - + +# These are only used in recovery mode. + +#restore_command = '' # command to use to restore an archived WAL file + # placeholders: %p = path of file to restore + # %f = file name only + # e.g. 'cp /mnt/server/archivedir/%f %p' +#archive_cleanup_command = '' # command to execute at every restartpoint +#recovery_end_command = '' # command to execute at completion of recovery + +# - Recovery Target - + +# Set these only when performing a targeted recovery. + +#recovery_target = '' # 'immediate' to end recovery as soon as a + # consistent state is reached + # (change requires restart) +#recovery_target_name = '' # the named restore point to which recovery will proceed + # (change requires restart) +#recovery_target_time = '' # the time stamp up to which recovery will proceed + # (change requires restart) +#recovery_target_xid = '' # the transaction ID up to which recovery will proceed + # (change requires restart) +#recovery_target_lsn = '' # the WAL LSN up to which recovery will proceed + # (change requires restart) +#recovery_target_inclusive = on # Specifies whether to stop: + # just after the specified recovery target (on) + # just before the recovery target (off) + # (change requires restart) +#recovery_target_timeline = 'latest' # 'current', 'latest', or timeline ID + # (change requires restart) +#recovery_target_action = 'pause' # 'pause', 'promote', 'shutdown' + # (change requires restart) + +# - WAL Summarization - + +#summarize_wal = off # run WAL summarizer process? +#wal_summary_keep_time = '10d' # when to remove old summary files, 0 = never + + +#------------------------------------------------------------------------------ +# REPLICATION +#------------------------------------------------------------------------------ + +# - Sending Servers - + +# Set these on the primary and on any standby that will send replication data. + +#max_wal_senders = 10 # max number of walsender processes + # (change requires restart) +#max_replication_slots = 10 # max number of replication slots + # (change requires restart) +#wal_keep_size = 0 # in megabytes; 0 disables +#max_slot_wal_keep_size = -1 # in megabytes; -1 disables +#idle_replication_slot_timeout = 0 # in minutes; 0 disables +#wal_sender_timeout = 60s # in milliseconds; 0 disables +#track_commit_timestamp = off # collect timestamp of transaction commit + # (change requires restart) + +# - Primary Server - + +# These settings are ignored on a standby server. + +#synchronous_standby_names = '' # standby servers that provide sync rep + # method to choose sync standbys, number of sync standbys, + # and comma-separated list of application_name + # from standby(s); '*' = all +#synchronized_standby_slots = '' # streaming replication standby server slot + # names that logical walsender processes will wait for + +# - Standby Servers - + +# These settings are ignored on a primary server. + +#primary_conninfo = '' # connection string to sending server +#primary_slot_name = '' # replication slot on sending server +#hot_standby = on # "off" disallows queries during recovery + # (change requires restart) +#max_standby_archive_delay = 30s # max delay before canceling queries + # when reading WAL from archive; + # -1 allows indefinite delay +#max_standby_streaming_delay = 30s # max delay before canceling queries + # when reading streaming WAL; + # -1 allows indefinite delay +#wal_receiver_create_temp_slot = off # create temp slot if primary_slot_name + # is not set +#wal_receiver_status_interval = 10s # send replies at least this often + # 0 disables +#hot_standby_feedback = off # send info from standby to prevent + # query conflicts +#wal_receiver_timeout = 60s # time that receiver waits for + # communication from primary + # in milliseconds; 0 disables +#wal_retrieve_retry_interval = 5s # time to wait before retrying to + # retrieve WAL after a failed attempt +#recovery_min_apply_delay = 0 # minimum delay for applying changes during recovery +#sync_replication_slots = off # enables slot synchronization on the physical standby from the primary + +# - Subscribers - + +# These settings are ignored on a publisher. + +#max_logical_replication_workers = 4 # taken from max_worker_processes + # (change requires restart) +#max_sync_workers_per_subscription = 2 # taken from max_logical_replication_workers +#max_parallel_apply_workers_per_subscription = 2 # taken from max_logical_replication_workers + + +#------------------------------------------------------------------------------ +# QUERY TUNING +#------------------------------------------------------------------------------ + +# - Planner Method Configuration - + +#enable_async_append = on +#enable_bitmapscan = on +#enable_gathermerge = on +#enable_hashagg = on +#enable_hashjoin = on +#enable_incremental_sort = on +#enable_indexscan = on +#enable_indexonlyscan = on +#enable_material = on +#enable_memoize = on +#enable_mergejoin = on +#enable_nestloop = on +#enable_parallel_append = on +#enable_parallel_hash = on +#enable_partition_pruning = on +#enable_partitionwise_join = off +#enable_partitionwise_aggregate = off +#enable_presorted_aggregate = on +#enable_seqscan = on +#enable_sort = on +#enable_tidscan = on +#enable_group_by_reordering = on +#enable_distinct_reordering = on + +# - Planner Cost Constants - + +#seq_page_cost = 1.0 # measured on an arbitrary scale +#random_page_cost = 4.0 # same scale as above +#cpu_tuple_cost = 0.01 # same scale as above +#cpu_index_tuple_cost = 0.005 # same scale as above +#cpu_operator_cost = 0.0025 # same scale as above +#parallel_setup_cost = 1000.0 # same scale as above +#parallel_tuple_cost = 0.1 # same scale as above +#min_parallel_table_scan_size = 8MB +#min_parallel_index_scan_size = 512kB +#effective_cache_size = 4GB + +#jit_above_cost = 100000 # perform JIT compilation if available + # and query more expensive than this; + # -1 disables +#jit_inline_above_cost = 500000 # inline small functions if query is + # more expensive than this; -1 disables +#jit_optimize_above_cost = 500000 # use expensive JIT optimizations if + # query is more expensive than this; + # -1 disables + +# - Genetic Query Optimizer - + +#geqo = on +#geqo_threshold = 12 +#geqo_effort = 5 # range 1-10 +#geqo_pool_size = 0 # selects default based on effort +#geqo_generations = 0 # selects default based on effort +#geqo_selection_bias = 2.0 # range 1.5-2.0 +#geqo_seed = 0.0 # range 0.0-1.0 + +# - Other Planner Options - + +#default_statistics_target = 100 # range 1-10000 +#constraint_exclusion = partition # on, off, or partition +#cursor_tuple_fraction = 0.1 # range 0.0-1.0 +#from_collapse_limit = 8 +#jit = on # allow JIT compilation +#join_collapse_limit = 8 # 1 disables collapsing of explicit + # JOIN clauses +#plan_cache_mode = auto # auto, force_generic_plan or + # force_custom_plan +#recursive_worktable_factor = 10.0 # range 0.001-1000000 + + +#------------------------------------------------------------------------------ +# REPORTING AND LOGGING +#------------------------------------------------------------------------------ + +# - Where to Log - + +#log_destination = 'stderr' # Valid values are combinations of + # stderr, csvlog, jsonlog, syslog, and + # eventlog, depending on platform. + # csvlog and jsonlog require + # logging_collector to be on. + +# This is used when logging to stderr: +#logging_collector = off # Enable capturing of stderr, jsonlog, + # and csvlog into log files. Required + # to be on for csvlogs and jsonlogs. + # (change requires restart) + +# These are only used if logging_collector is on: +#log_directory = 'log' # directory where log files are written, + # can be absolute or relative to PGDATA +#log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' # log file name pattern, + # can include strftime() escapes +#log_file_mode = 0600 # creation mode for log files, + # begin with 0 to use octal notation +#log_rotation_age = 1d # Automatic rotation of logfiles will + # happen after that time. 0 disables. +#log_rotation_size = 10MB # Automatic rotation of logfiles will + # happen after that much log output. + # 0 disables. +#log_truncate_on_rotation = off # If on, an existing log file with the + # same name as the new log file will be + # truncated rather than appended to. + # But such truncation only occurs on + # time-driven rotation, not on restarts + # or size-driven rotation. Default is + # off, meaning append to existing files + # in all cases. + +# These are relevant when logging to syslog: +#syslog_facility = 'LOCAL0' +#syslog_ident = 'postgres' +#syslog_sequence_numbers = on +#syslog_split_messages = on + +# This is only relevant when logging to eventlog (Windows): +# (change requires restart) +#event_source = 'PostgreSQL' + +# - When to Log - + +#log_min_messages = warning # values in order of decreasing detail: + # debug5 + # debug4 + # debug3 + # debug2 + # debug1 + # info + # notice + # warning + # error + # log + # fatal + # panic + +#log_min_error_statement = error # values in order of decreasing detail: + # debug5 + # debug4 + # debug3 + # debug2 + # debug1 + # info + # notice + # warning + # error + # log + # fatal + # panic (effectively off) + +#log_min_duration_statement = -1 # -1 is disabled, 0 logs all statements + # and their durations, > 0 logs only + # statements running at least this number + # of milliseconds + +#log_min_duration_sample = -1 # -1 is disabled, 0 logs a sample of statements + # and their durations, > 0 logs only a sample of + # statements running at least this number + # of milliseconds; + # sample fraction is determined by log_statement_sample_rate + +#log_statement_sample_rate = 1.0 # fraction of logged statements exceeding + # log_min_duration_sample to be logged; + # 1.0 logs all such statements, 0.0 never logs + + +#log_transaction_sample_rate = 0.0 # fraction of transactions whose statements + # are logged regardless of their duration; 1.0 logs all + # statements from all transactions, 0.0 never logs + +#log_startup_progress_interval = 10s # Time between progress updates for + # long-running startup operations. + # 0 disables the feature, > 0 indicates + # the interval in milliseconds. + +# - What to Log - + +#debug_print_parse = off +#debug_print_rewritten = off +#debug_print_plan = off +#debug_pretty_print = on +#log_autovacuum_min_duration = 10min # log autovacuum activity; + # -1 disables, 0 logs all actions and + # their durations, > 0 logs only + # actions running at least this number + # of milliseconds. +#log_checkpoints = on +#log_connections = off +#log_disconnections = off +#log_duration = off +#log_error_verbosity = default # terse, default, or verbose messages +#log_hostname = off +#log_line_prefix = '%m [%p] ' # special values: + # %a = application name + # %u = user name + # %d = database name + # %r = remote host and port + # %h = remote host + # %b = backend type + # %p = process ID + # %P = process ID of parallel group leader + # %t = timestamp without milliseconds + # %m = timestamp with milliseconds + # %n = timestamp with milliseconds (as a Unix epoch) + # %Q = query ID (0 if none or not computed) + # %i = command tag + # %e = SQL state + # %c = session ID + # %l = session line number + # %s = session start timestamp + # %v = virtual transaction ID + # %x = transaction ID (0 if none) + # %q = stop here in non-session + # processes + # %% = '%' + # e.g. '<%u%%%d> ' +#log_lock_waits = off # log lock waits >= deadlock_timeout +#log_recovery_conflict_waits = off # log standby recovery conflict waits + # >= deadlock_timeout +#log_parameter_max_length = -1 # when logging statements, limit logged + # bind-parameter values to N bytes; + # -1 means print in full, 0 disables +#log_parameter_max_length_on_error = 0 # when logging an error, limit logged + # bind-parameter values to N bytes; + # -1 means print in full, 0 disables +#log_statement = 'none' # none, ddl, mod, all +#log_replication_commands = off +#log_temp_files = -1 # log temporary files equal or larger + # than the specified size in kilobytes; + # -1 disables, 0 logs all temp files +#log_timezone = 'GMT' + +# - Process Title - + +#cluster_name = '' # added to process titles if nonempty + # (change requires restart) +#update_process_title = on + + +#------------------------------------------------------------------------------ +# STATISTICS +#------------------------------------------------------------------------------ + +# - Cumulative Query and Index Statistics - + +#track_activities = on +#track_activity_query_size = 1024 # (change requires restart) +#track_counts = on +#track_cost_delay_timing = off +#track_io_timing = off +#track_wal_io_timing = off +#track_functions = none # none, pl, all +#stats_fetch_consistency = cache # cache, none, snapshot + + +# - Monitoring - + +#compute_query_id = auto +#log_statement_stats = off +#log_parser_stats = off +#log_planner_stats = off +#log_executor_stats = off + + +#------------------------------------------------------------------------------ +# VACUUMING +#------------------------------------------------------------------------------ + +# - Automatic Vacuuming - + +#autovacuum = on # Enable autovacuum subprocess? 'on' + # requires track_counts to also be on. +autovacuum_worker_slots = 16 # autovacuum worker slots to allocate + # (change requires restart) +#autovacuum_max_workers = 3 # max number of autovacuum subprocesses +#autovacuum_naptime = 1min # time between autovacuum runs +#autovacuum_vacuum_threshold = 50 # min number of row updates before + # vacuum +#autovacuum_vacuum_insert_threshold = 1000 # min number of row inserts + # before vacuum; -1 disables insert + # vacuums +#autovacuum_analyze_threshold = 50 # min number of row updates before + # analyze +#autovacuum_vacuum_scale_factor = 0.2 # fraction of table size before vacuum +#autovacuum_vacuum_insert_scale_factor = 0.2 # fraction of unfrozen pages + # before insert vacuum +#autovacuum_analyze_scale_factor = 0.1 # fraction of table size before analyze +#autovacuum_vacuum_max_threshold = 100000000 # max number of row updates + # before vacuum; -1 disables max + # threshold +#autovacuum_freeze_max_age = 200000000 # maximum XID age before forced vacuum + # (change requires restart) +#autovacuum_multixact_freeze_max_age = 400000000 # maximum multixact age + # before forced vacuum + # (change requires restart) +#autovacuum_vacuum_cost_delay = 2ms # default vacuum cost delay for + # autovacuum, in milliseconds; + # -1 means use vacuum_cost_delay +#autovacuum_vacuum_cost_limit = -1 # default vacuum cost limit for + # autovacuum, -1 means use + # vacuum_cost_limit + +# - Cost-Based Vacuum Delay - + +#vacuum_cost_delay = 0 # 0-100 milliseconds (0 disables) +#vacuum_cost_page_hit = 1 # 0-10000 credits +#vacuum_cost_page_miss = 2 # 0-10000 credits +#vacuum_cost_page_dirty = 20 # 0-10000 credits +#vacuum_cost_limit = 200 # 1-10000 credits + +# - Freezing - + +#vacuum_freeze_table_age = 150000000 +#vacuum_freeze_min_age = 50000000 +#vacuum_failsafe_age = 1600000000 +#vacuum_multixact_freeze_table_age = 150000000 +#vacuum_multixact_freeze_min_age = 5000000 +#vacuum_multixact_failsafe_age = 1600000000 +#vacuum_max_eager_freeze_failure_rate = 0.03 # 0 disables eager scanning + +#------------------------------------------------------------------------------ +# CLIENT CONNECTION DEFAULTS +#------------------------------------------------------------------------------ + +# - Statement Behavior - + +#client_min_messages = notice # values in order of decreasing detail: + # debug5 + # debug4 + # debug3 + # debug2 + # debug1 + # log + # notice + # warning + # error +#search_path = '"$user", public' # schema names +#row_security = on +#default_table_access_method = 'heap' +#default_tablespace = '' # a tablespace name, '' uses the default +#default_toast_compression = 'pglz' # 'pglz' or 'lz4' +#temp_tablespaces = '' # a list of tablespace names, '' uses + # only default tablespace +#check_function_bodies = on +#default_transaction_isolation = 'read committed' +#default_transaction_read_only = off +#default_transaction_deferrable = off +#session_replication_role = 'origin' +#statement_timeout = 0 # in milliseconds, 0 is disabled +#transaction_timeout = 0 # in milliseconds, 0 is disabled +#lock_timeout = 0 # in milliseconds, 0 is disabled +#idle_in_transaction_session_timeout = 0 # in milliseconds, 0 is disabled +#idle_session_timeout = 0 # in milliseconds, 0 is disabled +#bytea_output = 'hex' # hex, escape +#xmlbinary = 'base64' +#xmloption = 'content' +#gin_pending_list_limit = 4MB +#createrole_self_grant = '' # set and/or inherit +#event_triggers = on + +# - Locale and Formatting - + +#datestyle = 'iso, mdy' +#intervalstyle = 'postgres' +#timezone = 'GMT' +#timezone_abbreviations = 'Default' # Select the set of available time zone + # abbreviations. Currently, there are + # Default + # Australia (historical usage) + # India + # You can create your own file in + # share/timezonesets/. +#extra_float_digits = 1 # min -15, max 3; any value >0 actually + # selects precise output mode +#client_encoding = sql_ascii # actually, defaults to database + # encoding + +# These settings are initialized by initdb, but they can be changed. +#lc_messages = '' # locale for system error message + # strings +#lc_monetary = 'C' # locale for monetary formatting +#lc_numeric = 'C' # locale for number formatting +#lc_time = 'C' # locale for time formatting + +#icu_validation_level = warning # report ICU locale validation + # errors at the given level + +# default configuration for text search +#default_text_search_config = 'pg_catalog.simple' + +# - Shared Library Preloading - + +#local_preload_libraries = '' +#session_preload_libraries = '' +#shared_preload_libraries = '' # (change requires restart) +#jit_provider = 'llvmjit' # JIT library to use + +# - Other Defaults - + +#dynamic_library_path = '$libdir' +#gin_fuzzy_search_limit = 0 + + +#------------------------------------------------------------------------------ +# LOCK MANAGEMENT +#------------------------------------------------------------------------------ + +#deadlock_timeout = 1s +#max_locks_per_transaction = 64 # min 10 + # (change requires restart) +#max_pred_locks_per_transaction = 64 # min 10 + # (change requires restart) +#max_pred_locks_per_relation = -2 # negative values mean + # (max_pred_locks_per_transaction + # / -max_pred_locks_per_relation) - 1 +#max_pred_locks_per_page = 2 # min 0 + + +#------------------------------------------------------------------------------ +# VERSION AND PLATFORM COMPATIBILITY +#------------------------------------------------------------------------------ + +# - Previous PostgreSQL Versions - + +#array_nulls = on +#backslash_quote = safe_encoding # on, off, or safe_encoding +#escape_string_warning = on +#lo_compat_privileges = off +#quote_all_identifiers = off +#standard_conforming_strings = on +#synchronize_seqscans = on + +# - Other Platforms and Clients - + +#transform_null_equals = off +#allow_alter_system = on + + +#------------------------------------------------------------------------------ +# ERROR HANDLING +#------------------------------------------------------------------------------ + +#exit_on_error = off # terminate session on any error? +#restart_after_crash = on # reinitialize after backend crash? +#data_sync_retry = off # retry or panic on failure to fsync + # data? + # (change requires restart) +#recovery_init_sync_method = fsync # fsync, syncfs (Linux 5.8+) + + +#------------------------------------------------------------------------------ +# CONFIG FILE INCLUDES +#------------------------------------------------------------------------------ + +# These options allow settings to be loaded from files other than the +# default postgresql.conf. Note that these are directives, not variable +# assignments, so they can usefully be given more than once. + +#include_dir = '...' # include files ending in '.conf' from + # a directory, e.g., 'conf.d' +#include_if_exists = '...' # include file only if it exists +#include = '...' # include file + + +#------------------------------------------------------------------------------ +# CUSTOMIZED OPTIONS +#------------------------------------------------------------------------------ + +# Add settings for extensions here \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 72863d00..d20d819d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,6 +24,7 @@ services: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: postgres + POSTGRES_INITDB_ARGS: -T ./config/postgresql/postgresql.conf ports: - '5431:5432' volumes: From e9cfa41933f1fa8c5fb0d621e40b82d5acbea778 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Tue, 11 Mar 2025 10:42:05 +0100 Subject: [PATCH 052/106] fix: console.log in services/class.ts verwijderd --- backend/src/services/class.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/src/services/class.ts b/backend/src/services/class.ts index a26aa7c2..4e010dda 100644 --- a/backend/src/services/class.ts +++ b/backend/src/services/class.ts @@ -79,8 +79,6 @@ export async function getClassTeacherInvitations( const invitations = await teacherInvitationRepository.findAllInvitationsForClass(cls); - console.log(invitations); - if (!invitations) { return []; } From b3299949b02b3b954e70e0d3315f480adf9689e4 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Tue, 11 Mar 2025 10:44:20 +0100 Subject: [PATCH 053/106] fix: onnodige null check verwijderd in services/class.ts --- backend/src/services/class.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/backend/src/services/class.ts b/backend/src/services/class.ts index 4e010dda..45185383 100644 --- a/backend/src/services/class.ts +++ b/backend/src/services/class.ts @@ -79,10 +79,6 @@ export async function getClassTeacherInvitations( const invitations = await teacherInvitationRepository.findAllInvitationsForClass(cls); - if (!invitations) { - return []; - } - if (full) { return invitations.map(mapToTeacherInvitationDTO); } From 2a7be3270517e6a392364690646c80604fb4625f Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Tue, 11 Mar 2025 12:12:39 +0100 Subject: [PATCH 054/106] feat: voorbeeld test file --- backend/tests/service/learning-paths.test.ts | 32 ++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 backend/tests/service/learning-paths.test.ts diff --git a/backend/tests/service/learning-paths.test.ts b/backend/tests/service/learning-paths.test.ts new file mode 100644 index 00000000..6ac15213 --- /dev/null +++ b/backend/tests/service/learning-paths.test.ts @@ -0,0 +1,32 @@ +import { describe, it, expect, vi } from 'vitest'; +import { fetchLearningPaths } from '../../src/services/learningPaths'; +import { fetchWithLogging } from '../../src/util/apiHelper'; +import { LearningPathResponse } from '../../src/interfaces/learningPath'; + + +describe('fetchLearningPaths', () => { + const mockHruids = ['pn_werking', 'art1']; + const language = 'en'; + const source = 'Test Source'; + + it('✅ Moet een succesvolle response retourneren wanneer hruids zijn opgegeven', async () => { + // Mock response van fetchWithLogging + //const mockResponse = [{ title: 'Test Path', hruids: mockHruids }]; + + const result: LearningPathResponse = await fetchLearningPaths(mockHruids, language, source); + + expect(result.success).toBe(true); + //expect(result.data).toEqual(mockResponse); + expect(result.source).toBe(source); + }); + + it('⚠️ Moet een foutmelding teruggeven als er geen hruids zijn opgegeven', async () => { + const result: LearningPathResponse = await fetchLearningPaths([], language, source); + + expect(result.success).toBe(false); + expect(result.data).toBeNull(); + expect(result.message).toBe(`No HRUIDs provided for ${source}.`); + }); + + +}); From 0053279dc3ac19257d799ba5dd4f6c95914df129 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Tue, 11 Mar 2025 13:50:55 +0100 Subject: [PATCH 055/106] ci: Frontend routing --- backend.Dockerfile | 18 - backend/{.env.test => .env.test.example} | 0 backend/Dockerfile | 35 + backend/package.json | 4 +- config/nginx/nginx.conf | 16 + config/postgresql/postgresql.conf | 862 ----------------------- docker-compose.production.yml | 108 +++ docker-compose.yml | 64 +- frontend.Dockerfile | 17 - frontend/Dockerfile | 35 + nginx/nginx.conf | 49 -- 11 files changed, 248 insertions(+), 960 deletions(-) delete mode 100644 backend.Dockerfile rename backend/{.env.test => .env.test.example} (100%) create mode 100644 backend/Dockerfile create mode 100644 config/nginx/nginx.conf delete mode 100644 config/postgresql/postgresql.conf create mode 100644 docker-compose.production.yml delete mode 100644 frontend.Dockerfile create mode 100644 frontend/Dockerfile delete mode 100644 nginx/nginx.conf diff --git a/backend.Dockerfile b/backend.Dockerfile deleted file mode 100644 index 88be591d..00000000 --- a/backend.Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -FROM node:22 - -WORKDIR /app - -COPY ./backend/package*.json ./ - -RUN npm install - -COPY ./backend ./backend -COPY ./tsconfig.json /app - -WORKDIR /app/backend - -RUN npm run build - -EXPOSE 2002 - -CMD ["npm", "run", "start"] \ No newline at end of file diff --git a/backend/.env.test b/backend/.env.test.example similarity index 100% rename from backend/.env.test rename to backend/.env.test.example diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 00000000..5f26847c --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,35 @@ +FROM node:22 AS build-stage + +WORKDIR /app + +# Install dependencies + +COPY package*.json ./ +COPY backend/package.json ./backend/ + +RUN npm install --silent + +# Build the backend + +# Root tsconfig.json +COPY tsconfig.json ./ + +WORKDIR /app/backend + +COPY backend ./ + +RUN npm run build + +FROM node:22 AS production-stage + +WORKDIR /app + +COPY package-lock.json backend/package.json ./ + +RUN npm install --silent --only=production + +COPY --from=build-stage /app/backend/dist ./dist/ + +EXPOSE 3000 + +CMD ["node", "--env-file=.env", "dist/app.js"] diff --git a/backend/package.json b/backend/package.json index 4ec84cc0..930932fb 100644 --- a/backend/package.json +++ b/backend/package.json @@ -7,7 +7,7 @@ "scripts": { "build": "NODE_ENV=production tsc --project tsconfig.json", "dev": "NODE_ENV=development tsx watch --env-file=.env.development.local src/app.ts", - "start": "NODE_ENV=production node --env-file=.env.production dist/app.js", + "start": "NODE_ENV=production node --env-file=.env dist/app.js", "format": "prettier --write src/", "format-check": "prettier --check src/", "lint": "eslint . --fix", @@ -18,7 +18,6 @@ "@mikro-orm/postgresql": "6.4.6", "@mikro-orm/reflection": "6.4.6", "@mikro-orm/sqlite": "6.4.6", - "@types/js-yaml": "^4.0.9", "axios": "^1.8.1", "dotenv": "^16.4.7", "express": "^5.0.1", @@ -34,6 +33,7 @@ "@types/express": "^5.0.0", "@types/node": "^22.13.4", "@types/response-time": "^2.3.8", + "@types/js-yaml": "^4.0.9", "globals": "^15.15.0", "ts-node": "^10.9.2", "tsx": "^4.19.3", diff --git a/config/nginx/nginx.conf b/config/nginx/nginx.conf new file mode 100644 index 00000000..650a09c9 --- /dev/null +++ b/config/nginx/nginx.conf @@ -0,0 +1,16 @@ +worker_processes auto; + +events { + worker_connections 1024; +} + +http { + server { + listen 80; + + location / { + root /usr/share/nginx/html; + try_files $uri $uri/ /index.html; + } + } +} diff --git a/config/postgresql/postgresql.conf b/config/postgresql/postgresql.conf deleted file mode 100644 index 25070161..00000000 --- a/config/postgresql/postgresql.conf +++ /dev/null @@ -1,862 +0,0 @@ -# ----------------------------- -# PostgreSQL configuration file -# ----------------------------- -# -# This file consists of lines of the form: -# -# name = value -# -# (The "=" is optional.) Whitespace may be used. Comments are introduced with -# "#" anywhere on a line. The complete list of parameter names and allowed -# values can be found in the PostgreSQL documentation. -# -# The commented-out settings shown in this file represent the default values. -# Re-commenting a setting is NOT sufficient to revert it to the default value; -# you need to reload the server. -# -# This file is read on server startup and when the server receives a SIGHUP -# signal. If you edit the file on a running system, you have to SIGHUP the -# server for the changes to take effect, run "pg_ctl reload", or execute -# "SELECT pg_reload_conf()". Some parameters, which are marked below, -# require a server shutdown and restart to take effect. -# -# Any parameter can also be given as a command-line option to the server, e.g., -# "postgres -c log_connections=on". Some parameters can be changed at run time -# with the "SET" SQL command. -# -# Memory units: B = bytes Time units: us = microseconds -# kB = kilobytes ms = milliseconds -# MB = megabytes s = seconds -# GB = gigabytes min = minutes -# TB = terabytes h = hours -# d = days - - -#------------------------------------------------------------------------------ -# FILE LOCATIONS -#------------------------------------------------------------------------------ - -# The default values of these variables are driven from the -D command-line -# option or PGDATA environment variable, represented here as ConfigDir. - -#data_directory = 'ConfigDir' # use data in another directory - # (change requires restart) -#hba_file = 'ConfigDir/pg_hba.conf' # host-based authentication file - # (change requires restart) -#ident_file = 'ConfigDir/pg_ident.conf' # ident configuration file - # (change requires restart) - -# If external_pid_file is not explicitly set, no extra PID file is written. -#external_pid_file = '' # write an extra PID file - # (change requires restart) - - -#------------------------------------------------------------------------------ -# CONNECTIONS AND AUTHENTICATION -#------------------------------------------------------------------------------ - -# - Connection Settings - - -listen_addresses = '*' # what IP address(es) to listen on; - # comma-separated list of addresses; - # defaults to 'localhost'; use '*' for all - # (change requires restart) -port = 5432 # (change requires restart) -#max_connections = 100 # (change requires restart) -#reserved_connections = 0 # (change requires restart) -#superuser_reserved_connections = 3 # (change requires restart) -#unix_socket_directories = '/tmp' # comma-separated list of directories - # (change requires restart) -#unix_socket_group = '' # (change requires restart) -#unix_socket_permissions = 0777 # begin with 0 to use octal notation - # (change requires restart) -#bonjour = off # advertise server via Bonjour - # (change requires restart) -#bonjour_name = '' # defaults to the computer name - # (change requires restart) - -# - TCP settings - -# see "man tcp" for details - -#tcp_keepalives_idle = 0 # TCP_KEEPIDLE, in seconds; - # 0 selects the system default -#tcp_keepalives_interval = 0 # TCP_KEEPINTVL, in seconds; - # 0 selects the system default -#tcp_keepalives_count = 0 # TCP_KEEPCNT; - # 0 selects the system default -#tcp_user_timeout = 0 # TCP_USER_TIMEOUT, in milliseconds; - # 0 selects the system default - -#client_connection_check_interval = 0 # time between checks for client - # disconnection while running queries; - # 0 for never - -# - Authentication - - -#authentication_timeout = 1min # 1s-600s -#password_encryption = scram-sha-256 # scram-sha-256 or md5 -#scram_iterations = 4096 -#md5_password_warnings = on - -# GSSAPI using Kerberos -#krb_server_keyfile = 'FILE:${sysconfdir}/krb5.keytab' -#krb_caseins_users = off -#gss_accept_delegation = off - -# - SSL - - -#ssl = off -#ssl_ca_file = '' -#ssl_cert_file = 'server.crt' -#ssl_crl_file = '' -#ssl_crl_dir = '' -#ssl_key_file = 'server.key' -#ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed TLSv1.2 ciphers -#ssl_tls13_ciphers = '' # allowed TLSv1.3 cipher suites, blank for default -#ssl_prefer_server_ciphers = on -#ssl_groups = 'prime256v1' -#ssl_min_protocol_version = 'TLSv1.2' -#ssl_max_protocol_version = '' -#ssl_dh_params_file = '' -#ssl_passphrase_command = '' -#ssl_passphrase_command_supports_reload = off - -# OAuth -#oauth_validator_libraries = '' # comma-separated list of trusted validator modules - - -#------------------------------------------------------------------------------ -# RESOURCE USAGE (except WAL) -#------------------------------------------------------------------------------ - -# - Memory - - -#shared_buffers = 128MB # min 128kB - # (change requires restart) -#huge_pages = try # on, off, or try - # (change requires restart) -#huge_page_size = 0 # zero for system default - # (change requires restart) -#temp_buffers = 8MB # min 800kB -#max_prepared_transactions = 0 # zero disables the feature - # (change requires restart) -# Caution: it is not advisable to set max_prepared_transactions nonzero unless -# you actively intend to use prepared transactions. -#work_mem = 4MB # min 64kB -#hash_mem_multiplier = 2.0 # 1-1000.0 multiplier on hash table work_mem -#maintenance_work_mem = 64MB # min 64kB -#autovacuum_work_mem = -1 # min 64kB, or -1 to use maintenance_work_mem -#logical_decoding_work_mem = 64MB # min 64kB -#max_stack_depth = 2MB # min 100kB -#shared_memory_type = mmap # the default is the first option - # supported by the operating system: - # mmap - # sysv - # windows - # (change requires restart) -#dynamic_shared_memory_type = posix # the default is usually the first option - # supported by the operating system: - # posix - # sysv - # windows - # mmap - # (change requires restart) -#min_dynamic_shared_memory = 0MB # (change requires restart) -#vacuum_buffer_usage_limit = 2MB # size of vacuum and analyze buffer access strategy ring; - # 0 to disable vacuum buffer access strategy; - # range 128kB to 16GB - -# SLRU buffers (change requires restart) -#commit_timestamp_buffers = 0 # memory for pg_commit_ts (0 = auto) -#multixact_offset_buffers = 16 # memory for pg_multixact/offsets -#multixact_member_buffers = 32 # memory for pg_multixact/members -#notify_buffers = 16 # memory for pg_notify -#serializable_buffers = 32 # memory for pg_serial -#subtransaction_buffers = 0 # memory for pg_subtrans (0 = auto) -#transaction_buffers = 0 # memory for pg_xact (0 = auto) - -# - Disk - - -#temp_file_limit = -1 # limits per-process temp file space - # in kilobytes, or -1 for no limit - -#max_notify_queue_pages = 1048576 # limits the number of SLRU pages allocated - # for NOTIFY / LISTEN queue - -# - Kernel Resources - - -#max_files_per_process = 1000 # min 64 - # (change requires restart) - -# - Background Writer - - -#bgwriter_delay = 200ms # 10-10000ms between rounds -#bgwriter_lru_maxpages = 100 # max buffers written/round, 0 disables -#bgwriter_lru_multiplier = 2.0 # 0-10.0 multiplier on buffers scanned/round -#bgwriter_flush_after = 0 # measured in pages, 0 disables - -# - I/O - - -#backend_flush_after = 0 # measured in pages, 0 disables -#effective_io_concurrency = 1 # 1-1000; 0 disables prefetching -#maintenance_io_concurrency = 10 # 1-1000; 0 disables prefetching -#io_combine_limit = 128kB # usually 1-32 blocks (depends on OS) - -# - Worker Processes - - -#max_worker_processes = 8 # (change requires restart) -#max_parallel_workers_per_gather = 2 # limited by max_parallel_workers -#max_parallel_maintenance_workers = 2 # limited by max_parallel_workers -#max_parallel_workers = 8 # number of max_worker_processes that - # can be used in parallel operations -#parallel_leader_participation = on - - -#------------------------------------------------------------------------------ -# WRITE-AHEAD LOG -#------------------------------------------------------------------------------ - -# - Settings - - -#wal_level = replica # minimal, replica, or logical - # (change requires restart) -#fsync = on # flush data to disk for crash safety - # (turning this off can cause - # unrecoverable data corruption) -#synchronous_commit = on # synchronization level; - # off, local, remote_write, remote_apply, or on -#wal_sync_method = fsync # the default is the first option - # supported by the operating system: - # open_datasync - # fdatasync (default on Linux and FreeBSD) - # fsync - # fsync_writethrough - # open_sync -#full_page_writes = on # recover from partial page writes -#wal_log_hints = off # also do full page writes of non-critical updates - # (change requires restart) -#wal_compression = off # enables compression of full-page writes; - # off, pglz, lz4, zstd, or on -#wal_init_zero = on # zero-fill new WAL files -#wal_recycle = on # recycle WAL files -#wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers - # (change requires restart) -#wal_writer_delay = 200ms # 1-10000 milliseconds -#wal_writer_flush_after = 1MB # measured in pages, 0 disables -#wal_skip_threshold = 2MB - -#commit_delay = 0 # range 0-100000, in microseconds -#commit_siblings = 5 # range 1-1000 - -# - Checkpoints - - -#checkpoint_timeout = 5min # range 30s-1d -#checkpoint_completion_target = 0.9 # checkpoint target duration, 0.0 - 1.0 -#checkpoint_flush_after = 0 # measured in pages, 0 disables -#checkpoint_warning = 30s # 0 disables -#max_wal_size = 1GB -#min_wal_size = 80MB - -# - Prefetching during recovery - - -#recovery_prefetch = try # prefetch pages referenced in the WAL? -#wal_decode_buffer_size = 512kB # lookahead window used for prefetching - # (change requires restart) - -# - Archiving - - -#archive_mode = off # enables archiving; off, on, or always - # (change requires restart) -#archive_library = '' # library to use to archive a WAL file - # (empty string indicates archive_command should - # be used) -#archive_command = '' # command to use to archive a WAL file - # placeholders: %p = path of file to archive - # %f = file name only - # e.g. 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f' -#archive_timeout = 0 # force a WAL file switch after this - # number of seconds; 0 disables - -# - Archive Recovery - - -# These are only used in recovery mode. - -#restore_command = '' # command to use to restore an archived WAL file - # placeholders: %p = path of file to restore - # %f = file name only - # e.g. 'cp /mnt/server/archivedir/%f %p' -#archive_cleanup_command = '' # command to execute at every restartpoint -#recovery_end_command = '' # command to execute at completion of recovery - -# - Recovery Target - - -# Set these only when performing a targeted recovery. - -#recovery_target = '' # 'immediate' to end recovery as soon as a - # consistent state is reached - # (change requires restart) -#recovery_target_name = '' # the named restore point to which recovery will proceed - # (change requires restart) -#recovery_target_time = '' # the time stamp up to which recovery will proceed - # (change requires restart) -#recovery_target_xid = '' # the transaction ID up to which recovery will proceed - # (change requires restart) -#recovery_target_lsn = '' # the WAL LSN up to which recovery will proceed - # (change requires restart) -#recovery_target_inclusive = on # Specifies whether to stop: - # just after the specified recovery target (on) - # just before the recovery target (off) - # (change requires restart) -#recovery_target_timeline = 'latest' # 'current', 'latest', or timeline ID - # (change requires restart) -#recovery_target_action = 'pause' # 'pause', 'promote', 'shutdown' - # (change requires restart) - -# - WAL Summarization - - -#summarize_wal = off # run WAL summarizer process? -#wal_summary_keep_time = '10d' # when to remove old summary files, 0 = never - - -#------------------------------------------------------------------------------ -# REPLICATION -#------------------------------------------------------------------------------ - -# - Sending Servers - - -# Set these on the primary and on any standby that will send replication data. - -#max_wal_senders = 10 # max number of walsender processes - # (change requires restart) -#max_replication_slots = 10 # max number of replication slots - # (change requires restart) -#wal_keep_size = 0 # in megabytes; 0 disables -#max_slot_wal_keep_size = -1 # in megabytes; -1 disables -#idle_replication_slot_timeout = 0 # in minutes; 0 disables -#wal_sender_timeout = 60s # in milliseconds; 0 disables -#track_commit_timestamp = off # collect timestamp of transaction commit - # (change requires restart) - -# - Primary Server - - -# These settings are ignored on a standby server. - -#synchronous_standby_names = '' # standby servers that provide sync rep - # method to choose sync standbys, number of sync standbys, - # and comma-separated list of application_name - # from standby(s); '*' = all -#synchronized_standby_slots = '' # streaming replication standby server slot - # names that logical walsender processes will wait for - -# - Standby Servers - - -# These settings are ignored on a primary server. - -#primary_conninfo = '' # connection string to sending server -#primary_slot_name = '' # replication slot on sending server -#hot_standby = on # "off" disallows queries during recovery - # (change requires restart) -#max_standby_archive_delay = 30s # max delay before canceling queries - # when reading WAL from archive; - # -1 allows indefinite delay -#max_standby_streaming_delay = 30s # max delay before canceling queries - # when reading streaming WAL; - # -1 allows indefinite delay -#wal_receiver_create_temp_slot = off # create temp slot if primary_slot_name - # is not set -#wal_receiver_status_interval = 10s # send replies at least this often - # 0 disables -#hot_standby_feedback = off # send info from standby to prevent - # query conflicts -#wal_receiver_timeout = 60s # time that receiver waits for - # communication from primary - # in milliseconds; 0 disables -#wal_retrieve_retry_interval = 5s # time to wait before retrying to - # retrieve WAL after a failed attempt -#recovery_min_apply_delay = 0 # minimum delay for applying changes during recovery -#sync_replication_slots = off # enables slot synchronization on the physical standby from the primary - -# - Subscribers - - -# These settings are ignored on a publisher. - -#max_logical_replication_workers = 4 # taken from max_worker_processes - # (change requires restart) -#max_sync_workers_per_subscription = 2 # taken from max_logical_replication_workers -#max_parallel_apply_workers_per_subscription = 2 # taken from max_logical_replication_workers - - -#------------------------------------------------------------------------------ -# QUERY TUNING -#------------------------------------------------------------------------------ - -# - Planner Method Configuration - - -#enable_async_append = on -#enable_bitmapscan = on -#enable_gathermerge = on -#enable_hashagg = on -#enable_hashjoin = on -#enable_incremental_sort = on -#enable_indexscan = on -#enable_indexonlyscan = on -#enable_material = on -#enable_memoize = on -#enable_mergejoin = on -#enable_nestloop = on -#enable_parallel_append = on -#enable_parallel_hash = on -#enable_partition_pruning = on -#enable_partitionwise_join = off -#enable_partitionwise_aggregate = off -#enable_presorted_aggregate = on -#enable_seqscan = on -#enable_sort = on -#enable_tidscan = on -#enable_group_by_reordering = on -#enable_distinct_reordering = on - -# - Planner Cost Constants - - -#seq_page_cost = 1.0 # measured on an arbitrary scale -#random_page_cost = 4.0 # same scale as above -#cpu_tuple_cost = 0.01 # same scale as above -#cpu_index_tuple_cost = 0.005 # same scale as above -#cpu_operator_cost = 0.0025 # same scale as above -#parallel_setup_cost = 1000.0 # same scale as above -#parallel_tuple_cost = 0.1 # same scale as above -#min_parallel_table_scan_size = 8MB -#min_parallel_index_scan_size = 512kB -#effective_cache_size = 4GB - -#jit_above_cost = 100000 # perform JIT compilation if available - # and query more expensive than this; - # -1 disables -#jit_inline_above_cost = 500000 # inline small functions if query is - # more expensive than this; -1 disables -#jit_optimize_above_cost = 500000 # use expensive JIT optimizations if - # query is more expensive than this; - # -1 disables - -# - Genetic Query Optimizer - - -#geqo = on -#geqo_threshold = 12 -#geqo_effort = 5 # range 1-10 -#geqo_pool_size = 0 # selects default based on effort -#geqo_generations = 0 # selects default based on effort -#geqo_selection_bias = 2.0 # range 1.5-2.0 -#geqo_seed = 0.0 # range 0.0-1.0 - -# - Other Planner Options - - -#default_statistics_target = 100 # range 1-10000 -#constraint_exclusion = partition # on, off, or partition -#cursor_tuple_fraction = 0.1 # range 0.0-1.0 -#from_collapse_limit = 8 -#jit = on # allow JIT compilation -#join_collapse_limit = 8 # 1 disables collapsing of explicit - # JOIN clauses -#plan_cache_mode = auto # auto, force_generic_plan or - # force_custom_plan -#recursive_worktable_factor = 10.0 # range 0.001-1000000 - - -#------------------------------------------------------------------------------ -# REPORTING AND LOGGING -#------------------------------------------------------------------------------ - -# - Where to Log - - -#log_destination = 'stderr' # Valid values are combinations of - # stderr, csvlog, jsonlog, syslog, and - # eventlog, depending on platform. - # csvlog and jsonlog require - # logging_collector to be on. - -# This is used when logging to stderr: -#logging_collector = off # Enable capturing of stderr, jsonlog, - # and csvlog into log files. Required - # to be on for csvlogs and jsonlogs. - # (change requires restart) - -# These are only used if logging_collector is on: -#log_directory = 'log' # directory where log files are written, - # can be absolute or relative to PGDATA -#log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' # log file name pattern, - # can include strftime() escapes -#log_file_mode = 0600 # creation mode for log files, - # begin with 0 to use octal notation -#log_rotation_age = 1d # Automatic rotation of logfiles will - # happen after that time. 0 disables. -#log_rotation_size = 10MB # Automatic rotation of logfiles will - # happen after that much log output. - # 0 disables. -#log_truncate_on_rotation = off # If on, an existing log file with the - # same name as the new log file will be - # truncated rather than appended to. - # But such truncation only occurs on - # time-driven rotation, not on restarts - # or size-driven rotation. Default is - # off, meaning append to existing files - # in all cases. - -# These are relevant when logging to syslog: -#syslog_facility = 'LOCAL0' -#syslog_ident = 'postgres' -#syslog_sequence_numbers = on -#syslog_split_messages = on - -# This is only relevant when logging to eventlog (Windows): -# (change requires restart) -#event_source = 'PostgreSQL' - -# - When to Log - - -#log_min_messages = warning # values in order of decreasing detail: - # debug5 - # debug4 - # debug3 - # debug2 - # debug1 - # info - # notice - # warning - # error - # log - # fatal - # panic - -#log_min_error_statement = error # values in order of decreasing detail: - # debug5 - # debug4 - # debug3 - # debug2 - # debug1 - # info - # notice - # warning - # error - # log - # fatal - # panic (effectively off) - -#log_min_duration_statement = -1 # -1 is disabled, 0 logs all statements - # and their durations, > 0 logs only - # statements running at least this number - # of milliseconds - -#log_min_duration_sample = -1 # -1 is disabled, 0 logs a sample of statements - # and their durations, > 0 logs only a sample of - # statements running at least this number - # of milliseconds; - # sample fraction is determined by log_statement_sample_rate - -#log_statement_sample_rate = 1.0 # fraction of logged statements exceeding - # log_min_duration_sample to be logged; - # 1.0 logs all such statements, 0.0 never logs - - -#log_transaction_sample_rate = 0.0 # fraction of transactions whose statements - # are logged regardless of their duration; 1.0 logs all - # statements from all transactions, 0.0 never logs - -#log_startup_progress_interval = 10s # Time between progress updates for - # long-running startup operations. - # 0 disables the feature, > 0 indicates - # the interval in milliseconds. - -# - What to Log - - -#debug_print_parse = off -#debug_print_rewritten = off -#debug_print_plan = off -#debug_pretty_print = on -#log_autovacuum_min_duration = 10min # log autovacuum activity; - # -1 disables, 0 logs all actions and - # their durations, > 0 logs only - # actions running at least this number - # of milliseconds. -#log_checkpoints = on -#log_connections = off -#log_disconnections = off -#log_duration = off -#log_error_verbosity = default # terse, default, or verbose messages -#log_hostname = off -#log_line_prefix = '%m [%p] ' # special values: - # %a = application name - # %u = user name - # %d = database name - # %r = remote host and port - # %h = remote host - # %b = backend type - # %p = process ID - # %P = process ID of parallel group leader - # %t = timestamp without milliseconds - # %m = timestamp with milliseconds - # %n = timestamp with milliseconds (as a Unix epoch) - # %Q = query ID (0 if none or not computed) - # %i = command tag - # %e = SQL state - # %c = session ID - # %l = session line number - # %s = session start timestamp - # %v = virtual transaction ID - # %x = transaction ID (0 if none) - # %q = stop here in non-session - # processes - # %% = '%' - # e.g. '<%u%%%d> ' -#log_lock_waits = off # log lock waits >= deadlock_timeout -#log_recovery_conflict_waits = off # log standby recovery conflict waits - # >= deadlock_timeout -#log_parameter_max_length = -1 # when logging statements, limit logged - # bind-parameter values to N bytes; - # -1 means print in full, 0 disables -#log_parameter_max_length_on_error = 0 # when logging an error, limit logged - # bind-parameter values to N bytes; - # -1 means print in full, 0 disables -#log_statement = 'none' # none, ddl, mod, all -#log_replication_commands = off -#log_temp_files = -1 # log temporary files equal or larger - # than the specified size in kilobytes; - # -1 disables, 0 logs all temp files -#log_timezone = 'GMT' - -# - Process Title - - -#cluster_name = '' # added to process titles if nonempty - # (change requires restart) -#update_process_title = on - - -#------------------------------------------------------------------------------ -# STATISTICS -#------------------------------------------------------------------------------ - -# - Cumulative Query and Index Statistics - - -#track_activities = on -#track_activity_query_size = 1024 # (change requires restart) -#track_counts = on -#track_cost_delay_timing = off -#track_io_timing = off -#track_wal_io_timing = off -#track_functions = none # none, pl, all -#stats_fetch_consistency = cache # cache, none, snapshot - - -# - Monitoring - - -#compute_query_id = auto -#log_statement_stats = off -#log_parser_stats = off -#log_planner_stats = off -#log_executor_stats = off - - -#------------------------------------------------------------------------------ -# VACUUMING -#------------------------------------------------------------------------------ - -# - Automatic Vacuuming - - -#autovacuum = on # Enable autovacuum subprocess? 'on' - # requires track_counts to also be on. -autovacuum_worker_slots = 16 # autovacuum worker slots to allocate - # (change requires restart) -#autovacuum_max_workers = 3 # max number of autovacuum subprocesses -#autovacuum_naptime = 1min # time between autovacuum runs -#autovacuum_vacuum_threshold = 50 # min number of row updates before - # vacuum -#autovacuum_vacuum_insert_threshold = 1000 # min number of row inserts - # before vacuum; -1 disables insert - # vacuums -#autovacuum_analyze_threshold = 50 # min number of row updates before - # analyze -#autovacuum_vacuum_scale_factor = 0.2 # fraction of table size before vacuum -#autovacuum_vacuum_insert_scale_factor = 0.2 # fraction of unfrozen pages - # before insert vacuum -#autovacuum_analyze_scale_factor = 0.1 # fraction of table size before analyze -#autovacuum_vacuum_max_threshold = 100000000 # max number of row updates - # before vacuum; -1 disables max - # threshold -#autovacuum_freeze_max_age = 200000000 # maximum XID age before forced vacuum - # (change requires restart) -#autovacuum_multixact_freeze_max_age = 400000000 # maximum multixact age - # before forced vacuum - # (change requires restart) -#autovacuum_vacuum_cost_delay = 2ms # default vacuum cost delay for - # autovacuum, in milliseconds; - # -1 means use vacuum_cost_delay -#autovacuum_vacuum_cost_limit = -1 # default vacuum cost limit for - # autovacuum, -1 means use - # vacuum_cost_limit - -# - Cost-Based Vacuum Delay - - -#vacuum_cost_delay = 0 # 0-100 milliseconds (0 disables) -#vacuum_cost_page_hit = 1 # 0-10000 credits -#vacuum_cost_page_miss = 2 # 0-10000 credits -#vacuum_cost_page_dirty = 20 # 0-10000 credits -#vacuum_cost_limit = 200 # 1-10000 credits - -# - Freezing - - -#vacuum_freeze_table_age = 150000000 -#vacuum_freeze_min_age = 50000000 -#vacuum_failsafe_age = 1600000000 -#vacuum_multixact_freeze_table_age = 150000000 -#vacuum_multixact_freeze_min_age = 5000000 -#vacuum_multixact_failsafe_age = 1600000000 -#vacuum_max_eager_freeze_failure_rate = 0.03 # 0 disables eager scanning - -#------------------------------------------------------------------------------ -# CLIENT CONNECTION DEFAULTS -#------------------------------------------------------------------------------ - -# - Statement Behavior - - -#client_min_messages = notice # values in order of decreasing detail: - # debug5 - # debug4 - # debug3 - # debug2 - # debug1 - # log - # notice - # warning - # error -#search_path = '"$user", public' # schema names -#row_security = on -#default_table_access_method = 'heap' -#default_tablespace = '' # a tablespace name, '' uses the default -#default_toast_compression = 'pglz' # 'pglz' or 'lz4' -#temp_tablespaces = '' # a list of tablespace names, '' uses - # only default tablespace -#check_function_bodies = on -#default_transaction_isolation = 'read committed' -#default_transaction_read_only = off -#default_transaction_deferrable = off -#session_replication_role = 'origin' -#statement_timeout = 0 # in milliseconds, 0 is disabled -#transaction_timeout = 0 # in milliseconds, 0 is disabled -#lock_timeout = 0 # in milliseconds, 0 is disabled -#idle_in_transaction_session_timeout = 0 # in milliseconds, 0 is disabled -#idle_session_timeout = 0 # in milliseconds, 0 is disabled -#bytea_output = 'hex' # hex, escape -#xmlbinary = 'base64' -#xmloption = 'content' -#gin_pending_list_limit = 4MB -#createrole_self_grant = '' # set and/or inherit -#event_triggers = on - -# - Locale and Formatting - - -#datestyle = 'iso, mdy' -#intervalstyle = 'postgres' -#timezone = 'GMT' -#timezone_abbreviations = 'Default' # Select the set of available time zone - # abbreviations. Currently, there are - # Default - # Australia (historical usage) - # India - # You can create your own file in - # share/timezonesets/. -#extra_float_digits = 1 # min -15, max 3; any value >0 actually - # selects precise output mode -#client_encoding = sql_ascii # actually, defaults to database - # encoding - -# These settings are initialized by initdb, but they can be changed. -#lc_messages = '' # locale for system error message - # strings -#lc_monetary = 'C' # locale for monetary formatting -#lc_numeric = 'C' # locale for number formatting -#lc_time = 'C' # locale for time formatting - -#icu_validation_level = warning # report ICU locale validation - # errors at the given level - -# default configuration for text search -#default_text_search_config = 'pg_catalog.simple' - -# - Shared Library Preloading - - -#local_preload_libraries = '' -#session_preload_libraries = '' -#shared_preload_libraries = '' # (change requires restart) -#jit_provider = 'llvmjit' # JIT library to use - -# - Other Defaults - - -#dynamic_library_path = '$libdir' -#gin_fuzzy_search_limit = 0 - - -#------------------------------------------------------------------------------ -# LOCK MANAGEMENT -#------------------------------------------------------------------------------ - -#deadlock_timeout = 1s -#max_locks_per_transaction = 64 # min 10 - # (change requires restart) -#max_pred_locks_per_transaction = 64 # min 10 - # (change requires restart) -#max_pred_locks_per_relation = -2 # negative values mean - # (max_pred_locks_per_transaction - # / -max_pred_locks_per_relation) - 1 -#max_pred_locks_per_page = 2 # min 0 - - -#------------------------------------------------------------------------------ -# VERSION AND PLATFORM COMPATIBILITY -#------------------------------------------------------------------------------ - -# - Previous PostgreSQL Versions - - -#array_nulls = on -#backslash_quote = safe_encoding # on, off, or safe_encoding -#escape_string_warning = on -#lo_compat_privileges = off -#quote_all_identifiers = off -#standard_conforming_strings = on -#synchronize_seqscans = on - -# - Other Platforms and Clients - - -#transform_null_equals = off -#allow_alter_system = on - - -#------------------------------------------------------------------------------ -# ERROR HANDLING -#------------------------------------------------------------------------------ - -#exit_on_error = off # terminate session on any error? -#restart_after_crash = on # reinitialize after backend crash? -#data_sync_retry = off # retry or panic on failure to fsync - # data? - # (change requires restart) -#recovery_init_sync_method = fsync # fsync, syncfs (Linux 5.8+) - - -#------------------------------------------------------------------------------ -# CONFIG FILE INCLUDES -#------------------------------------------------------------------------------ - -# These options allow settings to be loaded from files other than the -# default postgresql.conf. Note that these are directives, not variable -# assignments, so they can usefully be given more than once. - -#include_dir = '...' # include files ending in '.conf' from - # a directory, e.g., 'conf.d' -#include_if_exists = '...' # include file only if it exists -#include = '...' # include file - - -#------------------------------------------------------------------------------ -# CUSTOMIZED OPTIONS -#------------------------------------------------------------------------------ - -# Add settings for extensions here \ No newline at end of file diff --git a/docker-compose.production.yml b/docker-compose.production.yml new file mode 100644 index 00000000..64117082 --- /dev/null +++ b/docker-compose.production.yml @@ -0,0 +1,108 @@ +services: + web: + build: + context: . + dockerfile: ./frontend/Dockerfile + restart: unless-stopped + networks: + - dwengo-1 + labels: + - "traefik.enable=true" + - "traefik.http.routers.web.rule=PathPrefix(`/`)" + - "traefik.http.services.web.loadbalancer.server.port=80" + + api: + build: + context: . + dockerfile: ./backend/Dockerfile + restart: unless-stopped + volumes: + # TODO Replace with environment keys + - ./backend/.env:/app/.env + networks: + - dwengo-1 + depends_on: + - db + - logging + labels: + - "traefik.enable=true" + - "traefik.http.middlewares.api-prefix.stripprefix.prefixes=/api" + - "traefik.http.routers.api.rule=Host(`sel2-1.ugent.be`)" + - "traefik.http.routers.api.rule=PathPrefix(`/api`)" + - "traefik.http.routers.api.middlewares=api-prefix" + - "traefik.http.services.api.loadbalancer.server.port=3000" + + db: + image: postgres:latest + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + restart: unless-stopped + volumes: + - dwengo_postgres_data:/var/lib/postgresql/data + networks: + - dwengo-1 + + reverse-proxy: + image: traefik:v3.3 + command: > + --api.insecure=true + --providers.docker=true + --providers.docker.exposedbydefault=false + --entrypoints.web.address=:80/tcp + --entrypoints.web.http.redirections.entryPoint.to=websecure + --entrypoints.web.http.redirections.entrypoint.scheme=https + --entrypoints.websecure.address=:443/tcp + --entrypoints.websecure.http.tls=true + --entrypoints.websecure.http.tls.certResolver=letsencrypt + --entrypoints.websecure.http.tls.domains[0].main=sel2-1.ugent.be + --certificatesresolvers.letsencrypt.acme.email=timo.demeyst@ugent.be + --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json + --certificatesresolvers.letsencrypt.acme.httpChallenge=true + --certificatesresolvers.letsencrypt.acme.httpChallenge.entrypoint=web + ports: + - '8080:8080' + - '80:80/tcp' + - '443:443/tcp' + restart: unless-stopped + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - dwengo_letsencrypt:/letsencrypt:ro + networks: + - dwengo-1 + + logging: + image: grafana/loki:latest + ports: + - '3102:3102' + - '9095:9095' + volumes: + - ./config/loki/config.yml:/etc/loki/config.yaml + - dwengo_loki_data:/loki + command: -config.file=/etc/loki/config.yaml + restart: unless-stopped + networks: + - dwengo-1 + labels: + - "traefik.enable=true" + - "traefik.http.middlewares.logging-prefix.stripprefix.prefixes=/logging" + - "traefik.http.routers.web.rule=PathPrefix(`/logging`)" + - "traefik.http.routers.web.middlewares=logging-prefix" + - "traefik.http.services.web.loadbalancer.server.port=3102" + + dashboards: + image: grafana/grafana:latest + ports: + - '3100:3000' + volumes: + - dwengo_grafana_data:/var/lib/grafana + restart: unless-stopped + networks: + - dwengo-1 + +volumes: + dwengo_postgres_data: + dwengo_letsencrypt: + dwengo_loki_data: + dwengo_grafana_data: diff --git a/docker-compose.yml b/docker-compose.yml index d20d819d..06e882ca 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,33 +2,69 @@ services: web: build: context: . - dockerfile: ./frontend.Dockerfile - depends_on: - - api + dockerfile: ./frontend/Dockerfile ports: - - '443:443' - - '80:80' - volumes: - - /etc/letsencrypt/:/etc/letsencrypt/:ro + - '8090:80/tcp' + restart: unless-stopped +# networks: +# - dwengo-1 + labels: + - "traefik.enable=true" + - "traefik.http.routers.web.rule=PathPrefix(`/`)" + - "traefik.http.services.web.loadbalancer.server.port=80" + api: build: context: . - dockerfile: ./backend.Dockerfile + dockerfile: ./backend/Dockerfile + ports: + - '3000:3000/tcp' + restart: unless-stopped + volumes: + - ./backend/.env:/app/.env +# networks: +# - dwengo-1 depends_on: - db - ports: - - '2002:2002' + - logging + labels: + - "traefik.enable=true" + - "traefik.http.middlewares.api-prefix.stripprefix.prefixes=/api" + - "traefik.http.routers.api.rule=PathPrefix(`/api`)" + - "traefik.http.routers.api.middlewares=api-prefix" + - "traefik.http.services.api.loadbalancer.server.port=3000" + db: image: postgres:latest environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: postgres - POSTGRES_INITDB_ARGS: -T ./config/postgresql/postgresql.conf ports: - '5431:5432' + restart: unless-stopped volumes: - dwengo_postgres_data:/var/lib/postgresql/data +# networks: +# - dwengo-1 + + reverse-proxy: + image: traefik:v3.3 + command: > + --api.insecure=true + --providers.docker=true + --providers.docker.exposedbydefault=false + --entrypoints.web.address=:80/tcp + ports: + - '8080:8080' + - '80:80/tcp' +# - '443:443/tcp' + restart: unless-stopped + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - dwengo_letsencrypt:/letsencrypt:ro +# networks: +# - dwengo-1 logging: image: grafana/loki:latest @@ -40,6 +76,8 @@ services: - dwengo_loki_data:/loki command: -config.file=/etc/loki/config.yaml restart: unless-stopped +# networks: +# - dwengo-1 dashboards: image: grafana/grafana:latest @@ -48,9 +86,11 @@ services: volumes: - dwengo_grafana_data:/var/lib/grafana restart: unless-stopped +# networks: +# - dwengo-1 volumes: dwengo_postgres_data: + dwengo_letsencrypt: dwengo_loki_data: dwengo_grafana_data: - ssl: diff --git a/frontend.Dockerfile b/frontend.Dockerfile deleted file mode 100644 index b5b765d2..00000000 --- a/frontend.Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -# build stage -FROM node:22 AS build-stage -WORKDIR /app -COPY ./frontend/package*.json ./ -RUN npm install -COPY ./frontend ./frontend -COPY ./assets ./assets -WORKDIR /app/frontend -RUN npm run build - -# production stage -FROM nginx:stable AS production-stage -COPY ./nginx/nginx.conf /etc/nginx/ -COPY --from=build-stage /app/frontend/dist /usr/share/nginx/html -EXPOSE 80 -EXPOSE 443 -CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 00000000..841f80db --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,35 @@ +FROM node:22 AS build-stage + +# install simple http server for serving static content +RUN npm install -g http-server + +WORKDIR /app + +# Install dependencies + +COPY package*.json ./ +COPY ./frontend/package.json ./frontend/ + +RUN npm install --silent + +# Build the frontend + +# Root tsconfig.json +COPY tsconfig.json ./ +COPY assets ./assets/ + +WORKDIR /app/frontend + +COPY frontend ./ + +RUN npm run build + +FROM nginx:stable AS production-stage + +COPY config/nginx/nginx.conf /etc/nginx/nginx.conf + +COPY --from=build-stage /app/frontend/dist /usr/share/nginx/html + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/nginx/nginx.conf b/nginx/nginx.conf deleted file mode 100644 index 0185fdbc..00000000 --- a/nginx/nginx.conf +++ /dev/null @@ -1,49 +0,0 @@ -worker_processes auto; - -events { - worker_connections 1024; -} - -http { - server { - server_name sel2-1.ugent.be; - - location / { - root /usr/share/nginx/html; - index index.html index.htm; - } - - location /api/ { - proxy_pass http://127.0.0.1:2002/; - } - - listen 443 default_server ssl; # managed by Certbot - ssl_certificate /etc/letsencrypt/live/sel2-1.ugent.be/fullchain.pem; # managed by Certbot - ssl_certificate_key /etc/letsencrypt/live/sel2-1.ugent.be/privkey.pem; # managed by Certbot - include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot - ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot - -} - - server { - listen 2002; - server_name dwengo-api; - - location / { - root /usr/share/api; - } - - } - - server { - if ($host = sel2-1.ugent.be) { - return 301 https://$host$request_uri; - } # managed by Certbot - - - listen 80; - server_name sel2-1.ugent.be; - return 404; # managed by Certbot - - } -} \ No newline at end of file From f2bbb5ed1396d7a9fc506b63ff89d2d95ae50c09 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Tue, 11 Mar 2025 13:55:34 +0100 Subject: [PATCH 056/106] fix: Add network --- docker-compose.production.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker-compose.production.yml b/docker-compose.production.yml index 64117082..60d7425e 100644 --- a/docker-compose.production.yml +++ b/docker-compose.production.yml @@ -106,3 +106,6 @@ volumes: dwengo_letsencrypt: dwengo_loki_data: dwengo_grafana_data: + +networks: + dwengo-1: From 7c453467df8dfd0ce8aadc1ce69b50f25af202aa Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Tue, 11 Mar 2025 18:04:27 +0100 Subject: [PATCH 057/106] feat: submission endpoint geimplementeerd (ongetest) --- backend/src/controllers/submissions.ts | 25 +++++++++++++++++++++ backend/src/interfaces/submission.ts | 30 ++++++++++++++++++++++++++ backend/src/routes/learning-objects.ts | 4 ++++ backend/src/routes/submissions.ts | 14 ++++-------- backend/src/services/submissions.ts | 22 +++++++++++++++++++ 5 files changed, 85 insertions(+), 10 deletions(-) create mode 100644 backend/src/controllers/submissions.ts create mode 100644 backend/src/interfaces/submission.ts create mode 100644 backend/src/services/submissions.ts diff --git a/backend/src/controllers/submissions.ts b/backend/src/controllers/submissions.ts new file mode 100644 index 00000000..24449ae8 --- /dev/null +++ b/backend/src/controllers/submissions.ts @@ -0,0 +1,25 @@ +import { Request, Response } from "express"; +import { getSubmission } from "../services/submissions"; +import { Language } from "../entities/content/language"; + +interface SubmissionParams { + lohruid: string, + submissionNumber: number; +} + +export async function getSubmissionHandler( + req: Request, + res: Response, +): Promise { + const lohruid = req.params.lohruid; + const submissionNumber = req.params.submissionNumber; + + const submission = getSubmission(lohruid, Language.Dutch, '1', submissionNumber); + + if (!submission) { + res.status(404).json({ error: 'Submission not found' }); + return; + } + + res.json(submission); +} \ No newline at end of file diff --git a/backend/src/interfaces/submission.ts b/backend/src/interfaces/submission.ts new file mode 100644 index 00000000..daf00a64 --- /dev/null +++ b/backend/src/interfaces/submission.ts @@ -0,0 +1,30 @@ +import { Submission } from "../entities/assignments/submission.entity"; +import { Language } from "../entities/content/language"; +import { GroupDTO, mapToGroupDTO } from "./group"; +import { mapToStudentDTO, StudentDTO } from "./student"; + +export interface SubmissionDTO { + learningObjectHruid: string, + learningObjectLanguage: Language, + learningObjectVersion: string, + + submissionNumber: number, + submitter: StudentDTO | string, + time: Date, + group?: GroupDTO | string, + content: string, +} + +export function mapToSubmissionDTO(submission: Submission): SubmissionDTO { + return { + learningObjectHruid: submission.learningObjectHruid, + learningObjectLanguage: submission.learningObjectLanguage, + learningObjectVersion: submission.learningObjectVersion, + + submissionNumber: submission.submissionNumber, + submitter: mapToStudentDTO(submission.submitter), + time: submission.submissionTime, + group: submission.onBehalfOf ? mapToGroupDTO(submission.onBehalfOf) : undefined, + content: submission.content, + } +} \ No newline at end of file diff --git a/backend/src/routes/learning-objects.ts b/backend/src/routes/learning-objects.ts index 3717095a..77094955 100644 --- a/backend/src/routes/learning-objects.ts +++ b/backend/src/routes/learning-objects.ts @@ -4,6 +4,8 @@ import { getLearningObject, } from '../controllers/learning-objects.js'; +import submissionRoutes from './submissions.js'; + const router = express.Router(); // DWENGO learning objects @@ -24,4 +26,6 @@ router.get('/', getAllLearningObjects); // Example: http://localhost:3000/learningObject/un_ai7 router.get('/:hruid', getLearningObject); +router.use('/:hruid/submissions', submissionRoutes); + export default router; diff --git a/backend/src/routes/submissions.ts b/backend/src/routes/submissions.ts index cb4d3e85..b4e34c74 100644 --- a/backend/src/routes/submissions.ts +++ b/backend/src/routes/submissions.ts @@ -1,6 +1,9 @@ import express from 'express'; +import { getSubmissionHandler } from '../controllers/submissions'; const router = express.Router(); + + // Root endpoint used to search objects router.get('/', (req, res) => { res.json({ @@ -9,15 +12,6 @@ router.get('/', (req, res) => { }); // Information about an submission with id 'id' -router.get('/:id', (req, res) => { - res.json({ - id: req.params.id, - student: '0', - group: '0', - time: new Date(2025, 1, 1), - content: 'Wortel 2 is rationeel', - learningObject: '0', - }); -}); +router.get('/:id', getSubmissionHandler); export default router; diff --git a/backend/src/services/submissions.ts b/backend/src/services/submissions.ts new file mode 100644 index 00000000..2c2e1aad --- /dev/null +++ b/backend/src/services/submissions.ts @@ -0,0 +1,22 @@ +import { getSubmissionRepository } from "../data/repositories"; +import { Language } from "../entities/content/language"; +import { LearningObjectIdentifier } from "../entities/content/learning-object-identifier"; +import { mapToSubmissionDTO, SubmissionDTO } from "../interfaces/submission"; + +export async function getSubmission( + learningObjectHruid: string, + language: Language, + version: string, + submissionNumber: number, +): Promise { + const loId = new LearningObjectIdentifier(learningObjectHruid, language, version); + + const submissionRepository = getSubmissionRepository(); + const submission = await submissionRepository.findSubmissionByLearningObjectAndSubmissionNumber(loId, submissionNumber); + + if (!submission) { + return null; + } + + return mapToSubmissionDTO(submission); +} \ No newline at end of file From fd693dc55ff00fc18425376b1492429a0e78b36f Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Wed, 12 Mar 2025 15:23:33 +0100 Subject: [PATCH 058/106] feat: submissions van een assignment geimplementeerd --- backend/src/controllers/submissions.ts | 14 +++++++-- .../data/assignments/submission-repository.ts | 8 +++++ backend/src/entities/content/language.ts | 7 +++++ backend/src/routes/submissions.ts | 2 +- backend/src/services/assignments.ts | 31 +++++++++++++++++++ 5 files changed, 58 insertions(+), 4 deletions(-) diff --git a/backend/src/controllers/submissions.ts b/backend/src/controllers/submissions.ts index 24449ae8..c4911637 100644 --- a/backend/src/controllers/submissions.ts +++ b/backend/src/controllers/submissions.ts @@ -1,6 +1,6 @@ import { Request, Response } from "express"; import { getSubmission } from "../services/submissions"; -import { Language } from "../entities/content/language"; +import { Language, languageMap } from "../entities/content/language"; interface SubmissionParams { lohruid: string, @@ -12,9 +12,17 @@ export async function getSubmissionHandler( res: Response, ): Promise { const lohruid = req.params.lohruid; - const submissionNumber = req.params.submissionNumber; + const submissionNumber = +req.params.submissionNumber; - const submission = getSubmission(lohruid, Language.Dutch, '1', submissionNumber); + if (isNaN(submissionNumber)) { + res.status(404).json({ error: 'Submission number is not a number' }); + return; + } + + let lang = languageMap[req.query.language as string] || Language.Dutch; + let version = req.query.version as string || '1'; + + const submission = getSubmission(lohruid, lang, version, submissionNumber); if (!submission) { res.status(404).json({ error: 'Submission not found' }); diff --git a/backend/src/data/assignments/submission-repository.ts b/backend/src/data/assignments/submission-repository.ts index 5332d050..a4d34826 100644 --- a/backend/src/data/assignments/submission-repository.ts +++ b/backend/src/data/assignments/submission-repository.ts @@ -47,6 +47,14 @@ export class SubmissionRepository extends DwengoEntityRepository { ); } + public findAllSubmissionsForGroup( + group: Group, + ): Promise { + return this.find( + { onBehalfOf: group }, + ); + } + public deleteSubmissionByLearningObjectAndSubmissionNumber( loId: LearningObjectIdentifier, submissionNumber: number diff --git a/backend/src/entities/content/language.ts b/backend/src/entities/content/language.ts index b5d18c80..d62a2f22 100644 --- a/backend/src/entities/content/language.ts +++ b/backend/src/entities/content/language.ts @@ -4,3 +4,10 @@ export enum Language { English = 'en', Germany = 'de', } + +export const languageMap: Record = { + nl: Language.Dutch, + fr: Language.French, + en: Language.English, + de: Language.Germany, +}; \ No newline at end of file diff --git a/backend/src/routes/submissions.ts b/backend/src/routes/submissions.ts index b4e34c74..744b9e16 100644 --- a/backend/src/routes/submissions.ts +++ b/backend/src/routes/submissions.ts @@ -1,6 +1,6 @@ import express from 'express'; import { getSubmissionHandler } from '../controllers/submissions'; -const router = express.Router(); +const router = express.Router({ mergeParams: true }); diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts index 1ecfb4d4..1f50a1c9 100644 --- a/backend/src/services/assignments.ts +++ b/backend/src/services/assignments.ts @@ -1,12 +1,15 @@ import { getAssignmentRepository, getClassRepository, + getGroupRepository, + getSubmissionRepository, } from '../data/repositories.js'; import { AssignmentDTO, mapToAssignmentDTO, mapToAssignmentDTOId, } from '../interfaces/assignment.js'; +import { mapToSubmissionDTO, SubmissionDTO } from '../interfaces/submission.js'; export async function getAllAssignments( classid: string, @@ -50,3 +53,31 @@ export async function getAssignment( return mapToAssignmentDTO(assignment); } + +export async function getAssignmentsSubmissions( + classid: string, + assignmentNumber: number, +): Promise { + const classRepository = getClassRepository(); + const cls = await classRepository.findById(classid); + + if (!cls) { + return []; + } + + const assignmentRepository = getAssignmentRepository(); + const assignment = await assignmentRepository.findByClassAndId(cls, assignmentNumber); + + if (!assignment) { + return []; + } + + const groupRepository = getGroupRepository(); + const groups = await groupRepository.findAllGroupsForAssignment(assignment); + + const submissionRepository = getSubmissionRepository(); + const submissions = + (await Promise.all(groups.map(group => submissionRepository.findAllSubmissionsForGroup(group)))).flat(); + + return submissions.map(mapToSubmissionDTO); +} \ No newline at end of file From 788ca54742af42f53e837820bb0d5252c26d2d07 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Wed, 12 Mar 2025 15:26:50 +0100 Subject: [PATCH 059/106] feat: verbinding tussen service laag en api endpoint van submissions van een assignment --- backend/src/controllers/assignments.ts | 21 ++++++++++++++++++++- backend/src/routes/assignments.ts | 7 ++----- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index b1be1302..58897410 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -1,5 +1,5 @@ import { Request, Response } from 'express'; -import { getAllAssignments, getAssignment } from '../services/assignments.js'; +import { getAllAssignments, getAssignment, getAssignmentsSubmissions } from '../services/assignments.js'; // Typescript is annoy with with parameter forwarding from class.ts interface AssignmentParams { @@ -42,3 +42,22 @@ export async function getAssignmentHandler( res.json(assignment); } + +export async function getAssignmentsSubmissionsHandler( + req: Request, + res: Response, +): Promise { + const classid = req.params.classid; + const assignmentNumber = +req.params.id; + + if (isNaN(assignmentNumber)) { + res.status(400).json({ error: 'Assignment id must be a number' }); + return; + } + + const submissions = await getAssignmentsSubmissions(classid, assignmentNumber); + + res.json({ + submissions: submissions, + }); +} diff --git a/backend/src/routes/assignments.ts b/backend/src/routes/assignments.ts index 85f3bc82..bbc29194 100644 --- a/backend/src/routes/assignments.ts +++ b/backend/src/routes/assignments.ts @@ -2,6 +2,7 @@ import express from 'express'; import { getAllAssignmentsHandler, getAssignmentHandler, + getAssignmentsSubmissionsHandler, } from '../controllers/assignments.js'; import groupRouter from './groups.js'; @@ -13,11 +14,7 @@ router.get('/', getAllAssignmentsHandler); // Information about an assignment with id 'id' router.get('/:id', getAssignmentHandler); -router.get('/:id/submissions', (req, res) => { - res.json({ - submissions: ['0'], - }); -}); +router.get('/:id/submissions', getAssignmentsSubmissionsHandler); router.get('/:id/questions', (req, res) => { res.json({ From fca8e7ff7afe37811e084810c847b30d28a29b74 Mon Sep 17 00:00:00 2001 From: Joyelle Ndagijimana Date: Wed, 12 Mar 2025 15:28:05 +0100 Subject: [PATCH 060/106] test: alle functies in learningPaths.ts zijn getest en de testen slagen --- backend/tests/service/learning-paths.test.ts | 56 ++++++++++++++++++-- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/backend/tests/service/learning-paths.test.ts b/backend/tests/service/learning-paths.test.ts index 6ac15213..668c266f 100644 --- a/backend/tests/service/learning-paths.test.ts +++ b/backend/tests/service/learning-paths.test.ts @@ -1,32 +1,78 @@ import { describe, it, expect, vi } from 'vitest'; -import { fetchLearningPaths } from '../../src/services/learningPaths'; +import { fetchLearningPaths, searchLearningPaths } from '../../src/services/learningPaths'; import { fetchWithLogging } from '../../src/util/apiHelper'; import { LearningPathResponse } from '../../src/interfaces/learningPath'; +// Function to mock the fetchWithLogging module using vi +vi.mock('../../src/util/apiHelper', () => ({ + fetchWithLogging: vi.fn(), +})); describe('fetchLearningPaths', () => { const mockHruids = ['pn_werking', 'art1']; const language = 'en'; const source = 'Test Source'; + const mockResponse = [{ title: 'Test Path', hruids: mockHruids }]; - it('✅ Moet een succesvolle response retourneren wanneer hruids zijn opgegeven', async () => { + it('✅ Should return a successful response when HRUIDs are provided', async () => { // Mock response van fetchWithLogging - //const mockResponse = [{ title: 'Test Path', hruids: mockHruids }]; + const mockResponse = [{ title: 'Test Path', hruids: mockHruids }]; + + // Mock the function to return mockResponse + vi.mocked(fetchWithLogging).mockResolvedValue(mockResponse); const result: LearningPathResponse = await fetchLearningPaths(mockHruids, language, source); expect(result.success).toBe(true); - //expect(result.data).toEqual(mockResponse); + expect(result.data).toEqual(mockResponse); expect(result.source).toBe(source); }); - it('⚠️ Moet een foutmelding teruggeven als er geen hruids zijn opgegeven', async () => { + it('⚠️ Should return an error when no HRUIDs are provided', async () => { const result: LearningPathResponse = await fetchLearningPaths([], language, source); + vi.mocked(fetchWithLogging).mockResolvedValue(mockResponse); + expect(result.success).toBe(false); expect(result.data).toBeNull(); expect(result.message).toBe(`No HRUIDs provided for ${source}.`); }); + it('⚠️ Should return a failure response when no learning paths are found', async () => { + // Mock fetchWithLogging to return an empty array + vi.mocked(fetchWithLogging).mockResolvedValue([]); + const result: LearningPathResponse = await fetchLearningPaths(mockHruids, language, source); + + expect(result.success).toBe(false); + expect(result.data).toEqual([]); + expect(result.message).toBe(`No learning paths found for ${source}.`); + }); +}); + +describe('searchLearningPaths', () => { + const query = 'robotics'; + const language = 'en'; + + it('✅ Should return search results when API responds with data', async () => { + const mockResults = [ + { title: 'Robotics Basics', hruids: ['robotics_101'] }, + { title: 'Advanced Robotics', hruids: ['robotics_advanced'] }, + ]; + + // Mock fetchWithLogging to return search results + vi.mocked(fetchWithLogging).mockResolvedValue(mockResults); + + const result = await searchLearningPaths(query, language); + + expect(result).toEqual(mockResults); + }); + + it('⚠️ Should return an empty array when API returns no results', async () => { + vi.mocked(fetchWithLogging).mockResolvedValue([]); + + const result = await searchLearningPaths(query, language); + + expect(result).toEqual([]); + }); }); From d8b97f3aea345ed7674fd0960956e20209d9843a Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Wed, 12 Mar 2025 15:32:26 +0100 Subject: [PATCH 061/106] feat: verbinding student submissions --- backend/src/controllers/students.ts | 14 ++++++++++++++ .../data/assignments/submission-repository.ts | 8 ++++++++ backend/src/routes/students.ts | 7 ++----- backend/src/services/students.ts | 18 ++++++++++++++++++ 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/backend/src/controllers/students.ts b/backend/src/controllers/students.ts index a104979a..9a052a86 100644 --- a/backend/src/controllers/students.ts +++ b/backend/src/controllers/students.ts @@ -3,6 +3,7 @@ import { getStudentAssignments, getStudentClasses, getStudentGroups, + getStudentSubmissions, StudentService, } from '../services/students.js'; import { ClassDTO } from '../interfaces/class.js'; @@ -101,3 +102,16 @@ export async function getStudentGroupsHandler( groups: groups, }); } + +export async function getStudentSubmissionsHandler( + req: Request, + res: Response, +): Promise { + const username = req.params.id; + + const submissions = await getStudentSubmissions(username); + + res.json({ + submissions: submissions, + }); +} diff --git a/backend/src/data/assignments/submission-repository.ts b/backend/src/data/assignments/submission-repository.ts index a4d34826..6ec1217f 100644 --- a/backend/src/data/assignments/submission-repository.ts +++ b/backend/src/data/assignments/submission-repository.ts @@ -55,6 +55,14 @@ export class SubmissionRepository extends DwengoEntityRepository { ); } + public findAllSubmissionsForStudent( + student: Student, + ): Promise { + return this.find( + { submitter: student }, + ); + } + public deleteSubmissionByLearningObjectAndSubmissionNumber( loId: LearningObjectIdentifier, submissionNumber: number diff --git a/backend/src/routes/students.ts b/backend/src/routes/students.ts index c18b3e1c..0c8babce 100644 --- a/backend/src/routes/students.ts +++ b/backend/src/routes/students.ts @@ -7,6 +7,7 @@ import { getStudentClassesHandler, getStudentGroupsHandler, getStudentHandler, + getStudentSubmissionsHandler, } from '../controllers/students.js'; import { getStudentGroups } from '../services/students.js'; const router = express.Router(); @@ -25,11 +26,7 @@ router.get('/:username', getStudentHandler); router.get('/:id/classes', getStudentClassesHandler); // The list of submissions a student has made -router.get('/:id/submissions', (req, res) => { - res.json({ - submissions: ['0'], - }); -}); +router.get('/:id/submissions', getStudentSubmissionsHandler); // The list of assignments a student has router.get('/:id/assignments', getStudentAssignmentsHandler); diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts index fc0a8e1d..9524f281 100644 --- a/backend/src/services/students.ts +++ b/backend/src/services/students.ts @@ -2,12 +2,14 @@ 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 { mapToSubmissionDTO, SubmissionDTO } from '../interfaces/submission.js'; import { getAllAssignments } from './assignments.js'; import { UserService } from './users.js'; @@ -74,3 +76,19 @@ export async function getStudentGroups(username: string, full: boolean): Promise return groups.map(mapToGroupDTOId); } + +export async function getStudentSubmissions( + username: string +): Promise { + const studentRepository = getStudentRepository(); + const student = await studentRepository.findByUsername(username); + + if (!student) { + return []; + } + + const submissionRepository = getSubmissionRepository(); + const submissions = await submissionRepository.findAllSubmissionsForStudent(student); + + return submissions.map(mapToSubmissionDTO); +} \ No newline at end of file From e8975878aa596654641527adb47156fb45f434ef Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Wed, 12 Mar 2025 15:41:56 +0100 Subject: [PATCH 062/106] feat: verbinding tussen groepen en hun submissions --- backend/src/controllers/groups.ts | 30 ++++++++++++++++++++++- backend/src/routes/groups.ts | 4 +++- backend/src/services/groups.ts | 40 +++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 2 deletions(-) diff --git a/backend/src/controllers/groups.ts b/backend/src/controllers/groups.ts index e484c409..8b26b810 100644 --- a/backend/src/controllers/groups.ts +++ b/backend/src/controllers/groups.ts @@ -1,5 +1,5 @@ import { Request, Response } from 'express'; -import { getAllGroups, getGroup } from '../services/groups.js'; +import { getAllGroups, getGroup, getGroupSubmissions } from '../services/groups.js'; // Typescript is annoywith with parameter forwarding from class.ts interface GroupParams { @@ -53,3 +53,31 @@ export async function getAllGroupsHandler( groups: groups, }); } + +export async function getGroupSubmissionsHandler( + req: Request, + res: Response, +): Promise { + const classId = req.params.classid; + // const full = req.query.full === 'true'; + + const assignmentId = +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 + + if (isNaN(groupId)) { + res.status(400).json({ error: 'Group id must be a number' }); + return; + } + + const submissions = await getGroupSubmissions(classId, assignmentId, groupId); + + res.json({ + submissions: submissions, + }); +} \ No newline at end of file diff --git a/backend/src/routes/groups.ts b/backend/src/routes/groups.ts index d604c088..d17a330e 100644 --- a/backend/src/routes/groups.ts +++ b/backend/src/routes/groups.ts @@ -1,5 +1,5 @@ import express from 'express'; -import { getAllGroupsHandler, getGroupHandler } from '../controllers/groups.js'; +import { getAllGroupsHandler, getGroupHandler, getGroupSubmissionsHandler } from '../controllers/groups.js'; const router = express.Router({ mergeParams: true }); @@ -9,6 +9,8 @@ router.get('/', getAllGroupsHandler); // Information about a group (members, ... [TODO DOC]) router.get('/:groupid', getGroupHandler); +router.get('/:groupid', getGroupSubmissionsHandler); + // The list of questions a group has made router.get('/:id/question', (req, res) => { res.json({ diff --git a/backend/src/services/groups.ts b/backend/src/services/groups.ts index 467f90a3..a2af8e4a 100644 --- a/backend/src/services/groups.ts +++ b/backend/src/services/groups.ts @@ -2,12 +2,14 @@ import { getAssignmentRepository, getClassRepository, getGroupRepository, + getSubmissionRepository, } from '../data/repositories.js'; import { GroupDTO, mapToGroupDTO, mapToGroupDTOId, } from '../interfaces/group.js'; +import { mapToSubmissionDTO, SubmissionDTO } from '../interfaces/submission.js'; export async function getGroup( classId: string, @@ -82,3 +84,41 @@ export async function getAllGroups( return groups.map(mapToGroupDTOId); } + +export async function getGroupSubmissions( + classId: string, + assignmentNumber: number, + groupNumber: number, +): Promise { + const classRepository = getClassRepository(); + const cls = await classRepository.findById(classId); + + if (!cls) { + return []; + } + + const assignmentRepository = getAssignmentRepository(); + const assignment = await assignmentRepository.findByClassAndId( + cls, + assignmentNumber + ); + + if (!assignment) { + return []; + } + + const groupRepository = getGroupRepository(); + const group = await groupRepository.findByAssignmentAndGroupNumber( + assignment, + groupNumber + ); + + if (!group) { + return []; + } + + const submissionRepository = getSubmissionRepository(); + const submissions = await submissionRepository.findAllSubmissionsForGroup(group); + + return submissions.map(mapToSubmissionDTO); +} From 71cf652f2837f5b1b254af986ab41e5bbc0df82f Mon Sep 17 00:00:00 2001 From: Joyelle Ndagijimana Date: Wed, 12 Mar 2025 18:30:24 +0100 Subject: [PATCH 063/106] test: alle functies in learningObjects.ts zijn getest en de testen slagen --- .../tests/service/learning-objects.test.ts | 98 +++++++++++++++++++ backend/tests/service/learning-paths.test.ts | 10 +- 2 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 backend/tests/service/learning-objects.test.ts diff --git a/backend/tests/service/learning-objects.test.ts b/backend/tests/service/learning-objects.test.ts new file mode 100644 index 00000000..9b5bb126 --- /dev/null +++ b/backend/tests/service/learning-objects.test.ts @@ -0,0 +1,98 @@ +import {describe, it, expect, vi} from 'vitest'; +import { + LearningObjectMetadata, +} from "../../src/interfaces/learningPath"; +import {fetchWithLogging} from "../../src/util/apiHelper"; +import {getLearningObjectById, getLearningObjectsFromPath} from "../../src/services/learningObjects"; +import {fetchLearningPaths} from "../../src/services/learningPaths"; + +// Mock API functions +vi.mock('../../src/util/apiHelper', () => ({ + fetchWithLogging: vi.fn(), +})); + +vi.mock('../../src/services/learningPaths', () => ({ + fetchLearningPaths: vi.fn(), +})); + + +describe('getLearningObjectById', () => { + const hruid = 'test-object'; + const language = 'en'; + const mockMetadata: LearningObjectMetadata = { + hruid, + _id: '123', + uuid: 'uuid-123', + version: 1, + title: 'Test Object', + language, + difficulty: 5, + estimated_time: 120, + available: true, + teacher_exclusive: false, + educational_goals: [{source: 'source', id: 'id'}], + keywords: ['robotics'], + description: 'A test object', + target_ages: [10, 12], + content_type: 'markdown', + content_location: '', + skos_concepts: [], + return_value: undefined, + }; + + it('✅ Should return a filtered learning object when API provides data', async () => { + vi.mocked(fetchWithLogging).mockResolvedValueOnce(mockMetadata); + + const result = await getLearningObjectById(hruid, language); + + expect(result).toEqual({ + key: hruid, + _id: '123', + uuid: 'uuid-123', + version: 1, + title: 'Test Object', + htmlUrl: expect.stringContaining('/learningObject/getRaw?hruid=test-object&language=en'), + language, + difficulty: 5, + estimatedTime: 120, + available: true, + teacherExclusive: false, + educationalGoals: [{source: 'source', id: 'id'}], + keywords: ['robotics'], + description: 'A test object', + targetAges: [10, 12], + contentType: 'markdown', + contentLocation: '', + skosConcepts: [], + returnValue: undefined, + }); + }); + + it('⚠️ Should return null if API returns no metadata', async () => { + vi.mocked(fetchWithLogging).mockResolvedValueOnce(null); + const result = await getLearningObjectById(hruid, language); + expect(result).toBeNull(); + }); +}); + + +describe('getLearningObjectsFromPath', () => { + const hruid = 'test-path'; + const language = 'en'; + + it('⚠️ Should return an empty array if API returns an empty path response', async () => { + vi.mocked(fetchLearningPaths).mockResolvedValueOnce({success: false, source: 'Test Source', data: []}); + + const result = await getLearningObjectsFromPath(hruid, language); + + expect(result).toEqual([]); + }); + + it('❌ Should return an empty array and log an error if fetchLearningPaths fails', async () => { + vi.mocked(fetchLearningPaths).mockRejectedValueOnce(new Error('API Error')); + + const result = await getLearningObjectsFromPath(hruid, language); + + expect(result).toEqual([]); + }); +}); diff --git a/backend/tests/service/learning-paths.test.ts b/backend/tests/service/learning-paths.test.ts index 668c266f..8289a5e8 100644 --- a/backend/tests/service/learning-paths.test.ts +++ b/backend/tests/service/learning-paths.test.ts @@ -3,21 +3,19 @@ import { fetchLearningPaths, searchLearningPaths } from '../../src/services/lear import { fetchWithLogging } from '../../src/util/apiHelper'; import { LearningPathResponse } from '../../src/interfaces/learningPath'; -// Function to mock the fetchWithLogging module using vi +// Mock the fetchWithLogging module using vi vi.mock('../../src/util/apiHelper', () => ({ fetchWithLogging: vi.fn(), })); describe('fetchLearningPaths', () => { + // Mock data and response const mockHruids = ['pn_werking', 'art1']; const language = 'en'; const source = 'Test Source'; const mockResponse = [{ title: 'Test Path', hruids: mockHruids }]; it('✅ Should return a successful response when HRUIDs are provided', async () => { - // Mock response van fetchWithLogging - const mockResponse = [{ title: 'Test Path', hruids: mockHruids }]; - // Mock the function to return mockResponse vi.mocked(fetchWithLogging).mockResolvedValue(mockResponse); @@ -29,10 +27,10 @@ describe('fetchLearningPaths', () => { }); it('⚠️ Should return an error when no HRUIDs are provided', async () => { - const result: LearningPathResponse = await fetchLearningPaths([], language, source); - vi.mocked(fetchWithLogging).mockResolvedValue(mockResponse); + const result: LearningPathResponse = await fetchLearningPaths([], language, source); + expect(result.success).toBe(false); expect(result.data).toBeNull(); expect(result.message).toBe(`No HRUIDs provided for ${source}.`); From 9dfa16f5f0b2095f6ed46373982a2b4085f55fbd Mon Sep 17 00:00:00 2001 From: Lint Action Date: Wed, 12 Mar 2025 17:46:11 +0000 Subject: [PATCH 064/106] style: fix linting issues met Prettier --- docker-compose.production.yml | 28 +++++++++++------------ docker-compose.yml | 42 +++++++++++++++++------------------ 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/docker-compose.production.yml b/docker-compose.production.yml index 60d7425e..7480746f 100644 --- a/docker-compose.production.yml +++ b/docker-compose.production.yml @@ -7,9 +7,9 @@ services: networks: - dwengo-1 labels: - - "traefik.enable=true" - - "traefik.http.routers.web.rule=PathPrefix(`/`)" - - "traefik.http.services.web.loadbalancer.server.port=80" + - 'traefik.enable=true' + - 'traefik.http.routers.web.rule=PathPrefix(`/`)' + - 'traefik.http.services.web.loadbalancer.server.port=80' api: build: @@ -25,12 +25,12 @@ services: - db - logging labels: - - "traefik.enable=true" - - "traefik.http.middlewares.api-prefix.stripprefix.prefixes=/api" - - "traefik.http.routers.api.rule=Host(`sel2-1.ugent.be`)" - - "traefik.http.routers.api.rule=PathPrefix(`/api`)" - - "traefik.http.routers.api.middlewares=api-prefix" - - "traefik.http.services.api.loadbalancer.server.port=3000" + - 'traefik.enable=true' + - 'traefik.http.middlewares.api-prefix.stripprefix.prefixes=/api' + - 'traefik.http.routers.api.rule=Host(`sel2-1.ugent.be`)' + - 'traefik.http.routers.api.rule=PathPrefix(`/api`)' + - 'traefik.http.routers.api.middlewares=api-prefix' + - 'traefik.http.services.api.loadbalancer.server.port=3000' db: image: postgres:latest @@ -85,11 +85,11 @@ services: networks: - dwengo-1 labels: - - "traefik.enable=true" - - "traefik.http.middlewares.logging-prefix.stripprefix.prefixes=/logging" - - "traefik.http.routers.web.rule=PathPrefix(`/logging`)" - - "traefik.http.routers.web.middlewares=logging-prefix" - - "traefik.http.services.web.loadbalancer.server.port=3102" + - 'traefik.enable=true' + - 'traefik.http.middlewares.logging-prefix.stripprefix.prefixes=/logging' + - 'traefik.http.routers.web.rule=PathPrefix(`/logging`)' + - 'traefik.http.routers.web.middlewares=logging-prefix' + - 'traefik.http.services.web.loadbalancer.server.port=3102' dashboards: image: grafana/grafana:latest diff --git a/docker-compose.yml b/docker-compose.yml index 11e3cb3b..8d3a0010 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,12 +6,12 @@ services: ports: - '8090:80/tcp' restart: unless-stopped -# networks: -# - dwengo-1 + # networks: + # - dwengo-1 labels: - - "traefik.enable=true" - - "traefik.http.routers.web.rule=PathPrefix(`/`)" - - "traefik.http.services.web.loadbalancer.server.port=80" + - 'traefik.enable=true' + - 'traefik.http.routers.web.rule=PathPrefix(`/`)' + - 'traefik.http.services.web.loadbalancer.server.port=80' api: build: @@ -22,17 +22,17 @@ services: restart: unless-stopped volumes: - ./backend/.env:/app/.env -# networks: -# - dwengo-1 + # networks: + # - dwengo-1 depends_on: - db - logging labels: - - "traefik.enable=true" - - "traefik.http.middlewares.api-prefix.stripprefix.prefixes=/api" - - "traefik.http.routers.api.rule=PathPrefix(`/api`)" - - "traefik.http.routers.api.middlewares=api-prefix" - - "traefik.http.services.api.loadbalancer.server.port=3000" + - 'traefik.enable=true' + - 'traefik.http.middlewares.api-prefix.stripprefix.prefixes=/api' + - 'traefik.http.routers.api.rule=PathPrefix(`/api`)' + - 'traefik.http.routers.api.middlewares=api-prefix' + - 'traefik.http.services.api.loadbalancer.server.port=3000' db: image: postgres:latest @@ -45,8 +45,8 @@ services: restart: unless-stopped volumes: - dwengo_postgres_data:/var/lib/postgresql/data -# networks: -# - dwengo-1 + # networks: + # - dwengo-1 reverse-proxy: image: traefik:v3.3 @@ -58,13 +58,13 @@ services: ports: - '8080:8080' - '80:80/tcp' -# - '443:443/tcp' + # - '443:443/tcp' restart: unless-stopped volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - dwengo_letsencrypt:/letsencrypt:ro -# networks: -# - dwengo-1 + # networks: + # - dwengo-1 logging: image: grafana/loki:latest @@ -76,8 +76,8 @@ services: - dwengo_loki_data:/loki command: -config.file=/etc/loki/config.yaml restart: unless-stopped -# networks: -# - dwengo-1 + # networks: + # - dwengo-1 dashboards: image: grafana/grafana:latest @@ -86,8 +86,8 @@ services: volumes: - dwengo_grafana_data:/var/lib/grafana restart: unless-stopped -# networks: -# - dwengo-1 + # networks: + # - dwengo-1 idp: # Based on: https://medium.com/@fingervinicius/easy-running-keycloak-with-docker-compose-b0d7a4ee2358 image: quay.io/keycloak/keycloak:latest From db582ee46ac2ca8cbdc9a082cd1a436318ec29a8 Mon Sep 17 00:00:00 2001 From: Joyelle Ndagijimana Date: Wed, 12 Mar 2025 20:16:33 +0100 Subject: [PATCH 065/106] test: extra test bij learningObjects --- .../tests/service/learning-objects.test.ts | 48 ++++++++++++++----- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/backend/tests/service/learning-objects.test.ts b/backend/tests/service/learning-objects.test.ts index 9b5bb126..82729629 100644 --- a/backend/tests/service/learning-objects.test.ts +++ b/backend/tests/service/learning-objects.test.ts @@ -1,9 +1,12 @@ import {describe, it, expect, vi} from 'vitest'; import { - LearningObjectMetadata, + LearningObjectMetadata, LearningPath, } from "../../src/interfaces/learningPath"; import {fetchWithLogging} from "../../src/util/apiHelper"; -import {getLearningObjectById, getLearningObjectsFromPath} from "../../src/services/learningObjects"; +import { + getLearningObjectById, + getLearningObjectsFromPath +} from "../../src/services/learningObjects"; import {fetchLearningPaths} from "../../src/services/learningPaths"; // Mock API functions @@ -35,9 +38,7 @@ describe('getLearningObjectById', () => { description: 'A test object', target_ages: [10, 12], content_type: 'markdown', - content_location: '', - skos_concepts: [], - return_value: undefined, + content_location: '' }; it('✅ Should return a filtered learning object when API provides data', async () => { @@ -62,9 +63,7 @@ describe('getLearningObjectById', () => { description: 'A test object', targetAges: [10, 12], contentType: 'markdown', - contentLocation: '', - skosConcepts: [], - returnValue: undefined, + contentLocation: '' }); }); @@ -80,19 +79,44 @@ describe('getLearningObjectsFromPath', () => { const hruid = 'test-path'; const language = 'en'; - it('⚠️ Should return an empty array if API returns an empty path response', async () => { + it('✅ Should not give error or warning', async () => { + const mockPathResponse: LearningPath[] = [{ + _id: 'path-1', + hruid, + language, + title: 'Test Path', + description: '', + num_nodes: 1, + num_nodes_left: 0, + nodes: [], + keywords: '', + target_ages: [], + min_age: 10, + max_age: 12, + __order: 1, + }]; + + vi.mocked(fetchLearningPaths).mockResolvedValueOnce({ + success: true, + source: 'Test Source', + data: mockPathResponse + }); + + const result = await getLearningObjectsFromPath(hruid, language); + expect(result).toEqual([]); + }); + + it('⚠️ Should give a warning', async () => { vi.mocked(fetchLearningPaths).mockResolvedValueOnce({success: false, source: 'Test Source', data: []}); const result = await getLearningObjectsFromPath(hruid, language); - expect(result).toEqual([]); }); - it('❌ Should return an empty array and log an error if fetchLearningPaths fails', async () => { + it('❌ Should give an error', async () => { vi.mocked(fetchLearningPaths).mockRejectedValueOnce(new Error('API Error')); const result = await getLearningObjectsFromPath(hruid, language); - expect(result).toEqual([]); }); }); From 09bf765e8c71fdfde950a43ae1ea80b1decefd5f Mon Sep 17 00:00:00 2001 From: Lint Action Date: Wed, 12 Mar 2025 19:34:18 +0000 Subject: [PATCH 066/106] style: fix linting issues met Prettier --- .../tests/service/learning-objects.test.ts | 61 +++++++++---------- 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/backend/tests/service/learning-objects.test.ts b/backend/tests/service/learning-objects.test.ts index 82729629..130c237e 100644 --- a/backend/tests/service/learning-objects.test.ts +++ b/backend/tests/service/learning-objects.test.ts @@ -1,13 +1,8 @@ -import {describe, it, expect, vi} from 'vitest'; -import { - LearningObjectMetadata, LearningPath, -} from "../../src/interfaces/learningPath"; -import {fetchWithLogging} from "../../src/util/apiHelper"; -import { - getLearningObjectById, - getLearningObjectsFromPath -} from "../../src/services/learningObjects"; -import {fetchLearningPaths} from "../../src/services/learningPaths"; +import { describe, it, expect, vi } from 'vitest'; +import { LearningObjectMetadata, LearningPath } from '../../src/interfaces/learningPath'; +import { fetchWithLogging } from '../../src/util/apiHelper'; +import { getLearningObjectById, getLearningObjectsFromPath } from '../../src/services/learningObjects'; +import { fetchLearningPaths } from '../../src/services/learningPaths'; // Mock API functions vi.mock('../../src/util/apiHelper', () => ({ @@ -18,7 +13,6 @@ vi.mock('../../src/services/learningPaths', () => ({ fetchLearningPaths: vi.fn(), })); - describe('getLearningObjectById', () => { const hruid = 'test-object'; const language = 'en'; @@ -33,12 +27,12 @@ describe('getLearningObjectById', () => { estimated_time: 120, available: true, teacher_exclusive: false, - educational_goals: [{source: 'source', id: 'id'}], + educational_goals: [{ source: 'source', id: 'id' }], keywords: ['robotics'], description: 'A test object', target_ages: [10, 12], content_type: 'markdown', - content_location: '' + content_location: '', }; it('✅ Should return a filtered learning object when API provides data', async () => { @@ -58,12 +52,12 @@ describe('getLearningObjectById', () => { estimatedTime: 120, available: true, teacherExclusive: false, - educationalGoals: [{source: 'source', id: 'id'}], + educationalGoals: [{ source: 'source', id: 'id' }], keywords: ['robotics'], description: 'A test object', targetAges: [10, 12], contentType: 'markdown', - contentLocation: '' + contentLocation: '', }); }); @@ -74,32 +68,33 @@ describe('getLearningObjectById', () => { }); }); - describe('getLearningObjectsFromPath', () => { const hruid = 'test-path'; const language = 'en'; it('✅ Should not give error or warning', async () => { - const mockPathResponse: LearningPath[] = [{ - _id: 'path-1', - hruid, - language, - title: 'Test Path', - description: '', - num_nodes: 1, - num_nodes_left: 0, - nodes: [], - keywords: '', - target_ages: [], - min_age: 10, - max_age: 12, - __order: 1, - }]; + const mockPathResponse: LearningPath[] = [ + { + _id: 'path-1', + hruid, + language, + title: 'Test Path', + description: '', + num_nodes: 1, + num_nodes_left: 0, + nodes: [], + keywords: '', + target_ages: [], + min_age: 10, + max_age: 12, + __order: 1, + }, + ]; vi.mocked(fetchLearningPaths).mockResolvedValueOnce({ success: true, source: 'Test Source', - data: mockPathResponse + data: mockPathResponse, }); const result = await getLearningObjectsFromPath(hruid, language); @@ -107,7 +102,7 @@ describe('getLearningObjectsFromPath', () => { }); it('⚠️ Should give a warning', async () => { - vi.mocked(fetchLearningPaths).mockResolvedValueOnce({success: false, source: 'Test Source', data: []}); + vi.mocked(fetchLearningPaths).mockResolvedValueOnce({ success: false, source: 'Test Source', data: [] }); const result = await getLearningObjectsFromPath(hruid, language); expect(result).toEqual([]); From 7b317b28d1179109c525687fff00f9f750b74bee Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 13 Mar 2025 00:15:47 +0100 Subject: [PATCH 067/106] ci: Fix MIMETYPEs --- config/nginx/nginx.conf | 16 ++++++++++++++++ frontend/Dockerfile | 3 ++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/config/nginx/nginx.conf b/config/nginx/nginx.conf index 650a09c9..7a3b995e 100644 --- a/config/nginx/nginx.conf +++ b/config/nginx/nginx.conf @@ -1,10 +1,19 @@ worker_processes auto; + events { worker_connections 1024; } http { + include mime.types; + default_type application/octet-stream; + + types { + application/javascript js mjs; + text/css css; + } + server { listen 80; @@ -12,5 +21,12 @@ http { root /usr/share/nginx/html; try_files $uri $uri/ /index.html; } + + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { + root /usr/share/nginx/html; + expires 1y; + add_header Cache-Control "public"; + try_files $uri =404; + } } } diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 841f80db..b7799a2d 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -22,12 +22,13 @@ WORKDIR /app/frontend COPY frontend ./ -RUN npm run build +RUN npx vite build FROM nginx:stable AS production-stage COPY config/nginx/nginx.conf /etc/nginx/nginx.conf +COPY --from=build-stage /app/assets /usr/share/nginx/html/assets COPY --from=build-stage /app/frontend/dist /usr/share/nginx/html EXPOSE 80 From 774adb6688da73d2ac15ae714884197e17f83d8d Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 13 Mar 2025 01:21:38 +0100 Subject: [PATCH 068/106] fix: .js toevoegen aan imports --- backend/src/middleware/auth/auth.ts | 2 +- frontend/src/services/auth/auth-service.ts | 23 +++++++++++----------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/backend/src/middleware/auth/auth.ts b/backend/src/middleware/auth/auth.ts index ca64b3b3..5ff5a53c 100644 --- a/backend/src/middleware/auth/auth.ts +++ b/backend/src/middleware/auth/auth.ts @@ -6,7 +6,7 @@ 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'; +import { ForbiddenException, UnauthorizedException } from '../../exceptions.js'; const JWKS_CACHE = true; const JWKS_RATE_LIMIT = true; diff --git a/frontend/src/services/auth/auth-service.ts b/frontend/src/services/auth/auth-service.ts index 61032170..53f8cb61 100644 --- a/frontend/src/services/auth/auth-service.ts +++ b/frontend/src/services/auth/auth-service.ts @@ -12,12 +12,13 @@ import apiClient from "@/services/api-client.ts"; import router from "@/router"; import type { AxiosError } from "axios"; -const authConfig = await loadAuthConfig(); - -const userManagers: UserManagersForRoles = { - student: new UserManager(authConfig.student), - teacher: new UserManager(authConfig.teacher), -}; +async function getUserManagers(): Promise { + const authConfig = await loadAuthConfig(); + return { + student: new UserManager(authConfig.student), + teacher: new UserManager(authConfig.teacher), + }; +} /** * Load the information about who is currently logged in from the IDP. @@ -27,7 +28,7 @@ async function loadUser(): Promise { if (!activeRole) { return null; } - const user = await userManagers[activeRole].getUser(); + const user = await (await getUserManagers())[activeRole].getUser(); authState.user = user; authState.accessToken = user?.access_token || null; authState.activeRole = activeRole || null; @@ -59,7 +60,7 @@ async function initiateLogin() { async function loginAs(role: Role): Promise { // Storing it in local storage so that it won't be lost when redirecting outside of the app. authStorage.setActiveRole(role); - await userManagers[role].signinRedirect(); + await (await getUserManagers())[role].signinRedirect(); } /** @@ -70,7 +71,7 @@ async function handleLoginCallback(): Promise { if (!activeRole) { throw new Error("Login callback received, but the user is not logging in!"); } - authState.user = (await userManagers[activeRole].signinCallback()) || null; + authState.user = (await (await getUserManagers())[activeRole].signinCallback()) || null; } /** @@ -84,7 +85,7 @@ async function renewToken() { return; } try { - return await userManagers[activeRole].signinSilent(); + return await (await getUserManagers())[activeRole].signinSilent(); } catch (error) { console.log("Can't renew the token:"); console.log(error); @@ -98,7 +99,7 @@ async function renewToken() { async function logout(): Promise { const activeRole = authStorage.getActiveRole(); if (activeRole) { - await userManagers[activeRole].signoutRedirect(); + await (await getUserManagers())[activeRole].signoutRedirect(); authStorage.deleteActiveRole(); } } From 6a6eed89786549ec98999bbb63c45d7bf74c0350 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 13 Mar 2025 01:22:32 +0100 Subject: [PATCH 069/106] chore: Verplaats Keycloak naar config --- {idp => config/idp}/README.md | 0 {idp => config/idp}/student-realm.json | 2 +- {idp => config/idp}/teacher-realm.json | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename {idp => config/idp}/README.md (100%) rename {idp => config/idp}/student-realm.json (99%) rename {idp => config/idp}/teacher-realm.json (99%) diff --git a/idp/README.md b/config/idp/README.md similarity index 100% rename from idp/README.md rename to config/idp/README.md diff --git a/idp/student-realm.json b/config/idp/student-realm.json similarity index 99% rename from idp/student-realm.json rename to config/idp/student-realm.json index 697fda34..0c1d45f5 100644 --- a/idp/student-realm.json +++ b/config/idp/student-realm.json @@ -620,7 +620,7 @@ "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-jwt", - "redirectUris": ["urn:ietf:wg:oauth:2.0:oob", "http://localhost:5173/*", "http://localhost:5173"], + "redirectUris": ["urn:ietf:wg:oauth:2.0:oob", "http://localhost:5173/*", "http://localhost:5173", "http://localhost/*", "http://localhost", "https://sel2-1.ugent.be/*", "https://sel2-1.ugent.be"], "webOrigins": ["+"], "notBefore": 0, "bearerOnly": false, diff --git a/idp/teacher-realm.json b/config/idp/teacher-realm.json similarity index 99% rename from idp/teacher-realm.json rename to config/idp/teacher-realm.json index fd965e96..6bfd0c6b 100644 --- a/idp/teacher-realm.json +++ b/config/idp/teacher-realm.json @@ -620,7 +620,7 @@ "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "redirectUris": ["urn:ietf:wg:oauth:2.0:oob", "http://localhost:5173/*", "http://localhost:5173"], + "redirectUris": ["urn:ietf:wg:oauth:2.0:oob", "http://localhost:5173/*", "http://localhost:5173", "http://localhost/*", "http://localhost", "https://sel2-1.ugent.be/*", "https://sel2-1.ugent.be"], "webOrigins": ["+"], "notBefore": 0, "bearerOnly": false, From 0e2f9e5359610956a5552d5874a61a104aa1a4e7 Mon Sep 17 00:00:00 2001 From: Joyelle Ndagijimana Date: Thu, 13 Mar 2025 09:35:35 +0100 Subject: [PATCH 070/106] =?UTF-8?q?test:=20=C3=A9=C3=A9n=20van=20de=20test?= =?UTF-8?q?=20verbeterd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/tests/service/learning-paths.test.ts | 35 ++++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/backend/tests/service/learning-paths.test.ts b/backend/tests/service/learning-paths.test.ts index 8289a5e8..3749a986 100644 --- a/backend/tests/service/learning-paths.test.ts +++ b/backend/tests/service/learning-paths.test.ts @@ -1,7 +1,7 @@ -import { describe, it, expect, vi } from 'vitest'; -import { fetchLearningPaths, searchLearningPaths } from '../../src/services/learningPaths'; -import { fetchWithLogging } from '../../src/util/apiHelper'; -import { LearningPathResponse } from '../../src/interfaces/learningPath'; +import {describe, it, expect, vi} from 'vitest'; +import {fetchLearningPaths, searchLearningPaths} from '../../src/services/learningPaths'; +import {fetchWithLogging} from '../../src/util/apiHelper'; +import {LearningPathResponse} from '../../src/interfaces/learningPath'; // Mock the fetchWithLogging module using vi vi.mock('../../src/util/apiHelper', () => ({ @@ -13,7 +13,7 @@ describe('fetchLearningPaths', () => { const mockHruids = ['pn_werking', 'art1']; const language = 'en'; const source = 'Test Source'; - const mockResponse = [{ title: 'Test Path', hruids: mockHruids }]; + const mockResponse = [{title: 'Test Path', hruids: mockHruids}]; it('✅ Should return a successful response when HRUIDs are provided', async () => { // Mock the function to return mockResponse @@ -49,14 +49,27 @@ describe('fetchLearningPaths', () => { }); describe('searchLearningPaths', () => { - const query = 'robotics'; - const language = 'en'; + const query = 'https://dwengo.org/backend/api/learningPath/getPathsFromIdList?pathIdList=%7B%22hruids%22:%5B%22pn_werking%22,%22un_artificiele_intelligentie%22%5D%7D&language=nl'; + const language = 'nl'; it('✅ Should return search results when API responds with data', async () => { - const mockResults = [ - { title: 'Robotics Basics', hruids: ['robotics_101'] }, - { title: 'Advanced Robotics', hruids: ['robotics_advanced'] }, - ]; + + + const mockResults = [{ + _id: '67b4488c9dadb305c4104618', + language: 'nl', + hruid: 'pn_werking', + title: 'Werken met notebooks', + description: 'Een korte inleiding tot Python notebooks. Hoe ga je gemakkelijk en efficiënt met de notebooks aan de slag?', + num_nodes: 0, + num_nodes_left: 0, + nodes: [], + keywords: 'Python KIKS Wiskunde STEM AI', + target_ages: [14, 15, 16, 17, 18], + min_age: 14, + max_age: 18, + __order: 0 + }]; // Mock fetchWithLogging to return search results vi.mocked(fetchWithLogging).mockResolvedValue(mockResults); From 3d3bf6dba4957feb0a7aae5326d932bfe00b039a Mon Sep 17 00:00:00 2001 From: Lint Action Date: Thu, 13 Mar 2025 08:36:15 +0000 Subject: [PATCH 071/106] style: fix linting issues met Prettier --- backend/tests/service/learning-paths.test.ts | 47 ++++++++++---------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/backend/tests/service/learning-paths.test.ts b/backend/tests/service/learning-paths.test.ts index 3749a986..c002dbac 100644 --- a/backend/tests/service/learning-paths.test.ts +++ b/backend/tests/service/learning-paths.test.ts @@ -1,7 +1,7 @@ -import {describe, it, expect, vi} from 'vitest'; -import {fetchLearningPaths, searchLearningPaths} from '../../src/services/learningPaths'; -import {fetchWithLogging} from '../../src/util/apiHelper'; -import {LearningPathResponse} from '../../src/interfaces/learningPath'; +import { describe, it, expect, vi } from 'vitest'; +import { fetchLearningPaths, searchLearningPaths } from '../../src/services/learningPaths'; +import { fetchWithLogging } from '../../src/util/apiHelper'; +import { LearningPathResponse } from '../../src/interfaces/learningPath'; // Mock the fetchWithLogging module using vi vi.mock('../../src/util/apiHelper', () => ({ @@ -13,7 +13,7 @@ describe('fetchLearningPaths', () => { const mockHruids = ['pn_werking', 'art1']; const language = 'en'; const source = 'Test Source'; - const mockResponse = [{title: 'Test Path', hruids: mockHruids}]; + const mockResponse = [{ title: 'Test Path', hruids: mockHruids }]; it('✅ Should return a successful response when HRUIDs are provided', async () => { // Mock the function to return mockResponse @@ -49,27 +49,28 @@ describe('fetchLearningPaths', () => { }); describe('searchLearningPaths', () => { - const query = 'https://dwengo.org/backend/api/learningPath/getPathsFromIdList?pathIdList=%7B%22hruids%22:%5B%22pn_werking%22,%22un_artificiele_intelligentie%22%5D%7D&language=nl'; + const query = + 'https://dwengo.org/backend/api/learningPath/getPathsFromIdList?pathIdList=%7B%22hruids%22:%5B%22pn_werking%22,%22un_artificiele_intelligentie%22%5D%7D&language=nl'; const language = 'nl'; it('✅ Should return search results when API responds with data', async () => { - - - const mockResults = [{ - _id: '67b4488c9dadb305c4104618', - language: 'nl', - hruid: 'pn_werking', - title: 'Werken met notebooks', - description: 'Een korte inleiding tot Python notebooks. Hoe ga je gemakkelijk en efficiënt met de notebooks aan de slag?', - num_nodes: 0, - num_nodes_left: 0, - nodes: [], - keywords: 'Python KIKS Wiskunde STEM AI', - target_ages: [14, 15, 16, 17, 18], - min_age: 14, - max_age: 18, - __order: 0 - }]; + const mockResults = [ + { + _id: '67b4488c9dadb305c4104618', + language: 'nl', + hruid: 'pn_werking', + title: 'Werken met notebooks', + description: 'Een korte inleiding tot Python notebooks. Hoe ga je gemakkelijk en efficiënt met de notebooks aan de slag?', + num_nodes: 0, + num_nodes_left: 0, + nodes: [], + keywords: 'Python KIKS Wiskunde STEM AI', + target_ages: [14, 15, 16, 17, 18], + min_age: 14, + max_age: 18, + __order: 0, + }, + ]; // Mock fetchWithLogging to return search results vi.mocked(fetchWithLogging).mockResolvedValue(mockResults); From 3e2c73320b0f7353734fb5371d3ce8ae95a5f752 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Thu, 13 Mar 2025 13:54:45 +0100 Subject: [PATCH 072/106] fix: voeg .js toe submission --- backend/src/controllers/submissions.ts | 6 +++--- backend/src/interfaces/submission.ts | 10 +++++----- backend/src/routes/submissions.ts | 2 +- backend/src/services/submissions.ts | 14 +++++++------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/backend/src/controllers/submissions.ts b/backend/src/controllers/submissions.ts index c4911637..572dae43 100644 --- a/backend/src/controllers/submissions.ts +++ b/backend/src/controllers/submissions.ts @@ -1,6 +1,6 @@ import { Request, Response } from "express"; -import { getSubmission } from "../services/submissions"; -import { Language, languageMap } from "../entities/content/language"; +import { getSubmission } from "../services/submissions.js"; +import { Language, languageMap } from "../entities/content/language.js"; interface SubmissionParams { lohruid: string, @@ -30,4 +30,4 @@ export async function getSubmissionHandler( } res.json(submission); -} \ No newline at end of file +} diff --git a/backend/src/interfaces/submission.ts b/backend/src/interfaces/submission.ts index daf00a64..e0fddf07 100644 --- a/backend/src/interfaces/submission.ts +++ b/backend/src/interfaces/submission.ts @@ -1,7 +1,7 @@ -import { Submission } from "../entities/assignments/submission.entity"; -import { Language } from "../entities/content/language"; -import { GroupDTO, mapToGroupDTO } from "./group"; -import { mapToStudentDTO, StudentDTO } from "./student"; +import { Submission } from "../entities/assignments/submission.entity.js"; +import { Language } from "../entities/content/language.js"; +import { GroupDTO, mapToGroupDTO } from "./group.js"; +import { mapToStudentDTO, StudentDTO } from "./student.js"; export interface SubmissionDTO { learningObjectHruid: string, @@ -27,4 +27,4 @@ export function mapToSubmissionDTO(submission: Submission): SubmissionDTO { group: submission.onBehalfOf ? mapToGroupDTO(submission.onBehalfOf) : undefined, content: submission.content, } -} \ No newline at end of file +} diff --git a/backend/src/routes/submissions.ts b/backend/src/routes/submissions.ts index 744b9e16..1be419d4 100644 --- a/backend/src/routes/submissions.ts +++ b/backend/src/routes/submissions.ts @@ -1,5 +1,5 @@ import express from 'express'; -import { getSubmissionHandler } from '../controllers/submissions'; +import { getSubmissionHandler } from '../controllers/submissions.js'; const router = express.Router({ mergeParams: true }); diff --git a/backend/src/services/submissions.ts b/backend/src/services/submissions.ts index 2c2e1aad..c805c717 100644 --- a/backend/src/services/submissions.ts +++ b/backend/src/services/submissions.ts @@ -1,15 +1,15 @@ -import { getSubmissionRepository } from "../data/repositories"; -import { Language } from "../entities/content/language"; -import { LearningObjectIdentifier } from "../entities/content/learning-object-identifier"; -import { mapToSubmissionDTO, SubmissionDTO } from "../interfaces/submission"; +import { getSubmissionRepository } from "../data/repositories.js"; +import { Language } from "../entities/content/language.js"; +import { LearningObjectIdentifier } from "../entities/content/learning-object-identifier.js"; +import { mapToSubmissionDTO, SubmissionDTO } from "../interfaces/submission.js"; export async function getSubmission( learningObjectHruid: string, language: Language, version: string, - submissionNumber: number, + submissionNumber: number, ): Promise { - const loId = new LearningObjectIdentifier(learningObjectHruid, language, version); + const loId = new LearningObjectIdentifier(learningObjectHruid, language, version); const submissionRepository = getSubmissionRepository(); const submission = await submissionRepository.findSubmissionByLearningObjectAndSubmissionNumber(loId, submissionNumber); @@ -19,4 +19,4 @@ export async function getSubmission( } return mapToSubmissionDTO(submission); -} \ No newline at end of file +} From fc5ba93ba09a9565a1482f70d553c49190d83996 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 13 Mar 2025 14:27:12 +0100 Subject: [PATCH 073/106] fix: Voorbereiding production --- backend/.env.development.example | 16 ++++- backend/.env.example | 2 +- backend/.env.production | 7 -- backend/.env.production.example | 28 ++++++++ backend/src/app.ts | 26 +++---- compose.override.yml | 72 +++++++++++++++++++ compose.prod.yml | 110 ++++++++++++++++++++++++++++ compose.yml | 52 ++++++++++++++ config/nginx/nginx.conf | 2 +- docker-compose.production.yml | 111 ---------------------------- docker-compose.yml | 120 ------------------------------- frontend/Dockerfile | 2 +- frontend/src/config.ts | 2 +- 13 files changed, 293 insertions(+), 257 deletions(-) delete mode 100644 backend/.env.production create mode 100644 backend/.env.production.example create mode 100644 compose.override.yml create mode 100644 compose.prod.yml create mode 100644 compose.yml delete mode 100644 docker-compose.production.yml delete mode 100644 docker-compose.yml diff --git a/backend/.env.development.example b/backend/.env.development.example index 247ff054..c9f3cdbf 100644 --- a/backend/.env.development.example +++ b/backend/.env.development.example @@ -1,10 +1,16 @@ -DWENGO_PORT=3000 +# +# Basic configuration +# + +DWENGO_PORT=3000 # The port the backend will listen on DWENGO_DB_HOST=localhost -DWENGO_DB_PORT=5431 +DWENGO_DB_PORT=5432 DWENGO_DB_USERNAME=postgres DWENGO_DB_PASSWORD=postgres DWENGO_DB_UPDATE=true +# Auth + DWENGO_AUTH_STUDENT_URL=http://localhost:7080/realms/student DWENGO_AUTH_STUDENT_CLIENT_ID=dwengo DWENGO_AUTH_STUDENT_JWKS_ENDPOINT=http://localhost:7080/realms/student/protocol/openid-connect/certs @@ -14,3 +20,9 @@ DWENGO_AUTH_TEACHER_JWKS_ENDPOINT=http://localhost:7080/realms/teacher/protocol/ # Allow Vite dev-server to access the backend (for testing purposes). Don't forget to remove this in production! DWENGO_CORS_ALLOWED_ORIGINS=http://localhost:5173 + +# +# Advanced configuration +# + +# LOKI_HOST=http://localhost:9001 # The address of the Loki instance, used for logging diff --git a/backend/.env.example b/backend/.env.example index 105a1654..0a8f3994 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,4 +1,4 @@ -DWENGO_PORT=3000 # The port the backend will listen on +DWENGO_PORT=3000 # The port the backend will listen on DWENGO_DB_HOST=domain-or-ip-of-database DWENGO_DB_PORT=5432 diff --git a/backend/.env.production b/backend/.env.production deleted file mode 100644 index 8ee6113e..00000000 --- a/backend/.env.production +++ /dev/null @@ -1,7 +0,0 @@ -DWENGO_PORT=3000 -DWENGO_DB_HOST=localhost -DWENGO_DB_PORT=5432 -DWENGO_DB_NAME=postgres -DWENGO_DB_USERNAME=postgres -DWENGO_DB_PASSWORD=postgres -DWENGO_DB_UPDATE=true diff --git a/backend/.env.production.example b/backend/.env.production.example new file mode 100644 index 00000000..1adf7d95 --- /dev/null +++ b/backend/.env.production.example @@ -0,0 +1,28 @@ +DWENGO_PORT=3000 # The port the backend will listen on +DWENGO_DB_HOST=db # Name of the database container +DWENGO_DB_PORT=5432 + +# Change this to the actual credentials of the user Dwengo should use in the backend +DWENGO_DB_NAME=postgres +DWENGO_DB_USERNAME=postgres +DWENGO_DB_PASSWORD=postgres + +# Set this to true when the database scheme needs to be updated. In that case, take a backup first. +DWENGO_DB_UPDATE=false + +# Data for the identity provider via which the students authenticate. +DWENGO_AUTH_STUDENT_URL=https://sel2-1.ugent.be/idp/realms/student +DWENGO_AUTH_STUDENT_CLIENT_ID=dwengo +DWENGO_AUTH_STUDENT_JWKS_ENDPOINT=http://idp:7080/idp/realms/student/protocol/openid-connect/certs # Name of the idp container +# Data for the identity provider via which the teachers authenticate. +DWENGO_AUTH_TEACHER_URL=https://sel2-1.ugent.be/idp/realms/teacher +DWENGO_AUTH_TEACHER_CLIENT_ID=dwengo +DWENGO_AUTH_TEACHER_JWKS_ENDPOINT=http://idp:7080/idp/realms/teacher/protocol/openid-connect/certs # Name of the idp container + +# +# Advanced configuration +# + +# Logging and monitoring + +# LOKI_HOST=http://logging:3102 # The address of the Loki instance, used for logging diff --git a/backend/src/app.ts b/backend/src/app.ts index 61d173ec..55352220 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -30,29 +30,29 @@ app.use(responseTime(responseTimeLogger)); app.use(authenticateUser); // TODO Replace with Express routes -app.get('/', (_, res: Response) => { - logger.debug('GET /'); +app.get('/api/', (_, res: Response) => { + logger.debug('GET /api/'); res.json({ message: 'Hello Dwengo!🚀', }); }); -app.use('/student', studentRouter); -app.use('/group', groupRouter); -app.use('/assignment', assignmentRouter); -app.use('/submission', submissionRouter); -app.use('/class', classRouter); -app.use('/question', questionRouter); -app.use('/auth', authRouter); -app.use('/theme', themeRoutes); -app.use('/learningPath', learningPathRoutes); -app.use('/learningObject', learningObjectRoutes); +app.use('/api/student', studentRouter); +app.use('/api/group', groupRouter); +app.use('/api/assignment', assignmentRouter); +app.use('/api/submission', submissionRouter); +app.use('/api/class', classRouter); +app.use('/api/question', questionRouter); +app.use('/api/auth', authRouter); +app.use('/api/theme', themeRoutes); +app.use('/api/learningPath', learningPathRoutes); +app.use('/api/learningObject', learningObjectRoutes); async function startServer() { await initORM(); app.listen(port, () => { - logger.info(`Server is running at http://localhost:${port}`); + logger.info(`Server is running at http://localhost:${port}/api`); }); } diff --git a/compose.override.yml b/compose.override.yml new file mode 100644 index 00000000..5c35441e --- /dev/null +++ b/compose.override.yml @@ -0,0 +1,72 @@ +# +# Use this configuration to test the production configuration locally. +# +# This configuration builds the frontend and backend services as Docker images, +# and uses the paths for the services, instead of ports. +# +services: + web: + build: + context: . + dockerfile: frontend/Dockerfile + ports: + - '8080:8080/tcp' + restart: unless-stopped + labels: + - 'traefik.http.routers.web.rule=PathPrefix(`/`)' + - 'traefik.http.services.web.loadbalancer.server.port=8080' + + api: + build: + context: . + dockerfile: backend/Dockerfile + ports: + - '3000:3000/tcp' + restart: unless-stopped + volumes: + - ./backend/.env:/app/.env + depends_on: + - db + - logging + labels: + - 'traefik.http.routers.api.rule=PathPrefix(`/api`)' + - 'traefik.http.services.api.loadbalancer.server.port=3000' + + idp: + # Also see compose.yml + labels: + - 'traefik.http.routers.idp.rule=PathPrefix(`/idp`)' + - 'traefik.http.services.idp.loadbalancer.server.port=7080' + environment: + PROXY_ADDRESS_FORWARDING: 'true' + KC_HTTP_RELATIVE_PATH: '/idp' + + reverse-proxy: + image: traefik:v3.3 + command: + # Enable web UI + - '--api.insecure=true' + + # Add Docker provider + - '--providers.docker=true' + - '--providers.docker.exposedbydefault=true' + + # Add web entrypoint + - '--entrypoints.web.address=:80/tcp' + ports: + - '9000:8080' + - '80:80/tcp' + restart: unless-stopped + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + + dashboards: + image: grafana/grafana:latest + ports: + - '9002:3000' + volumes: + - dwengo_grafana_data:/var/lib/grafana + restart: unless-stopped + +volumes: + dwengo_grafana_data: diff --git a/compose.prod.yml b/compose.prod.yml new file mode 100644 index 00000000..2063323e --- /dev/null +++ b/compose.prod.yml @@ -0,0 +1,110 @@ +# +# This file is used to define the production environment for the project. +# It is used to deploy the project on a server. +# Should not be used for local development. +# +services: + web: + build: + context: . + dockerfile: frontend/Dockerfile + restart: unless-stopped + networks: + - dwengo-1 + labels: + - 'traefik.enable=true' + - 'traefik.http.routers.web.rule=PathPrefix(`/`)' + - 'traefik.http.services.web.loadbalancer.server.port=80' + + api: + build: + context: . + dockerfile: backend/Dockerfile + restart: unless-stopped + volumes: + # TODO Replace with environment keys + - ./backend/.env:/app/.env + depends_on: + - db + - logging + networks: + - dwengo-1 + labels: + - 'traefik.enable=true' + - 'traefik.http.routers.api.rule=PathPrefix(`/api`)' + - 'traefik.http.services.api.loadbalancer.server.port=3000' + + db: + # Also see compose.yml + networks: + - dwengo-1 + + idp: + # Also see compose.yml + # TODO Replace with proper production command + command: ['start-dev', '--http-port', '7080', '--https-port', '7443', '--import-realm'] + networks: + - dwengo-1 + labels: + - 'traefik.enable=true' + - 'traefik.http.routers.idp.rule=PathPrefix(`/idp`)' + - 'traefik.http.services.idp.loadbalancer.server.port=7080' + env_file: + - ./config/idp/.env + environment: + KC_HOSTNAME: 'sel2-1.ugent.be' + PROXY_ADDRESS_FORWARDING: 'true' + KC_HTTP_RELATIVE_PATH: '/idp' + + reverse-proxy: + image: traefik:v3.3 + ports: + - '80:80/tcp' + - '443:443/tcp' + command: + # Add Docker provider + - "--providers.docker=true" + - "--providers.docker.exposedbydefault=false" + + # Add web entrypoint + - "--entrypoints.web.address=:80/tcp" + - "--entrypoints.web.http.redirections.entryPoint.to=websecure" + - "--entrypoints.web.http.redirections.entryPoint.scheme=https" + + # Add websecure entrypoint + - "--entrypoints.websecure.address=:443/tcp" + - "--entrypoints.websecure.http.tls=true" + - "--entrypoints.websecure.http.tls.certResolver=letsencrypt" + - "--entrypoints.websecure.http.tls.domains[0].main=sel2-1.ugent.be" + + # Certificates + - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true" + - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web" + - "--certificatesresolvers.letsencrypt.acme.email=timo.demeyst@ugent.be" + - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" + restart: unless-stopped + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - dwengo_letsencrypt:/letsencrypt + networks: + - dwengo-1 + + logging: + # Also see compose.yml + networks: + - dwengo-1 + + dashboards: + image: grafana/grafana:latest + ports: + - '9002:3000' + restart: unless-stopped + volumes: + - dwengo_grafana_data:/var/lib/grafana + +volumes: + dwengo_grafana_data: + dwengo_letsencrypt: + +networks: + dwengo-1: diff --git a/compose.yml b/compose.yml new file mode 100644 index 00000000..2bb736bf --- /dev/null +++ b/compose.yml @@ -0,0 +1,52 @@ +# +# Use this configuration during development. +# +# This configuration is suitable to access the services using their ports. +# +services: + db: + image: postgres:latest + ports: + - '5432:5432' + restart: unless-stopped + volumes: + - dwengo_postgres_data:/var/lib/postgresql/data + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + + idp: # Based on: https://medium.com/@fingervinicius/easy-running-keycloak-with-docker-compose-b0d7a4ee2358 + image: quay.io/keycloak/keycloak:latest + ports: + - '7080:7080' + # - '7443:7443' + command: [ 'start-dev', '--http-port', '7080', '--https-port', '7443', '--import-realm' ] + restart: unless-stopped + volumes: + - ./config/idp:/opt/keycloak/data/import + depends_on: + - db + environment: + KC_HOSTNAME: localhost + KC_HOSTNAME_PORT: 7080 + KC_HOSTNAME_STRICT_BACKCHANNEL: 'true' + KC_BOOTSTRAP_ADMIN_USERNAME: admin + KC_BOOTSTRAP_ADMIN_PASSWORD: admin + KC_HEALTH_ENABLED: 'true' + KC_LOG_LEVEL: info + + logging: + image: grafana/loki:latest + ports: + - '9001:3102' + - '9095:9095' + command: -config.file=/etc/loki/config.yaml + restart: unless-stopped + volumes: + - ./config/loki/config.yml:/etc/loki/config.yaml + - dwengo_loki_data:/loki + +volumes: + dwengo_loki_data: + dwengo_postgres_data: diff --git a/config/nginx/nginx.conf b/config/nginx/nginx.conf index 7a3b995e..975b9580 100644 --- a/config/nginx/nginx.conf +++ b/config/nginx/nginx.conf @@ -15,7 +15,7 @@ http { } server { - listen 80; + listen 8080; location / { root /usr/share/nginx/html; diff --git a/docker-compose.production.yml b/docker-compose.production.yml deleted file mode 100644 index 7480746f..00000000 --- a/docker-compose.production.yml +++ /dev/null @@ -1,111 +0,0 @@ -services: - web: - build: - context: . - dockerfile: ./frontend/Dockerfile - restart: unless-stopped - networks: - - dwengo-1 - labels: - - 'traefik.enable=true' - - 'traefik.http.routers.web.rule=PathPrefix(`/`)' - - 'traefik.http.services.web.loadbalancer.server.port=80' - - api: - build: - context: . - dockerfile: ./backend/Dockerfile - restart: unless-stopped - volumes: - # TODO Replace with environment keys - - ./backend/.env:/app/.env - networks: - - dwengo-1 - depends_on: - - db - - logging - labels: - - 'traefik.enable=true' - - 'traefik.http.middlewares.api-prefix.stripprefix.prefixes=/api' - - 'traefik.http.routers.api.rule=Host(`sel2-1.ugent.be`)' - - 'traefik.http.routers.api.rule=PathPrefix(`/api`)' - - 'traefik.http.routers.api.middlewares=api-prefix' - - 'traefik.http.services.api.loadbalancer.server.port=3000' - - db: - image: postgres:latest - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: postgres - restart: unless-stopped - volumes: - - dwengo_postgres_data:/var/lib/postgresql/data - networks: - - dwengo-1 - - reverse-proxy: - image: traefik:v3.3 - command: > - --api.insecure=true - --providers.docker=true - --providers.docker.exposedbydefault=false - --entrypoints.web.address=:80/tcp - --entrypoints.web.http.redirections.entryPoint.to=websecure - --entrypoints.web.http.redirections.entrypoint.scheme=https - --entrypoints.websecure.address=:443/tcp - --entrypoints.websecure.http.tls=true - --entrypoints.websecure.http.tls.certResolver=letsencrypt - --entrypoints.websecure.http.tls.domains[0].main=sel2-1.ugent.be - --certificatesresolvers.letsencrypt.acme.email=timo.demeyst@ugent.be - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json - --certificatesresolvers.letsencrypt.acme.httpChallenge=true - --certificatesresolvers.letsencrypt.acme.httpChallenge.entrypoint=web - ports: - - '8080:8080' - - '80:80/tcp' - - '443:443/tcp' - restart: unless-stopped - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - - dwengo_letsencrypt:/letsencrypt:ro - networks: - - dwengo-1 - - logging: - image: grafana/loki:latest - ports: - - '3102:3102' - - '9095:9095' - volumes: - - ./config/loki/config.yml:/etc/loki/config.yaml - - dwengo_loki_data:/loki - command: -config.file=/etc/loki/config.yaml - restart: unless-stopped - networks: - - dwengo-1 - labels: - - 'traefik.enable=true' - - 'traefik.http.middlewares.logging-prefix.stripprefix.prefixes=/logging' - - 'traefik.http.routers.web.rule=PathPrefix(`/logging`)' - - 'traefik.http.routers.web.middlewares=logging-prefix' - - 'traefik.http.services.web.loadbalancer.server.port=3102' - - dashboards: - image: grafana/grafana:latest - ports: - - '3100:3000' - volumes: - - dwengo_grafana_data:/var/lib/grafana - restart: unless-stopped - networks: - - dwengo-1 - -volumes: - dwengo_postgres_data: - dwengo_letsencrypt: - dwengo_loki_data: - dwengo_grafana_data: - -networks: - dwengo-1: diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 8d3a0010..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,120 +0,0 @@ -services: - web: - build: - context: . - dockerfile: ./frontend/Dockerfile - ports: - - '8090:80/tcp' - restart: unless-stopped - # networks: - # - dwengo-1 - labels: - - 'traefik.enable=true' - - 'traefik.http.routers.web.rule=PathPrefix(`/`)' - - 'traefik.http.services.web.loadbalancer.server.port=80' - - api: - build: - context: . - dockerfile: ./backend/Dockerfile - ports: - - '3000:3000/tcp' - restart: unless-stopped - volumes: - - ./backend/.env:/app/.env - # networks: - # - dwengo-1 - depends_on: - - db - - logging - labels: - - 'traefik.enable=true' - - 'traefik.http.middlewares.api-prefix.stripprefix.prefixes=/api' - - 'traefik.http.routers.api.rule=PathPrefix(`/api`)' - - 'traefik.http.routers.api.middlewares=api-prefix' - - 'traefik.http.services.api.loadbalancer.server.port=3000' - - db: - image: postgres:latest - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: postgres - ports: - - '5431:5432' - restart: unless-stopped - volumes: - - dwengo_postgres_data:/var/lib/postgresql/data - # networks: - # - dwengo-1 - - reverse-proxy: - image: traefik:v3.3 - command: > - --api.insecure=true - --providers.docker=true - --providers.docker.exposedbydefault=false - --entrypoints.web.address=:80/tcp - ports: - - '8080:8080' - - '80:80/tcp' - # - '443:443/tcp' - restart: unless-stopped - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - - dwengo_letsencrypt:/letsencrypt:ro - # networks: - # - dwengo-1 - - logging: - image: grafana/loki:latest - ports: - - '3102:3102' - - '9095:9095' - volumes: - - ./config/loki/config.yml:/etc/loki/config.yaml - - dwengo_loki_data:/loki - command: -config.file=/etc/loki/config.yaml - restart: unless-stopped - # networks: - # - dwengo-1 - - dashboards: - image: grafana/grafana:latest - ports: - - '3100:3000' - volumes: - - dwengo_grafana_data:/var/lib/grafana - restart: unless-stopped - # networks: - # - dwengo-1 - - idp: # Based on: https://medium.com/@fingervinicius/easy-running-keycloak-with-docker-compose-b0d7a4ee2358 - image: quay.io/keycloak/keycloak:latest - volumes: - - ./idp:/opt/keycloak/data/import - environment: - KC_HOSTNAME: localhost - KC_HOSTNAME_PORT: 7080 - KC_HOSTNAME_STRICT_BACKCHANNEL: 'true' - KC_BOOTSTRAP_ADMIN_USERNAME: admin - KC_BOOTSTRAP_ADMIN_PASSWORD: admin - KC_HEALTH_ENABLED: 'true' - KC_LOG_LEVEL: info - healthcheck: - test: ['CMD', 'curl', '-f', 'http://localhost:7080/health/ready'] - interval: 15s - timeout: 2s - retries: 15 - command: ['start-dev', '--http-port', '7080', '--https-port', '7443', '--import-realm'] - ports: - - '7080:7080' - - '7443:7443' - depends_on: - - db - -volumes: - dwengo_postgres_data: - dwengo_letsencrypt: - dwengo_loki_data: - dwengo_grafana_data: diff --git a/frontend/Dockerfile b/frontend/Dockerfile index b7799a2d..9cbb61ea 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -31,6 +31,6 @@ COPY config/nginx/nginx.conf /etc/nginx/nginx.conf COPY --from=build-stage /app/assets /usr/share/nginx/html/assets COPY --from=build-stage /app/frontend/dist /usr/share/nginx/html -EXPOSE 80 +EXPOSE 8080 CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/src/config.ts b/frontend/src/config.ts index 9feb71b3..3f303846 100644 --- a/frontend/src/config.ts +++ b/frontend/src/config.ts @@ -1,5 +1,5 @@ export const apiConfig = { - baseUrl: window.location.hostname == "localhost" ? "http://localhost:3000" : window.location.origin, + baseUrl: (window.location.hostname === "localhost" && !(window.location.port === '80' || window.location.port === '')) ? "http://localhost:3000/api" : window.location.origin + "/api", }; export const loginRoute = "/login"; From a1aaf342d7554e4bb821958df480ee51c031a634 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Thu, 13 Mar 2025 14:39:12 +0100 Subject: [PATCH 074/106] feat: POST method voor assignment geimplementeerd --- backend/src/controllers/assignments.ts | 32 ++++++++++++++++++- .../entities/assignments/assignment.entity.ts | 4 +-- backend/src/interfaces/assignment.ts | 20 ++++++++++-- backend/src/routes/assignments.ts | 3 ++ backend/src/services/assignments.ts | 27 ++++++++++++++++ 5 files changed, 81 insertions(+), 5 deletions(-) diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index 58897410..dcaa07a2 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -1,5 +1,7 @@ import { Request, Response } from 'express'; -import { getAllAssignments, getAssignment, getAssignmentsSubmissions } from '../services/assignments.js'; +import { createAssignment, getAllAssignments, getAssignment, getAssignmentsSubmissions } from '../services/assignments.js'; +import { AssignmentDTO, mapToAssignment, mapToAssignmentDTO } from '../interfaces/assignment.js'; +import { getAssignmentRepository, getClassRepository } from '../data/repositories.js'; // Typescript is annoy with with parameter forwarding from class.ts interface AssignmentParams { @@ -21,6 +23,34 @@ export async function getAllAssignmentsHandler( }); } +export async function createAssignmentHandler( + req: Request, + res: Response, +): Promise { + const classid = req.params.classid; + const assignmentData = req.body as AssignmentDTO; + + if (!assignmentData.description + || !assignmentData.language + || !assignmentData.learningPath + || !assignmentData.title + ) { + res.status(400).json({ + error: 'Missing one or more required fields: title, description, learningPath, title', + }); + return; + } + + const assignment = createAssignment(classid, assignmentData); + + if (!assignment) { + res.status(500).json({ error: "Could not create assignment "}); + return; + } + + res.status(201).json({ assignment: assignment }); +} + export async function getAssignmentHandler( req: Request, res: Response diff --git a/backend/src/entities/assignments/assignment.entity.ts b/backend/src/entities/assignments/assignment.entity.ts index f6e3c3eb..ecb02b90 100644 --- a/backend/src/entities/assignments/assignment.entity.ts +++ b/backend/src/entities/assignments/assignment.entity.ts @@ -25,8 +25,8 @@ export class Assignment { }) within!: Class; - @PrimaryKey({ type: 'number' }) - id!: number; + @PrimaryKey({ type: 'number', autoincrement: true }) + id?: number; @Property({ type: 'string' }) title!: string; diff --git a/backend/src/interfaces/assignment.ts b/backend/src/interfaces/assignment.ts index ad6d8330..de7ce25f 100644 --- a/backend/src/interfaces/assignment.ts +++ b/backend/src/interfaces/assignment.ts @@ -1,4 +1,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'; export interface AssignmentDTO { @@ -13,7 +16,7 @@ export interface AssignmentDTO { export function mapToAssignmentDTOId(assignment: Assignment): AssignmentDTO { return { - id: assignment.id, + id: assignment.id!, class: assignment.within.classId, title: assignment.title, description: assignment.description, @@ -25,7 +28,7 @@ export function mapToAssignmentDTOId(assignment: Assignment): AssignmentDTO { export function mapToAssignmentDTO(assignment: Assignment): AssignmentDTO { return { - id: assignment.id, + id: assignment.id!, class: assignment.within.classId, title: assignment.title, description: assignment.description, @@ -34,3 +37,16 @@ export function mapToAssignmentDTO(assignment: Assignment): AssignmentDTO { // Groups: assignment.groups.map(mapToGroupDTO), }; } + +export function mapToAssignment(assignmentData: AssignmentDTO, cls: Class): Assignment { + const assignment = new Assignment(); + assignment.title = assignmentData.title; + assignment.description = assignmentData.description; + assignment.learningPathHruid = assignmentData.learningPath; + assignment.learningPathLanguage = languageMap[assignmentData.language] || FALLBACK_LANG; + assignment.within = cls; + + console.log(assignment); + + return assignment; +} diff --git a/backend/src/routes/assignments.ts b/backend/src/routes/assignments.ts index bbc29194..a733d093 100644 --- a/backend/src/routes/assignments.ts +++ b/backend/src/routes/assignments.ts @@ -1,5 +1,6 @@ import express from 'express'; import { + createAssignmentHandler, getAllAssignmentsHandler, getAssignmentHandler, getAssignmentsSubmissionsHandler, @@ -11,6 +12,8 @@ const router = express.Router({ mergeParams: true }); // Root endpoint used to search objects router.get('/', getAllAssignmentsHandler); +router.post('/', createAssignmentHandler); + // Information about an assignment with id 'id' router.get('/:id', getAssignmentHandler); diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts index 1f50a1c9..e67c2c0c 100644 --- a/backend/src/services/assignments.ts +++ b/backend/src/services/assignments.ts @@ -4,8 +4,10 @@ import { getGroupRepository, getSubmissionRepository, } from '../data/repositories.js'; +import { Assignment } from '../entities/assignments/assignment.entity.js'; import { AssignmentDTO, + mapToAssignment, mapToAssignmentDTO, mapToAssignmentDTOId, } from '../interfaces/assignment.js'; @@ -33,6 +35,31 @@ export async function getAllAssignments( return assignments.map(mapToAssignmentDTOId); } +export async function createAssignment( + classid: string, + assignmentData: AssignmentDTO, +): Promise { + const classRepository = getClassRepository(); + const cls = await classRepository.findById(classid); + + if (!cls) { + return null; + } + + const assignment = mapToAssignment(assignmentData, cls); + const assignmentRepository = getAssignmentRepository(); + + try { + const newAssignment = assignmentRepository.create(assignment); + await assignmentRepository.save(newAssignment); + + return newAssignment; + } catch(e) { + return null; + } + +} + export async function getAssignment( classid: string, id: number From 3dd1edc95de924b6a1e7b3355f4e9d07af4f020c Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Thu, 13 Mar 2025 15:02:11 +0100 Subject: [PATCH 075/106] feat: question route als subroute van learning objects --- backend/src/controllers/questions.ts | 118 +++++++++++++++++++++++++ backend/src/interfaces/answer.ts | 38 ++++++++ backend/src/interfaces/question.ts | 52 ++++++----- backend/src/routes/learning-objects.ts | 4 + backend/src/routes/questions.ts | 44 ++++----- backend/src/services/questions.ts | 103 +++++++++++++++++++++ 6 files changed, 310 insertions(+), 49 deletions(-) create mode 100644 backend/src/controllers/questions.ts create mode 100644 backend/src/interfaces/answer.ts create mode 100644 backend/src/services/questions.ts diff --git a/backend/src/controllers/questions.ts b/backend/src/controllers/questions.ts new file mode 100644 index 00000000..850dcdf7 --- /dev/null +++ b/backend/src/controllers/questions.ts @@ -0,0 +1,118 @@ +import {Request, Response} from "express"; +import { + createQuestion, + deleteQuestion, + getAllQuestions, + getAnswersByQuestion, + getQuestion +} from "../services/questions.js"; +import {QuestionDTO, QuestionId} from "../interfaces/question.js"; +import {FALLBACK_LANG, FALLBACK_SEQ_NUM} from "../config.js"; +import {LearningObjectIdentifier} from "../entities/content/learning-object-identifier.js"; +import {Language} from "../entities/content/language.js"; + +function getObjectId(req: Request, res: Response): LearningObjectIdentifier | null { + const { hruid, version} = req.params + const lang = req.query.lang + + if (!hruid || !version ) { + res.status(400).json({ error: "Missing required parameters." }); + return null; + } + + return { + hruid, + language: lang as Language || FALLBACK_LANG, + version + } +} + +function getQuestionId(req: Request, res: Response): QuestionId | null { + const seq = req.params.seq + const learningObjectIdentifier = getObjectId(req,res); + + if (!learningObjectIdentifier) + return null + + return { + learningObjectIdentifier, + sequenceNumber: seq ? Number(seq) : FALLBACK_SEQ_NUM + } +} + +export async function getAllQuestionsHandler( + req: Request, + res: Response +): Promise { + const objectId = getObjectId(req, res); + const full = req.query.full === 'true'; + + if (!objectId) + return + + const questions = await getAllQuestions(objectId, full); + + if (!questions) + res.status(404).json({ error: `Questions not found.` }); + else + res.json(questions); +} + +export async function getQuestionHandler(req: Request, res: Response): Promise { + const questionId = getQuestionId(req, res); + + if (!questionId) + return + + const question = await getQuestion(questionId); + + if (!question) + res.status(404).json({ error: `Question not found.` }); + else + res.json(question) +} + +export async function getQuestionAnswersHandler(req: Request, res: Response): Promise { + const questionId = getQuestionId(req, res); + const full = req.query.full === 'true'; + + if (!questionId) + return + + const answers = getAnswersByQuestion(questionId, full); + + if (!answers) + res.status(404).json({ error: `Questions not found.` }); + else + res.json(answers) +} + +export async function createQuestionHandler(req: Request, res: Response): Promise { + const questionDTO = req.body as QuestionDTO; + + if (!questionDTO.learningObjectIdentifier || !questionDTO.author || !questionDTO.content) { + res.status(400).json({error: 'Missing required fields: identifier and content'}); + return; + } + + const question = await createQuestion(questionDTO); + + if (!question) + res.status(400).json({error: 'Could not add question'}); + else + res.json(question) +} + +export async function deleteQuestionHandler(req: Request, res: Response): Promise { + const questionId = getQuestionId(res, req) + + const question = await deleteQuestion(questionId); + + if (!question) + res.status(400).json({error: 'Could not find nor delete question'}); + else + res.json(question) +} + + + diff --git a/backend/src/interfaces/answer.ts b/backend/src/interfaces/answer.ts new file mode 100644 index 00000000..1b363c0d --- /dev/null +++ b/backend/src/interfaces/answer.ts @@ -0,0 +1,38 @@ +import {mapToUserDTO, UserDTO} from "./user.js"; +import {mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId} from "./question.js"; +import {Answer} from "../entities/questions/answer.entity.js"; + +export interface AnswerDTO { + author: UserDTO; + toQuestion: QuestionDTO; + sequenceNumber: number; + timestamp: string; + content: string; +} + +/** + * Convert a Question entity to a DTO format. + */ +export function mapToAnswerDTO(answer: Answer): AnswerDTO { + return { + author: mapToUserDTO(answer.author), + toQuestion: mapToQuestionDTO(answer.toQuestion), + sequenceNumber: answer.sequenceNumber, + timestamp: answer.timestamp.toISOString(), + content: answer.content, + }; +} + +export interface AnswerId { + author: string; + toQuestion: QuestionId; + sequenceNumber: number; +} + +export function mapToAnswerId(answer: AnswerDTO): AnswerId { + return { + author: answer.author.username, + toQuestion: mapToQuestionId(answer.toQuestion), + sequenceNumber: answer.sequenceNumber + } +} diff --git a/backend/src/interfaces/question.ts b/backend/src/interfaces/question.ts index 90de4999..33dced69 100644 --- a/backend/src/interfaces/question.ts +++ b/backend/src/interfaces/question.ts @@ -1,48 +1,56 @@ import { Question } from '../entities/questions/question.entity.js'; +import {mapToUserDTO, UserDTO} from "./user.js"; +import {LearningObjectIdentifier} from "../entities/content/learning-object-identifier.js"; +import {Teacher} from "../entities/users/teacher.entity"; export interface QuestionDTO { - learningObjectHruid: string; - learningObjectLanguage: string; - learningObjectVersion: string; - sequenceNumber: number; - authorUsername: string; - timestamp: string; + learningObjectIdentifier: LearningObjectIdentifier; + sequenceNumber?: number; + author: UserDTO; + timestamp?: string; content: string; - endpoints?: { - classes: string; - questions: string; - invitations: string; - groups: string; - }; } /** * Convert a Question entity to a DTO format. */ export function mapToQuestionDTO(question: Question): QuestionDTO { + const learningObjectIdentifier = { + hruid: question.learningObjectHruid, + language: question.learningObjectLanguage, + version: question.learningObjectVersion + } + return { - learningObjectHruid: question.learningObjectHruid, - learningObjectLanguage: question.learningObjectLanguage, - learningObjectVersion: question.learningObjectVersion, + learningObjectIdentifier, sequenceNumber: question.sequenceNumber, - authorUsername: question.author.username, + author: question.author, timestamp: question.timestamp.toISOString(), content: question.content, }; } export interface QuestionId { - learningObjectHruid: string; - learningObjectLanguage: string; - learningObjectVersion: string; + learningObjectIdentifier: LearningObjectIdentifier; sequenceNumber: number; } export function mapToQuestionId(question: QuestionDTO): QuestionId { return { - learningObjectHruid: question.learningObjectHruid, - learningObjectLanguage: question.learningObjectLanguage, - learningObjectVersion: question.learningObjectVersion, + learningObjectIdentifier: question.learningObjectIdentifier, sequenceNumber: question.sequenceNumber, }; } + +export function mapToQuestion(questionDTO: QuestionDTO): Question { + const question = new Question(); + question.author = mapToUserDTO(questionDTO.author); + question.learningObjectHruid = questionDTO.learningObjectIdentifier.hruid; + question.learningObjectLanguage = questionDTO.learningObjectIdentifier.language; + question.learningObjectVersion = questionDTO.learningObjectIdentifier.version; + question.content = questionDTO.content; + question.sequenceNumber = questionDTO.sequenceNumber; + question.timestamp = questionDTO.timestamp; + + return question; +} diff --git a/backend/src/routes/learning-objects.ts b/backend/src/routes/learning-objects.ts index 77094955..6b708d4b 100644 --- a/backend/src/routes/learning-objects.ts +++ b/backend/src/routes/learning-objects.ts @@ -5,6 +5,8 @@ import { } from '../controllers/learning-objects.js'; import submissionRoutes from './submissions.js'; +import questionRoutes from './questions.js'; + const router = express.Router(); @@ -28,4 +30,6 @@ router.get('/:hruid', getLearningObject); router.use('/:hruid/submissions', submissionRoutes); +router.use('/:hruid/:version/questions', questionRoutes) + export default router; diff --git a/backend/src/routes/questions.ts b/backend/src/routes/questions.ts index f683d998..d635a85e 100644 --- a/backend/src/routes/questions.ts +++ b/backend/src/routes/questions.ts @@ -1,34 +1,24 @@ import express from 'express'; -const router = express.Router(); +import { + createQuestionHandler, deleteQuestionHandler, + getAllQuestionsHandler, + getQuestionAnswersHandler, + getQuestionHandler +} from "../controllers/questions.js"; +const router = express.Router({ mergeParams: true }); + +// query language // Root endpoint used to search objects -router.get('/', (req, res) => { - res.json({ - questions: ['0', '1'], - }); -}); +router.get('/', getAllQuestionsHandler); -// Information about an question with id 'id' -router.get('/:id', (req, res) => { - res.json({ - id: req.params.id, - student: '0', - group: '0', - time: new Date(2025, 1, 1), - content: - 'Zijn alle gehele getallen groter dan 2 gelijk aan de som van 2 priemgetallen????', - learningObject: '0', - links: { - self: `${req.baseUrl}/${req.params.id}`, - answers: `${req.baseUrl}/${req.params.id}/answers`, - }, - }); -}); +router.post('/', createQuestionHandler); -router.get('/:id/answers', (req, res) => { - res.json({ - answers: ['0'], - }); -}); +router.delete('/:seq', deleteQuestionHandler); + +// Information about a question with id +router.get('/:seq', getQuestionHandler); + +router.get('/answers/:seq', getQuestionAnswersHandler); export default router; diff --git a/backend/src/services/questions.ts b/backend/src/services/questions.ts new file mode 100644 index 00000000..e3c64bbb --- /dev/null +++ b/backend/src/services/questions.ts @@ -0,0 +1,103 @@ +import {getAnswerRepository, getQuestionRepository} from "../data/repositories.js"; +import {mapToQuestion, 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 {QuestionRepository} from "../data/questions/question-repository.js"; +import {LearningObjectIdentifier} from "../entities/content/learning-object-identifier.js"; + +export async function getAllQuestions( + id: LearningObjectIdentifier, full: boolean +): Promise { + const questionRepository: QuestionRepository = getQuestionRepository(); + const questions = await questionRepository.findAllQuestionsAboutLearningObject(id); + + if (!questions) { + return []; + } + + const questionsDTO: QuestionDTO[] = questions.map(mapToQuestionDTO); + + if (full) { + return questionsDTO; + } + + return questionsDTO.map(mapToQuestionId); +} + +async function fetchQuestion(questionId: QuestionId): Promise { + const questionRepository = getQuestionRepository(); + + return await questionRepository.findOne( + { + learningObjectHruid: questionId.learningObjectIdentifier.hruid, + learningObjectLanguage: questionId.learningObjectIdentifier.language, + learningObjectVersion: questionId.learningObjectIdentifier.version, + sequenceNumber: questionId.sequenceNumber + } + ) +} + +export async function getQuestion(questionId: QuestionId): Promise { + const question = await fetchQuestion(questionId); + + if (!question) + return null + + return mapToQuestionDTO(question); +} + +export async function getAnswersByQuestion(questionId: QuestionId, full: boolean) { + const answerRepository = getAnswerRepository(); + const question = await fetchQuestion(questionId); + + if (!question) + return []; + + const answers: Answer[] = await answerRepository.findAllAnswersToQuestion(question); + + if (!answers) + return [] + + const answersDTO = answers.map(mapToAnswerDTO); + + if (full) + return answersDTO + + return answersDTO.map(mapToAnswerId); +} + +export async function createQuestion(questionDTO: QuestionDTO) { + const questionRepository = getQuestionRepository(); + const question = mapToQuestion(questionDTO); + + try { + const newQuestion = questionRepository.create(question) + await questionRepository.save(newQuestion); + } catch (e) { + return null + } + + return newQuestion; +} + +export async function deleteQuestion(questionId: QuestionId) { + const questionRepository = getQuestionRepository(); + + const question = await fetchQuestion(questionId); + + if (!question) + return null + + try { + await questionRepository.removeQuestionByLearningObjectAndSequenceNumber( + questionId.learningObjectIdentifier, questionId.sequenceNumber + ); + } catch (e) { + return null + } + + return question +} + + From 79422ab85429a440f0908603f38d4a7cdae36199 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Thu, 13 Mar 2025 15:11:29 +0100 Subject: [PATCH 076/106] feat: POST method voor class geimplementeerd --- backend/src/controllers/classes.ts | 26 +++++++++++++++ backend/src/entities/classes/class.entity.ts | 2 +- backend/src/interfaces/assignment.ts | 4 +-- backend/src/interfaces/class.ts | 18 ++++++++++- backend/src/interfaces/group.ts | 2 +- backend/src/routes/classes.ts | 3 ++ backend/src/services/class.ts | 34 +++++++++++++++++++- backend/src/services/students.ts | 4 +-- 8 files changed, 85 insertions(+), 8 deletions(-) diff --git a/backend/src/controllers/classes.ts b/backend/src/controllers/classes.ts index b8061b5a..5fd4ce9d 100644 --- a/backend/src/controllers/classes.ts +++ b/backend/src/controllers/classes.ts @@ -1,11 +1,14 @@ import { Request, Response } from 'express'; import { + createClass, getAllClasses, getClass, getClassStudents, getClassStudentsIds, getClassTeacherInvitations, } from '../services/class.js'; +import { ClassDTO, mapToClass } from '../interfaces/class.js'; +import { getClassRepository, getStudentRepository, getTeacherRepository } from '../data/repositories.js'; export async function getAllClassesHandler( req: Request, @@ -19,6 +22,29 @@ export async function getAllClassesHandler( }); } +export async function createClassHandler( + req: Request, + res: Response, +): Promise { + const classData = req.body as ClassDTO; + + if (!classData.displayName) { + res.status(400).json({ + error: 'Missing one or more required fields: displayName', + }); + return; + } + + const cls = await createClass(classData); + + if (!cls) { + res.status(500).json({ error: "Something went wrong while creating class" }); + return + } + + res.status(201).json({ class: cls }); +} + export async function getClassHandler( req: Request, res: Response diff --git a/backend/src/entities/classes/class.entity.ts b/backend/src/entities/classes/class.entity.ts index b40b5baf..b9fd4d0e 100644 --- a/backend/src/entities/classes/class.entity.ts +++ b/backend/src/entities/classes/class.entity.ts @@ -17,7 +17,7 @@ import { ClassRepository } from '../../data/classes/class-repository.js'; }) export class Class { @PrimaryKey() - classId = v4(); + classId? = v4(); @Property({ type: 'string' }) displayName!: string; diff --git a/backend/src/interfaces/assignment.ts b/backend/src/interfaces/assignment.ts index de7ce25f..8f6120b6 100644 --- a/backend/src/interfaces/assignment.ts +++ b/backend/src/interfaces/assignment.ts @@ -17,7 +17,7 @@ export interface AssignmentDTO { export function mapToAssignmentDTOId(assignment: Assignment): AssignmentDTO { return { id: assignment.id!, - class: assignment.within.classId, + class: assignment.within.classId!, title: assignment.title, description: assignment.description, learningPath: assignment.learningPathHruid, @@ -29,7 +29,7 @@ export function mapToAssignmentDTOId(assignment: Assignment): AssignmentDTO { export function mapToAssignmentDTO(assignment: Assignment): AssignmentDTO { return { id: assignment.id!, - class: assignment.within.classId, + class: assignment.within.classId!, title: assignment.title, description: assignment.description, learningPath: assignment.learningPathHruid, diff --git a/backend/src/interfaces/class.ts b/backend/src/interfaces/class.ts index c18ee842..379a8635 100644 --- a/backend/src/interfaces/class.ts +++ b/backend/src/interfaces/class.ts @@ -1,4 +1,7 @@ +import { Collection } from '@mikro-orm/core'; import { Class } from '../entities/classes/class.entity.js'; +import { Student } from '../entities/users/student.entity.js'; +import { Teacher } from '../entities/users/teacher.entity.js'; export interface ClassDTO { id: string; @@ -16,7 +19,7 @@ export interface ClassDTO { export function mapToClassDTO(cls: Class): ClassDTO { return { - id: cls.classId, + id: cls.classId!, displayName: cls.displayName, teachers: cls.teachers.map((teacher) => { return teacher.username; @@ -27,3 +30,16 @@ export function mapToClassDTO(cls: Class): ClassDTO { joinRequests: [], // TODO }; } + +export function mapToClass( + classData: ClassDTO, + students: Collection, + teachers: Collection +): Class { + const cls = new Class(); + cls.displayName = classData.displayName; + cls.students = students; + cls.teachers = teachers; + + return cls; +} diff --git a/backend/src/interfaces/group.ts b/backend/src/interfaces/group.ts index 9c4cecdc..ca7f687a 100644 --- a/backend/src/interfaces/group.ts +++ b/backend/src/interfaces/group.ts @@ -18,7 +18,7 @@ export function mapToGroupDTO(group: Group): GroupDTO { export function mapToGroupDTOId(group: Group): GroupDTO { return { - assignment: group.assignment.id, + assignment: group.assignment.id!, groupNumber: group.groupNumber, members: group.members.map((member) => { return member.username; diff --git a/backend/src/routes/classes.ts b/backend/src/routes/classes.ts index c67b573b..e0972988 100644 --- a/backend/src/routes/classes.ts +++ b/backend/src/routes/classes.ts @@ -1,5 +1,6 @@ import express from 'express'; import { + createClassHandler, getAllClassesHandler, getClassHandler, getClassStudentsHandler, @@ -12,6 +13,8 @@ const router = express.Router(); // Root endpoint used to search objects router.get('/', getAllClassesHandler); +router.post('/', createClassHandler); + // Information about an class with id 'id' router.get('/:id', getClassHandler); diff --git a/backend/src/services/class.ts b/backend/src/services/class.ts index 45185383..a7f5f7ea 100644 --- a/backend/src/services/class.ts +++ b/backend/src/services/class.ts @@ -1,7 +1,10 @@ import { getClassRepository, + getStudentRepository, getTeacherInvitationRepository, + getTeacherRepository, } from '../data/repositories.js'; +import { Class } from '../entities/classes/class.entity.js'; import { ClassDTO, mapToClassDTO } from '../interfaces/class.js'; import { mapToStudentDTO, StudentDTO } from '../interfaces/student.js'; import { @@ -27,10 +30,39 @@ export async function getAllClasses( return classes.map(mapToClassDTO); } return classes.map((cls) => { - return cls.classId; + return cls.classId!; }); } +export async function createClass(classData: ClassDTO): Promise { + const teacherRepository = getTeacherRepository(); + const teacherUsernames = classData.teachers || []; + const teachers = (await Promise.all(teacherUsernames.map(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 cls = mapToClass(classData, teachers, students); + + const classRepository = getClassRepository(); + + try { + const newClass = classRepository.create({ + displayName: classData.displayName, + teachers: teachers, + students: students, + }); + await classRepository.save(newClass); + + return newClass; + } catch(e) { + return null; + } +} + export async function getClass(classId: string): Promise { const classRepository = getClassRepository(); const cls = await classRepository.findById(classId); diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts index 9524f281..602ee4e1 100644 --- a/backend/src/services/students.ts +++ b/backend/src/services/students.ts @@ -34,7 +34,7 @@ export async function getStudentClasses(username: string, full: boolean): Promis return classes.map(mapToClassDTO); } - return classes.map((cls) => cls.classId); + return classes.map((cls) => cls.classId!); } export async function getStudentAssignments(username: string, full: boolean): Promise { @@ -51,7 +51,7 @@ export async function getStudentAssignments(username: string, full: boolean): Pr const assignments = ( await Promise.all( classes.map(async (cls) => { - return await getAllAssignments(cls.classId, full); + return await getAllAssignments(cls.classId!, full); }) ) ).flat(); From e3e1fc3f05d2d817e958471e329d32c6b2559a76 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Thu, 13 Mar 2025 15:30:07 +0100 Subject: [PATCH 077/106] fix: gebruik create question functie --- backend/src/config.ts | 6 +++++- backend/src/controllers/questions.ts | 5 ++++- backend/src/interfaces/question.ts | 19 +++---------------- backend/src/services/questions.ts | 16 +++++++++++----- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/backend/src/config.ts b/backend/src/config.ts index 8fd8ec3f..5c7dd858 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -5,6 +5,10 @@ // Load .env file // Dotenv.config(); +import {Language} from "./entities/content/language.js"; + export const DWENGO_API_BASE = 'https://dwengo.org/backend/api'; -export const FALLBACK_LANG = 'nl'; +export const FALLBACK_LANG: Language = Language.Dutch; + +export const FALLBACK_SEQ_NUM = 1; diff --git a/backend/src/controllers/questions.ts b/backend/src/controllers/questions.ts index 850dcdf7..aa39853a 100644 --- a/backend/src/controllers/questions.ts +++ b/backend/src/controllers/questions.ts @@ -104,7 +104,10 @@ export async function createQuestionHandler(req: Request, res: Response): Promis } export async function deleteQuestionHandler(req: Request, res: Response): Promise { - const questionId = getQuestionId(res, req) + const questionId = getQuestionId(req, res); + + if (!questionId) + return const question = await deleteQuestion(questionId); diff --git a/backend/src/interfaces/question.ts b/backend/src/interfaces/question.ts index 33dced69..23c3edea 100644 --- a/backend/src/interfaces/question.ts +++ b/backend/src/interfaces/question.ts @@ -1,7 +1,6 @@ import { Question } from '../entities/questions/question.entity.js'; -import {mapToUserDTO, UserDTO} from "./user.js"; +import {UserDTO} from "./user.js"; import {LearningObjectIdentifier} from "../entities/content/learning-object-identifier.js"; -import {Teacher} from "../entities/users/teacher.entity"; export interface QuestionDTO { learningObjectIdentifier: LearningObjectIdentifier; @@ -23,7 +22,7 @@ export function mapToQuestionDTO(question: Question): QuestionDTO { return { learningObjectIdentifier, - sequenceNumber: question.sequenceNumber, + sequenceNumber: question.sequenceNumber!, author: question.author, timestamp: question.timestamp.toISOString(), content: question.content, @@ -38,19 +37,7 @@ export interface QuestionId { export function mapToQuestionId(question: QuestionDTO): QuestionId { return { learningObjectIdentifier: question.learningObjectIdentifier, - sequenceNumber: question.sequenceNumber, + sequenceNumber: question.sequenceNumber!, }; } -export function mapToQuestion(questionDTO: QuestionDTO): Question { - const question = new Question(); - question.author = mapToUserDTO(questionDTO.author); - question.learningObjectHruid = questionDTO.learningObjectIdentifier.hruid; - question.learningObjectLanguage = questionDTO.learningObjectIdentifier.language; - question.learningObjectVersion = questionDTO.learningObjectIdentifier.version; - question.content = questionDTO.content; - question.sequenceNumber = questionDTO.sequenceNumber; - question.timestamp = questionDTO.timestamp; - - return question; -} diff --git a/backend/src/services/questions.ts b/backend/src/services/questions.ts index e3c64bbb..a2fa01c6 100644 --- a/backend/src/services/questions.ts +++ b/backend/src/services/questions.ts @@ -1,10 +1,12 @@ import {getAnswerRepository, getQuestionRepository} from "../data/repositories.js"; -import {mapToQuestion, mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId} from "../interfaces/question.js"; +import {mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId} from "../interfaces/question.js"; import {Question} from "../entities/questions/question.entity.js"; import {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"; export async function getAllQuestions( id: LearningObjectIdentifier, full: boolean @@ -69,16 +71,20 @@ export async function getAnswersByQuestion(questionId: QuestionId, full: boolean export async function createQuestion(questionDTO: QuestionDTO) { const questionRepository = getQuestionRepository(); - const question = mapToQuestion(questionDTO); + + const author = mapToUser(questionDTO.author, new Student()) try { - const newQuestion = questionRepository.create(question) - await questionRepository.save(newQuestion); + await questionRepository.createQuestion({ + loId: questionDTO.learningObjectIdentifier, + author, + content: questionDTO.content } + ); } catch (e) { return null } - return newQuestion; + return questionDTO; } export async function deleteQuestion(questionId: QuestionId) { From 4c76a8217868f26741ba4de5fb790cb4c899f124 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Thu, 13 Mar 2025 15:49:13 +0100 Subject: [PATCH 078/106] feat: (broken) POST method voor groep --- backend/src/controllers/groups.ts | 26 +++++++++- .../src/entities/assignments/group.entity.ts | 4 +- backend/src/interfaces/group.ts | 4 +- backend/src/interfaces/teacher-invitation.ts | 2 +- backend/src/routes/groups.ts | 4 +- backend/src/services/groups.ts | 48 +++++++++++++++++++ 6 files changed, 81 insertions(+), 7 deletions(-) diff --git a/backend/src/controllers/groups.ts b/backend/src/controllers/groups.ts index 8b26b810..5fd90553 100644 --- a/backend/src/controllers/groups.ts +++ b/backend/src/controllers/groups.ts @@ -1,5 +1,6 @@ import { Request, Response } from 'express'; -import { getAllGroups, getGroup, getGroupSubmissions } from '../services/groups.js'; +import { createGroup, getAllGroups, getGroup, getGroupSubmissions } from '../services/groups.js'; +import { GroupDTO } from '../interfaces/group.js'; // Typescript is annoywith with parameter forwarding from class.ts interface GroupParams { @@ -54,6 +55,29 @@ export async function getAllGroupsHandler( }); } +export async function createGroupHandler( + req: Request, + res: Response, +): Promise { + const classid = req.params.classid; + const assignmentId = +req.params.assignmentid; + + if (isNaN(assignmentId)) { + res.status(400).json({ error: 'Assignment id must be a number' }); + return; + } + + const groupData = req.body as GroupDTO; + const group = createGroup(groupData, classid, assignmentId); + + if (!group) { + res.status(500).json({ error: "Something went wrong while creating group" }); + return + } + + res.status(201).json({ group: group }); +} + export async function getGroupSubmissionsHandler( req: Request, res: Response, diff --git a/backend/src/entities/assignments/group.entity.ts b/backend/src/entities/assignments/group.entity.ts index 9c2ed2cf..0db4f6b0 100644 --- a/backend/src/entities/assignments/group.entity.ts +++ b/backend/src/entities/assignments/group.entity.ts @@ -17,8 +17,8 @@ export class Group { }) assignment!: Assignment; - @PrimaryKey({ type: 'integer' }) - groupNumber!: number; + @PrimaryKey({ type: 'integer', autoincrement: true }) + groupNumber?: number; @ManyToMany({ entity: () => { diff --git a/backend/src/interfaces/group.ts b/backend/src/interfaces/group.ts index ca7f687a..2dd3f2c1 100644 --- a/backend/src/interfaces/group.ts +++ b/backend/src/interfaces/group.ts @@ -11,7 +11,7 @@ export interface GroupDTO { export function mapToGroupDTO(group: Group): GroupDTO { return { assignment: mapToAssignmentDTO(group.assignment), // ERROR: , group.assignment.within), - groupNumber: group.groupNumber, + groupNumber: group.groupNumber!, members: group.members.map(mapToStudentDTO), }; } @@ -19,7 +19,7 @@ export function mapToGroupDTO(group: Group): GroupDTO { export function mapToGroupDTOId(group: Group): GroupDTO { return { assignment: group.assignment.id!, - groupNumber: group.groupNumber, + groupNumber: group.groupNumber!, members: group.members.map((member) => { return member.username; }), diff --git a/backend/src/interfaces/teacher-invitation.ts b/backend/src/interfaces/teacher-invitation.ts index badbbbd7..d29e7476 100644 --- a/backend/src/interfaces/teacher-invitation.ts +++ b/backend/src/interfaces/teacher-invitation.ts @@ -24,6 +24,6 @@ export function mapToTeacherInvitationDTOIds( return { sender: invitation.sender.username, receiver: invitation.receiver.username, - class: invitation.class.classId, + class: invitation.class.classId!, }; } diff --git a/backend/src/routes/groups.ts b/backend/src/routes/groups.ts index d17a330e..b3be0f7a 100644 --- a/backend/src/routes/groups.ts +++ b/backend/src/routes/groups.ts @@ -1,11 +1,13 @@ import express from 'express'; -import { getAllGroupsHandler, getGroupHandler, getGroupSubmissionsHandler } from '../controllers/groups.js'; +import { createGroupHandler, getAllGroupsHandler, getGroupHandler, getGroupSubmissionsHandler } from '../controllers/groups.js'; const router = express.Router({ mergeParams: true }); // Root endpoint used to search objects router.get('/', getAllGroupsHandler); +router.post('/', createGroupHandler); + // Information about a group (members, ... [TODO DOC]) router.get('/:groupid', getGroupHandler); diff --git a/backend/src/services/groups.ts b/backend/src/services/groups.ts index a2af8e4a..65e111fb 100644 --- a/backend/src/services/groups.ts +++ b/backend/src/services/groups.ts @@ -1,9 +1,12 @@ +import { GroupRepository } from '../data/assignments/group-repository.js'; import { getAssignmentRepository, getClassRepository, getGroupRepository, + getStudentRepository, getSubmissionRepository, } from '../data/repositories.js'; +import { Group } from '../entities/assignments/group.entity.js'; import { GroupDTO, mapToGroupDTO, @@ -51,6 +54,51 @@ export async function getGroup( return mapToGroupDTOId(group); } +export async function createGroup( + groupData: GroupDTO, + classid: string, + assignmentNumber: number, +): Promise { + 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); + + console.log(members); + + const classRepository = getClassRepository(); + const cls = await classRepository.findById(classid); + + if (!cls) { + return null; + } + + const assignmentRepository = getAssignmentRepository(); + const assignment = await assignmentRepository.findByClassAndId(cls, assignmentNumber); + + if (!assignment) { + return null; + } + + const groupRepository = getGroupRepository(); + try { + console.log('EEEEE'); + const newGroup = groupRepository.create({ + assignment: assignment, + members: members, + }); + console.log('OOOOOO'); + await groupRepository.save(newGroup); + console.log('AAAAAA'); + + return newGroup; + } catch(e) { + console.log(e); + return null; + } +} + export async function getAllGroups( classId: string, assignmentNumber: number, From 53c85eadb09706eeeb4f1c04e2a31dbf224f5c6d Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Thu, 13 Mar 2025 16:39:59 +0100 Subject: [PATCH 079/106] feat: (BROKEN) kolossale bug in POST group aan het fixen --- backend/src/data/users/student-repository.ts | 15 +++++++++++++-- backend/src/data/users/teacher-repository.ts | 10 +++++++++- .../src/entities/assignments/assignment.entity.ts | 3 ++- backend/src/entities/assignments/group.entity.ts | 10 ++++------ backend/src/entities/users/student.entity.ts | 8 ++++++++ backend/src/services/groups.ts | 3 --- 6 files changed, 36 insertions(+), 13 deletions(-) diff --git a/backend/src/data/users/student-repository.ts b/backend/src/data/users/student-repository.ts index 6835e430..d3be9081 100644 --- a/backend/src/data/users/student-repository.ts +++ b/backend/src/data/users/student-repository.ts @@ -1,4 +1,15 @@ import { Student } from '../../entities/users/student.entity.js'; -import { UserRepository } from './user-repository.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 UserRepository {} + +export class StudentRepository extends DwengoEntityRepository { + public findByUsername(username: string): Promise { + return this.findOne({ username: username }); + } + public deleteByUsername(username: string): Promise { + return this.deleteWhere({ username: username }); + } +} \ No newline at end of file diff --git a/backend/src/data/users/teacher-repository.ts b/backend/src/data/users/teacher-repository.ts index 4c4f7687..23ba559e 100644 --- a/backend/src/data/users/teacher-repository.ts +++ b/backend/src/data/users/teacher-repository.ts @@ -1,4 +1,12 @@ 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 UserRepository {} +export class TeacherRepository extends DwengoEntityRepository { + public findByUsername(username: string): Promise { + return this.findOne({ username: username }); + } + public deleteByUsername(username: string): Promise { + return this.deleteWhere({ username: username }); + } +} \ No newline at end of file diff --git a/backend/src/entities/assignments/assignment.entity.ts b/backend/src/entities/assignments/assignment.entity.ts index ecb02b90..015dcdc5 100644 --- a/backend/src/entities/assignments/assignment.entity.ts +++ b/backend/src/entities/assignments/assignment.entity.ts @@ -1,4 +1,5 @@ import { + Collection, Entity, Enum, ManyToOne, @@ -50,5 +51,5 @@ export class Assignment { }, mappedBy: 'assignment', }) - groups!: Group[]; + groups!: Collection; } diff --git a/backend/src/entities/assignments/group.entity.ts b/backend/src/entities/assignments/group.entity.ts index 0db4f6b0..446c0c0d 100644 --- a/backend/src/entities/assignments/group.entity.ts +++ b/backend/src/entities/assignments/group.entity.ts @@ -1,4 +1,4 @@ -import { Entity, ManyToMany, ManyToOne, PrimaryKey } from '@mikro-orm/core'; +import { Collection, 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'; @@ -20,10 +20,8 @@ export class Group { @PrimaryKey({ type: 'integer', autoincrement: true }) groupNumber?: number; - @ManyToMany({ - entity: () => { - return Student; - }, + @ManyToMany(() => { + return Student; }) - members!: Student[]; + members!: Collection; } diff --git a/backend/src/entities/users/student.entity.ts b/backend/src/entities/users/student.entity.ts index 7ad8c9b3..c5632e84 100644 --- a/backend/src/entities/users/student.entity.ts +++ b/backend/src/entities/users/student.entity.ts @@ -19,4 +19,12 @@ export class Student extends User { return Group; }) groups!: Collection; + + constructor( + public username: string, + public firstName: string, + public lastName: string + ) { + super(); + } } diff --git a/backend/src/services/groups.ts b/backend/src/services/groups.ts index 65e111fb..2f2d0a14 100644 --- a/backend/src/services/groups.ts +++ b/backend/src/services/groups.ts @@ -83,14 +83,11 @@ export async function createGroup( const groupRepository = getGroupRepository(); try { - console.log('EEEEE'); const newGroup = groupRepository.create({ assignment: assignment, members: members, }); - console.log('OOOOOO'); await groupRepository.save(newGroup); - console.log('AAAAAA'); return newGroup; } catch(e) { From d9eb8def72ee94fcc82e130085c086b5c8de6a7c Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Thu, 13 Mar 2025 16:45:59 +0100 Subject: [PATCH 080/106] feat: submission route post delete --- backend/src/controllers/submissions.ts | 41 ++++++++++++++++---- backend/src/interfaces/submission.ts | 27 ++++++++++--- backend/src/interfaces/teacher-invitation.ts | 2 +- backend/src/routes/groups.ts | 2 +- backend/src/routes/submissions.ts | 6 ++- backend/src/services/submissions.ts | 39 ++++++++++++++++++- 6 files changed, 100 insertions(+), 17 deletions(-) diff --git a/backend/src/controllers/submissions.ts b/backend/src/controllers/submissions.ts index 572dae43..1a5da7ea 100644 --- a/backend/src/controllers/submissions.ts +++ b/backend/src/controllers/submissions.ts @@ -1,28 +1,29 @@ import { Request, Response } from "express"; -import { getSubmission } from "../services/submissions.js"; +import {createSubmission, deleteSubmission, getSubmission} from "../services/submissions.js"; import { Language, languageMap } from "../entities/content/language.js"; +import {SubmissionDTO} from "../interfaces/submission"; interface SubmissionParams { - lohruid: string, - submissionNumber: number; + hruid: string, + id: number; } export async function getSubmissionHandler( req: Request, res: Response, ): Promise { - const lohruid = req.params.lohruid; - const submissionNumber = +req.params.submissionNumber; + const lohruid = req.params.hruid; + const submissionNumber = +req.params.id; if (isNaN(submissionNumber)) { - res.status(404).json({ error: 'Submission number is not a number' }); + res.status(400).json({ error: 'Submission number is not a number' }); return; } let lang = languageMap[req.query.language as string] || Language.Dutch; let version = req.query.version as string || '1'; - const submission = getSubmission(lohruid, lang, version, submissionNumber); + const submission = await getSubmission(lohruid, lang, version, submissionNumber); if (!submission) { res.status(404).json({ error: 'Submission not found' }); @@ -31,3 +32,29 @@ export async function getSubmissionHandler( res.json(submission); } + +export async function createSubmissionHandler(req: Request, res: Response){ + const submissionDTO = req.body as SubmissionDTO; + + const submission = await createSubmission(submissionDTO); + + if (!submission) + res.status(404).json({ error: 'Submission not added' }); + else + res.json(submission) +} + +export async function deleteSubmissionHandler(req: Request, res: Response){ + const hruid = req.params.hruid; + const submissionNumber = +req.params.id; + + let lang = languageMap[req.query.language as string] || Language.Dutch; + let version = req.query.version as string || '1'; + + const submission = await deleteSubmission(hruid, lang, version, submissionNumber); + + if (!submission) + res.status(404).json({ error: 'Submission not found' }); + else + res.json(submission) +} diff --git a/backend/src/interfaces/submission.ts b/backend/src/interfaces/submission.ts index e0fddf07..b6227fc4 100644 --- a/backend/src/interfaces/submission.ts +++ b/backend/src/interfaces/submission.ts @@ -1,17 +1,19 @@ import { Submission } from "../entities/assignments/submission.entity.js"; import { Language } from "../entities/content/language.js"; import { GroupDTO, mapToGroupDTO } from "./group.js"; -import { mapToStudentDTO, StudentDTO } from "./student.js"; +import {mapToStudent, mapToStudentDTO, StudentDTO} from "./student.js"; +import {mapToUser} from "./user"; +import {Student} from "../entities/users/student.entity"; export interface SubmissionDTO { learningObjectHruid: string, learningObjectLanguage: Language, learningObjectVersion: string, - submissionNumber: number, - submitter: StudentDTO | string, - time: Date, - group?: GroupDTO | string, + submissionNumber?: number, + submitter: StudentDTO, + time?: Date, + group?: GroupDTO, content: string, } @@ -28,3 +30,18 @@ export function mapToSubmissionDTO(submission: Submission): SubmissionDTO { content: submission.content, } } + +export function mapToSubmission(submissionDTO: SubmissionDTO): Submission { + const submission = new Submission(); + submission.learningObjectHruid = submissionDTO.learningObjectHruid; + submission.learningObjectLanguage = submissionDTO.learningObjectLanguage; + submission.learningObjectVersion = submissionDTO.learningObjectVersion; + // submission.submissionNumber = submissionDTO.submissionNumber; + submission.submitter = mapToStudent(submissionDTO.submitter) ; + // submission.submissionTime = submissionDTO.time; + // submission.onBehalfOf = submissionDTO.group!; + // TODO fix group + submission.content = submissionDTO.content; + + return submission; +} diff --git a/backend/src/interfaces/teacher-invitation.ts b/backend/src/interfaces/teacher-invitation.ts index badbbbd7..d29e7476 100644 --- a/backend/src/interfaces/teacher-invitation.ts +++ b/backend/src/interfaces/teacher-invitation.ts @@ -24,6 +24,6 @@ export function mapToTeacherInvitationDTOIds( return { sender: invitation.sender.username, receiver: invitation.receiver.username, - class: invitation.class.classId, + class: invitation.class.classId!, }; } diff --git a/backend/src/routes/groups.ts b/backend/src/routes/groups.ts index d17a330e..c2a462ca 100644 --- a/backend/src/routes/groups.ts +++ b/backend/src/routes/groups.ts @@ -12,7 +12,7 @@ router.get('/:groupid', getGroupHandler); router.get('/:groupid', getGroupSubmissionsHandler); // The list of questions a group has made -router.get('/:id/question', (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 1be419d4..e431260f 100644 --- a/backend/src/routes/submissions.ts +++ b/backend/src/routes/submissions.ts @@ -1,5 +1,5 @@ import express from 'express'; -import { getSubmissionHandler } from '../controllers/submissions.js'; +import {createSubmissionHandler, deleteSubmissionHandler, getSubmissionHandler} from '../controllers/submissions.js'; const router = express.Router({ mergeParams: true }); @@ -11,7 +11,11 @@ router.get('/', (req, res) => { }); }); +router.post('/:id', createSubmissionHandler); + // Information about an submission with id 'id' router.get('/:id', getSubmissionHandler); +router.delete('/:id', deleteSubmissionHandler); + export default router; diff --git a/backend/src/services/submissions.ts b/backend/src/services/submissions.ts index c805c717..bf30386b 100644 --- a/backend/src/services/submissions.ts +++ b/backend/src/services/submissions.ts @@ -1,7 +1,7 @@ -import { getSubmissionRepository } from "../data/repositories.js"; +import {getGroupRepository, getSubmissionRepository} from "../data/repositories.js"; import { Language } from "../entities/content/language.js"; import { LearningObjectIdentifier } from "../entities/content/learning-object-identifier.js"; -import { mapToSubmissionDTO, SubmissionDTO } from "../interfaces/submission.js"; +import {mapToSubmission, mapToSubmissionDTO, SubmissionDTO} from "../interfaces/submission.js"; export async function getSubmission( learningObjectHruid: string, @@ -20,3 +20,38 @@ export async function getSubmission( return mapToSubmissionDTO(submission); } + +export async function createSubmission(submissionDTO: SubmissionDTO) { + const submissionRepository = getSubmissionRepository(); + const submission = mapToSubmission(submissionDTO); + + try { + const newSubmission = await submissionRepository.create(submission); + await submissionRepository.save(newSubmission); + } catch (e) { + return null + } + + return submission; +} + +export async function deleteSubmission( + learningObjectHruid: string, + language: Language, + version: string, + submissionNumber: number +) { + const submissionRepository = getSubmissionRepository(); + + const submission = getSubmission(learningObjectHruid, language, version, submissionNumber); + + if (!submission) + return null + + const loId = new LearningObjectIdentifier(learningObjectHruid, language, version); + await submissionRepository.deleteSubmissionByLearningObjectAndSubmissionNumber(loId, submissionNumber); + + return submission; +} + + From b5390258e356323ad90404c4ed249693622909aa Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Thu, 13 Mar 2025 18:44:41 +0100 Subject: [PATCH 081/106] fix: import errors van gabe gefixt, teacher en student abstractie weggedaan --- backend/src/app.ts | 2 +- backend/src/controllers/questions.ts | 4 +- backend/src/controllers/students.ts | 89 +- backend/src/controllers/submissions.ts | 4 +- backend/src/controllers/teachers.ts | 109 +- backend/src/controllers/themes.ts | 2 +- .../content/learning-object-repository.ts | 9 + .../src/data/questions/question-repository.ts | 1 + backend/src/data/repositories.ts | 1 - .../entities/assignments/assignment.entity.ts | 2 +- .../src/entities/assignments/group.entity.ts | 1 + backend/src/entities/classes/class.entity.ts | 1 + .../src/entities/content/attachment.entity.ts | 1 + backend/src/entities/content/language.ts | 2 +- backend/src/interfaces/answer.ts | 2 +- backend/src/interfaces/question.ts | 6 +- backend/src/interfaces/student.ts | 9 +- backend/src/interfaces/submission.ts | 2 +- backend/src/interfaces/teacher.ts | 33 + backend/src/routes/learning-objects.ts | 2 + backend/src/services/learning-objects.ts | 7 +- .../dwengo-api-learning-object-provider.ts | 2 +- .../dwengo-api-learning-path-provider.ts | 2 +- backend/src/services/questions.ts | 3 +- backend/src/services/students.ts | 54 +- backend/src/services/submissions.ts | 4 +- backend/src/services/teachers.ts | 200 +- .../data/assignments/submissions.test.ts | 8 +- .../tests/data/content/attachments.test.ts | 4 +- .../data/content/learning-objects.test.ts | 4 +- backend/tests/data/questions/answers.test.ts | 6 +- .../tests/data/questions/questions.test.ts | 6 +- .../assignments/submission.testdata.ts | 10 +- .../content/learning-paths.testdata.ts | 4 +- .../questions/questions.testdata.ts | 8 +- package-lock.json | 9330 +++++++++++++++++ 36 files changed, 9754 insertions(+), 180 deletions(-) create mode 100644 backend/src/interfaces/teacher.ts create mode 100644 package-lock.json diff --git a/backend/src/app.ts b/backend/src/app.ts index 93de72bd..abb2bbbc 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -5,7 +5,7 @@ import themeRoutes from './routes/themes.js'; import learningPathRoutes from './routes/learning-paths.js'; import learningObjectRoutes from './routes/learning-objects.js'; -import studentRouter from './routes/student.js'; +import studentRouter from './routes/students.js'; import groupRouter from './routes/groups.js'; import assignmentRouter from './routes/assignments.js'; import submissionRouter from './routes/submissions.js'; diff --git a/backend/src/controllers/questions.ts b/backend/src/controllers/questions.ts index aa39853a..2b57f4c9 100644 --- a/backend/src/controllers/questions.ts +++ b/backend/src/controllers/questions.ts @@ -12,7 +12,7 @@ import {LearningObjectIdentifier} from "../entities/content/learning-object-iden import {Language} from "../entities/content/language.js"; function getObjectId(req: Request, res: Response): LearningObjectIdentifier | null { - const { hruid, version} = req.params + const { hruid, version } = req.params const lang = req.query.lang if (!hruid || !version ) { @@ -23,7 +23,7 @@ function getObjectId(req: Request, res: Response): LearningObjectIdentifier | nu return { hruid, language: lang as Language || FALLBACK_LANG, - version + version: +version } } diff --git a/backend/src/controllers/students.ts b/backend/src/controllers/students.ts index 9a052a86..aae37b50 100644 --- a/backend/src/controllers/students.ts +++ b/backend/src/controllers/students.ts @@ -1,49 +1,106 @@ import { Request, Response } from 'express'; import { + createStudent, + deleteStudent, + getStudent, getStudentAssignments, getStudentClasses, getStudentGroups, getStudentSubmissions, - StudentService, } from '../services/students.js'; import { ClassDTO } from '../interfaces/class.js'; import { getAllAssignments } from '../services/assignments.js'; import { - createUserHandler, - deleteUserHandler, - getAllUsersHandler, 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'; // TODO: accept arguments (full, ...) // TODO: endpoints export async function getAllStudentsHandler( req: Request, - res: Response + res: Response, ): Promise { - await getAllUsersHandler(req, res, new StudentService()); + const full = req.query.full === 'true'; + + const studentRepository = getStudentRepository(); + + const students: StudentDTO[] | string[] = full + ? await getAllStudents() + : await getAllStudents(); + + if (!students) { + res.status(404).json({ error: `Student not found.` }); + return; + } + + res.status(201).json(students); } + export async function getStudentHandler( req: Request, - res: Response + res: Response, ): Promise { - await getUserHandler(req, res, new StudentService()); + const username = req.params.username; + + if (!username) { + res.status(400).json({ error: 'Missing required field: username' }); + return; + } + + const user = await getStudent(username); + + if (!user) { + res.status(404).json({ + error: `User with username '${username}' not found.`, + }); + return; + } + + res.status(201).json(user); } export async function createStudentHandler( req: Request, - res: Response -): Promise { - await createUserHandler(req, res, new StudentService(), Student); + 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', + }); + return; + } + + const newUser = await createStudent(userData); + res.status(201).json(newUser); } export async function deleteStudentHandler( req: Request, - res: Response -): Promise { - await deleteUserHandler(req, res, new StudentService()); + res: Response, +) { + const username = req.params.username; + + if (!username) { + res.status(400).json({ error: 'Missing required field: username' }); + return; + } + + const deletedUser = await deleteStudent(username); + if (!deletedUser) { + res.status(404).json({ + error: `User with username '${username}' not found.`, + }); + return; + } + + res.status(200).json(deletedUser); } export async function getStudentClassesHandler( @@ -115,3 +172,7 @@ export async function getStudentSubmissionsHandler( submissions: submissions, }); } +function getAllStudents(): StudentDTO[] | string[] | PromiseLike { + throw new Error('Function not implemented.'); +} + diff --git a/backend/src/controllers/submissions.ts b/backend/src/controllers/submissions.ts index 1a5da7ea..4080d1e7 100644 --- a/backend/src/controllers/submissions.ts +++ b/backend/src/controllers/submissions.ts @@ -21,7 +21,7 @@ export async function getSubmissionHandler( } let lang = languageMap[req.query.language as string] || Language.Dutch; - let version = req.query.version as string || '1'; + let version = (req.query.version || 1) as number; const submission = await getSubmission(lohruid, lang, version, submissionNumber); @@ -49,7 +49,7 @@ export async function deleteSubmissionHandler(req: Request, res: Response){ const submissionNumber = +req.params.id; let lang = languageMap[req.query.language as string] || Language.Dutch; - let version = req.query.version as string || '1'; + let version = (req.query.version || 1) as number; const submission = await deleteSubmission(hruid, lang, version, submissionNumber); diff --git a/backend/src/controllers/teachers.ts b/backend/src/controllers/teachers.ts index c083710d..02846fd9 100644 --- a/backend/src/controllers/teachers.ts +++ b/backend/src/controllers/teachers.ts @@ -1,49 +1,96 @@ import { Request, Response } from 'express'; -import { TeacherUserService, TeacherService } from '../services/teachers.js'; +import { createTeacher, 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 { - createUserHandler, - deleteUserHandler, - getAllUsersHandler, - getUserHandler, -} from './users.js'; import { Teacher } from '../entities/users/teacher.entity.js'; +import { TeacherDTO } from '../interfaces/teacher.js'; +import { getTeacherRepository } from '../data/repositories.js'; export async function getAllTeachersHandler( req: Request, - res: Response + res: Response, ): Promise { - await getAllUsersHandler(req, res, new TeacherUserService()); + const full = req.query.full === 'true'; + + const teacherRepository = getTeacherRepository(); + + const teachers: TeacherDTO[] | string[] = full + ? await getAllTeachers() + : await getAllTeachers(); + + if (!teachers) { + res.status(404).json({ error: `Teacher not found.` }); + return; + } + + res.status(201).json(teachers); } + export async function getTeacherHandler( req: Request, - res: Response + res: Response, ): Promise { - await getUserHandler(req, res, new TeacherUserService()); + const username = req.params.username; + + if (!username) { + res.status(400).json({ error: 'Missing required field: username' }); + return; + } + + const user = await getTeacher(username); + + if (!user) { + res.status(404).json({ + error: `User with username '${username}' not found.`, + }); + return; + } + + res.status(201).json(user); } export async function createTeacherHandler( req: Request, - res: Response -): Promise { - await createUserHandler( - req, - res, - new TeacherUserService(), - Teacher - ); + 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', + }); + return; + } + + const newUser = await createTeacher(userData); + res.status(201).json(newUser); } export async function deleteTeacherHandler( req: Request, - res: Response -): Promise { - await deleteUserHandler(req, res, new TeacherUserService()); + res: Response, +) { + const username = req.params.username; + + if (!username) { + res.status(400).json({ error: 'Missing required field: username' }); + return; + } + + const deletedUser = await deleteTeacher(username); + if (!deletedUser) { + res.status(404).json({ + error: `User with username '${username}' not found.`, + }); + return; + } + + res.status(200).json(deletedUser); } + export async function getTeacherClassHandler( req: Request, res: Response @@ -57,11 +104,9 @@ export async function getTeacherClassHandler( return; } - const teacherService = new TeacherService(); - const classes: ClassDTO[] | string[] = full - ? await teacherService.getClassesByTeacher(username) - : await teacherService.getClassIdsByTeacher(username); + ? await getClassesByTeacher(username) + : await getClassIdsByTeacher(username); res.status(201).json(classes); } catch (error) { @@ -83,11 +128,9 @@ export async function getTeacherStudentHandler( return; } - const teacherService = new TeacherService(); - const students: StudentDTO[] | string[] = full - ? await teacherService.getStudentsByTeacher(username) - : await teacherService.getStudentIdsByTeacher(username); + ? await getStudentsByTeacher(username) + : await getStudentIdsByTeacher(username); res.status(201).json(students); } catch (error) { @@ -109,11 +152,9 @@ export async function getTeacherQuestionHandler( return; } - const teacherService = new TeacherService(); - const questions: QuestionDTO[] | QuestionId[] = full - ? await teacherService.getQuestionsByTeacher(username) - : await teacherService.getQuestionIdsByTeacher(username); + ? await getQuestionsByTeacher(username) + : await getQuestionIdsByTeacher(username); res.status(201).json(questions); } catch (error) { diff --git a/backend/src/controllers/themes.ts b/backend/src/controllers/themes.ts index fe1eb818..61a1a834 100644 --- a/backend/src/controllers/themes.ts +++ b/backend/src/controllers/themes.ts @@ -1,6 +1,6 @@ import { Request, Response } from 'express'; import { themes } from '../data/themes.js'; -import { loadTranslations } from '../util/translationHelper.js'; +import { loadTranslations } from '../util/translation-helper.js'; interface Translations { curricula_page: { diff --git a/backend/src/data/content/learning-object-repository.ts b/backend/src/data/content/learning-object-repository.ts index 2b544533..49b4c536 100644 --- a/backend/src/data/content/learning-object-repository.ts +++ b/backend/src/data/content/learning-object-repository.ts @@ -1,6 +1,8 @@ import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; import { LearningObject } from '../../entities/content/learning-object.entity.js'; import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js'; +import { Language } from '../../entities/content/language.js'; +import { Teacher } from '../../entities/users/teacher.entity.js'; export class LearningObjectRepository extends DwengoEntityRepository { public findByIdentifier(identifier: LearningObjectIdentifier): Promise { @@ -30,4 +32,11 @@ export class LearningObjectRepository extends DwengoEntityRepository { + return this.find( + { admins: teacher }, + { populate: ['admins'] } // Make sure to load admin relations + ); + } } diff --git a/backend/src/data/questions/question-repository.ts b/backend/src/data/questions/question-repository.ts index d65d4575..dc8502d1 100644 --- a/backend/src/data/questions/question-repository.ts +++ b/backend/src/data/questions/question-repository.ts @@ -2,6 +2,7 @@ import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; import { Question } from '../../entities/questions/question.entity.js'; import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js'; 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 { diff --git a/backend/src/data/repositories.ts b/backend/src/data/repositories.ts index 31269611..0c091aa9 100644 --- a/backend/src/data/repositories.ts +++ b/backend/src/data/repositories.ts @@ -56,7 +56,6 @@ function repositoryGetter>( } /* Users */ -export const getUserRepository = repositoryGetter(User); export const getStudentRepository = repositoryGetter(Student); export const getTeacherRepository = repositoryGetter(Teacher); diff --git a/backend/src/entities/assignments/assignment.entity.ts b/backend/src/entities/assignments/assignment.entity.ts index 015dcdc5..4992cca4 100644 --- a/backend/src/entities/assignments/assignment.entity.ts +++ b/backend/src/entities/assignments/assignment.entity.ts @@ -51,5 +51,5 @@ export class Assignment { }, mappedBy: 'assignment', }) - groups!: Collection; + groups!: Group[]; } diff --git a/backend/src/entities/assignments/group.entity.ts b/backend/src/entities/assignments/group.entity.ts index 45065db6..a961ed53 100644 --- a/backend/src/entities/assignments/group.entity.ts +++ b/backend/src/entities/assignments/group.entity.ts @@ -1,6 +1,7 @@ import { Collection, 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'; @Entity({ repository: () => { diff --git a/backend/src/entities/classes/class.entity.ts b/backend/src/entities/classes/class.entity.ts index 00079c85..a2c016ef 100644 --- a/backend/src/entities/classes/class.entity.ts +++ b/backend/src/entities/classes/class.entity.ts @@ -8,6 +8,7 @@ import { import { v4 } from 'uuid'; import { Teacher } from '../users/teacher.entity.js'; import { Student } from '../users/student.entity.js'; +import { ClassRepository } from '../../data/classes/class-repository.js'; @Entity({ repository: () => { diff --git a/backend/src/entities/content/attachment.entity.ts b/backend/src/entities/content/attachment.entity.ts index 08497b58..3903f055 100644 --- a/backend/src/entities/content/attachment.entity.ts +++ b/backend/src/entities/content/attachment.entity.ts @@ -1,5 +1,6 @@ import { Entity, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'; import { LearningObject } from './learning-object.entity.js'; +import { AttachmentRepository } from '../../data/content/attachment-repository.js'; @Entity({ repository: () => { diff --git a/backend/src/entities/content/language.ts b/backend/src/entities/content/language.ts index 30331683..40a00919 100644 --- a/backend/src/entities/content/language.ts +++ b/backend/src/entities/content/language.ts @@ -189,5 +189,5 @@ export const languageMap: Record = { nl: Language.Dutch, fr: Language.French, en: Language.English, - de: Language.Germany, + de: Language.German, }; \ No newline at end of file diff --git a/backend/src/interfaces/answer.ts b/backend/src/interfaces/answer.ts index 1b363c0d..0b86a4d1 100644 --- a/backend/src/interfaces/answer.ts +++ b/backend/src/interfaces/answer.ts @@ -17,7 +17,7 @@ export function mapToAnswerDTO(answer: Answer): AnswerDTO { return { author: mapToUserDTO(answer.author), toQuestion: mapToQuestionDTO(answer.toQuestion), - sequenceNumber: answer.sequenceNumber, + sequenceNumber: answer.sequenceNumber!, timestamp: answer.timestamp.toISOString(), content: answer.content, }; diff --git a/backend/src/interfaces/question.ts b/backend/src/interfaces/question.ts index 23c3edea..69039ee9 100644 --- a/backend/src/interfaces/question.ts +++ b/backend/src/interfaces/question.ts @@ -1,11 +1,13 @@ 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; sequenceNumber?: number; - author: UserDTO; + author: StudentDTO; timestamp?: string; content: string; } @@ -23,7 +25,7 @@ export function mapToQuestionDTO(question: Question): QuestionDTO { return { learningObjectIdentifier, sequenceNumber: question.sequenceNumber!, - author: question.author, + author: mapToStudentDTO(question.author), timestamp: question.timestamp.toISOString(), content: question.content, }; diff --git a/backend/src/interfaces/student.ts b/backend/src/interfaces/student.ts index 529c6ead..9790eb35 100644 --- a/backend/src/interfaces/student.ts +++ b/backend/src/interfaces/student.ts @@ -23,10 +23,11 @@ export function mapToStudentDTO(student: Student): StudentDTO { } export function mapToStudent(studentData: StudentDTO): Student { - const student = new Student(); - student.username = studentData.username; - student.firstName = studentData.firstName; - student.lastName = studentData.lastName; + const student = new Student( + studentData.username, + studentData.firstName, + studentData.lastName, + ); return student; } diff --git a/backend/src/interfaces/submission.ts b/backend/src/interfaces/submission.ts index b6227fc4..2400075f 100644 --- a/backend/src/interfaces/submission.ts +++ b/backend/src/interfaces/submission.ts @@ -8,7 +8,7 @@ import {Student} from "../entities/users/student.entity"; export interface SubmissionDTO { learningObjectHruid: string, learningObjectLanguage: Language, - learningObjectVersion: string, + learningObjectVersion: number, submissionNumber?: number, submitter: StudentDTO, diff --git a/backend/src/interfaces/teacher.ts b/backend/src/interfaces/teacher.ts new file mode 100644 index 00000000..93dbf5fd --- /dev/null +++ b/backend/src/interfaces/teacher.ts @@ -0,0 +1,33 @@ +import { Teacher } from '../entities/users/teacher.entity.js'; + +export interface TeacherDTO { + id: string; + username: string; + firstName: string; + lastName: string; + endpoints?: { + classes: string; + questions: string; + invitations: string; + groups: string; + }; +} + +export function mapToTeacherDTO(teacher: Teacher): TeacherDTO { + return { + id: teacher.username, + username: teacher.username, + firstName: teacher.firstName, + lastName: teacher.lastName, + }; +} + +export function mapToTeacher(TeacherData: TeacherDTO): Teacher { + const teacher = new Teacher( + TeacherData.username, + TeacherData.firstName, + TeacherData.lastName, + ); + + return teacher; +} diff --git a/backend/src/routes/learning-objects.ts b/backend/src/routes/learning-objects.ts index 3f598509..45e0df4c 100644 --- a/backend/src/routes/learning-objects.ts +++ b/backend/src/routes/learning-objects.ts @@ -1,7 +1,9 @@ import express from 'express'; import { getAllLearningObjects, + getAttachment, getLearningObject, + getLearningObjectHTML, } from '../controllers/learning-objects.js'; import submissionRoutes from './submissions.js'; diff --git a/backend/src/services/learning-objects.ts b/backend/src/services/learning-objects.ts index 65ad11a6..4f7d66a8 100644 --- a/backend/src/services/learning-objects.ts +++ b/backend/src/services/learning-objects.ts @@ -5,8 +5,7 @@ import { LearningObjectMetadata, LearningObjectNode, LearningPathResponse, -} from '../interfaces/learning-path.js'; -import { fetchLearningPaths } from './learning-paths.js'; +} from '../interfaces/learning-content.js'; function filterData( data: LearningObjectMetadata, @@ -132,3 +131,7 @@ export async function getLearningObjectIdsFromPath( ): Promise { return (await fetchLearningObjects(hruid, false, language)) as string[]; } +function fetchLearningPaths(arg0: string[], language: string, arg2: string): LearningPathResponse | PromiseLike { + throw new Error('Function not implemented.'); +} + diff --git a/backend/src/services/learning-objects/dwengo-api-learning-object-provider.ts b/backend/src/services/learning-objects/dwengo-api-learning-object-provider.ts index 37e68c07..dfee329d 100644 --- a/backend/src/services/learning-objects/dwengo-api-learning-object-provider.ts +++ b/backend/src/services/learning-objects/dwengo-api-learning-object-provider.ts @@ -1,5 +1,5 @@ import { DWENGO_API_BASE } from '../../config.js'; -import { fetchWithLogging } from '../../util/apiHelper.js'; +import { fetchWithLogging } from '../../util/api-helper.js'; import { FilteredLearningObject, LearningObjectIdentifier, diff --git a/backend/src/services/learning-paths/dwengo-api-learning-path-provider.ts b/backend/src/services/learning-paths/dwengo-api-learning-path-provider.ts index 2b1d17a6..a6093bb4 100644 --- a/backend/src/services/learning-paths/dwengo-api-learning-path-provider.ts +++ b/backend/src/services/learning-paths/dwengo-api-learning-path-provider.ts @@ -1,4 +1,4 @@ -import { fetchWithLogging } from '../../util/apiHelper.js'; +import { fetchWithLogging } from '../../util/api-helper.js'; import { DWENGO_API_BASE } from '../../config.js'; import { LearningPath, LearningPathResponse } from '../../interfaces/learning-content.js'; import { LearningPathProvider } from './learning-path-provider.js'; diff --git a/backend/src/services/questions.ts b/backend/src/services/questions.ts index a2fa01c6..6be684f9 100644 --- a/backend/src/services/questions.ts +++ b/backend/src/services/questions.ts @@ -7,6 +7,7 @@ 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 @@ -72,7 +73,7 @@ export async function getAnswersByQuestion(questionId: QuestionId, full: boolean export async function createQuestion(questionDTO: QuestionDTO) { const questionRepository = getQuestionRepository(); - const author = mapToUser(questionDTO.author, new Student()) + const author = mapToStudent(questionDTO.author); try { await questionRepository.createQuestion({ diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts index 602ee4e1..4fc5c895 100644 --- a/backend/src/services/students.ts +++ b/backend/src/services/students.ts @@ -9,13 +9,61 @@ 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'; -export class StudentService extends UserService { - constructor() { - super(getStudentRepository()); + +export async function getAllStudents(): Promise { + const studentRepository = getStudentRepository(); + const users = await studentRepository.findAll(); + return users.map(mapToStudentDTO); +} + +export async function getAllStudentIds(): Promise { + const users = await getAllStudents(); + return users.map((user) => { + return user.username; + }); +} + +export async function getStudent(username: string): Promise { + const studentRepository = getStudentRepository(); + const user = await studentRepository.findByUsername(username); + return user ? mapToStudentDTO(user) : null; +} + +export async function createStudent(userData: StudentDTO): Promise { + const studentRepository = getStudentRepository(); + + try { + const newStudent = studentRepository.create(mapToStudent(userData)); + await studentRepository.save(newStudent); + + return mapToStudentDTO(newStudent); + } catch(e) { + console.log(e); + return null; + } +} + +export async function deleteStudent(username: string): Promise { + const studentRepository = getStudentRepository(); + + const user = await studentRepository.findByUsername(username); + + if (!user) { + return null; + } + + try { + await studentRepository.deleteByUsername(username); + + return mapToStudentDTO(user); + } catch(e) { + console.log(e); + return null; } } diff --git a/backend/src/services/submissions.ts b/backend/src/services/submissions.ts index bf30386b..ce6f7bff 100644 --- a/backend/src/services/submissions.ts +++ b/backend/src/services/submissions.ts @@ -6,7 +6,7 @@ import {mapToSubmission, mapToSubmissionDTO, SubmissionDTO} from "../interfaces/ export async function getSubmission( learningObjectHruid: string, language: Language, - version: string, + version: number, submissionNumber: number, ): Promise { const loId = new LearningObjectIdentifier(learningObjectHruid, language, version); @@ -38,7 +38,7 @@ export async function createSubmission(submissionDTO: SubmissionDTO) { export async function deleteSubmission( learningObjectHruid: string, language: Language, - version: string, + version: number, submissionNumber: number ) { const submissionRepository = getSubmissionRepository(); diff --git a/backend/src/services/teachers.ts b/backend/src/services/teachers.ts index b87c3b92..12325857 100644 --- a/backend/src/services/teachers.ts +++ b/backend/src/services/teachers.ts @@ -17,93 +17,133 @@ import { } from '../interfaces/question.js'; import { UserService } from './users.js'; import { mapToUser } from '../interfaces/user.js'; +import { mapToTeacher, mapToTeacherDTO, TeacherDTO } from '../interfaces/teacher.js'; -export class TeacherUserService extends UserService { - constructor() { - super(getTeacherRepository()); +export async function getAllTeachers(): Promise { + const teacherRepository = getTeacherRepository(); + const users = await teacherRepository.findAll(); + return users.map(mapToTeacherDTO); +} + +export async function getAllTeacherIds(): Promise { + const users = await getAllTeachers(); + return users.map((user) => { + return user.username; + }); +} + +export async function getTeacher(username: string): Promise { + const teacherRepository = getTeacherRepository(); + const user = await teacherRepository.findByUsername(username); + return user ? mapToTeacherDTO(user) : null; +} + +export async function createTeacher(userData: TeacherDTO): Promise { + const teacherRepository = getTeacherRepository(); + + try { + const newTeacher = teacherRepository.create(mapToTeacher(userData)); + await teacherRepository.save(newTeacher); + + return mapToTeacherDTO(newTeacher); + } catch(e) { + console.log(e); + return null; } } -export class TeacherService { - protected teacherService = new TeacherUserService(); - protected teacherRepository = getTeacherRepository(); - protected classRepository = getClassRepository(); - protected learningObjectRepository = getLearningObjectRepository(); - protected questionRepository = getQuestionRepository(); +export async function deleteTeacher(username: string): Promise { + const teacherRepository = getTeacherRepository(); - async fetchClassesByTeacher(username: string): Promise { - const teacher = await this.teacherRepository.findByUsername(username); - if (!teacher) { - return []; - } + const user = await teacherRepository.findByUsername(username); - const classes = await this.classRepository.findByTeacher(teacher); - return classes.map(mapToClassDTO); + if (!user) { + return null; } - async getClassesByTeacher(username: string): Promise { - return await this.fetchClassesByTeacher(username); - } - - async getClassIdsByTeacher(username: string): Promise { - const classes = await this.fetchClassesByTeacher(username); - return classes.map((cls) => { - return cls.id; - }); - } - - async fetchStudentsByTeacher(username: string) { - const classes = await this.getClassIdsByTeacher(username); - - return ( - await Promise.all( - classes.map(async (id) => { - return getClassStudents(id); - }) - ) - ).flat(); - } - - async getStudentsByTeacher(username: string): Promise { - return await this.fetchStudentsByTeacher(username); - } - - async getStudentIdsByTeacher(username: string): Promise { - const students = await this.fetchStudentsByTeacher(username); - return students.map((student) => { - return student.username; - }); - } - - async fetchTeacherQuestions(username: string): Promise { - const teacherDTO = - await this.teacherService.getUserByUsername(username); - if (!teacherDTO) { - throw new Error(`Teacher with username '${username}' not found.`); - } - - const teacher = mapToUser(teacherDTO, new Teacher()); - - // Find all learning objects that this teacher manages - const learningObjects = - await this.learningObjectRepository.findAllByTeacher(teacher); - - // Fetch all questions related to these learning objects - const questions = - await this.questionRepository.findAllByLearningObjects( - learningObjects - ); - - return questions.map(mapToQuestionDTO); - } - - async getQuestionsByTeacher(username: string): Promise { - return await this.fetchTeacherQuestions(username); - } - - async getQuestionIdsByTeacher(username: string): Promise { - const questions = await this.fetchTeacherQuestions(username); - - return questions.map(mapToQuestionId); + try { + await teacherRepository.deleteByUsername(username); + + return mapToTeacherDTO(user); + } catch(e) { + console.log(e); + return null; } } + +export async function fetchClassesByTeacher(username: string): Promise { + const teacherRepository = getTeacherRepository(); + const teacher = await teacherRepository.findByUsername(username); + if (!teacher) { + return []; + } + + const classRepository = getClassRepository(); + const classes = await classRepository.findByTeacher(teacher); + return classes.map(mapToClassDTO); +} + +export async function getClassesByTeacher(username: string): Promise { + return await fetchClassesByTeacher(username); +} + +export async function getClassIdsByTeacher(username: string): Promise { + const classes = await fetchClassesByTeacher(username); + return classes.map((cls) => { + return cls.id; + }); +} + +export async function fetchStudentsByTeacher(username: string) { + const classes = await getClassIdsByTeacher(username); + + return ( + await Promise.all( + classes.map(async (id) => { + return 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); + return students.map((student) => { + return student.username; + }); +} + +export async function fetchTeacherQuestions(username: string): Promise { + const teacherRepository = getTeacherRepository(); + const teacher = await teacherRepository.findByUsername(username); + if (!teacher) { + throw new Error(`Teacher with username '${username}' not found.`); + } + + // Find all learning objects that this teacher manages + const learningObjectRepository = getLearningObjectRepository(); + const learningObjects = await learningObjectRepository.findAllByTeacher(teacher); + + // Fetch all questions related to these learning objects + const questionRepository = getQuestionRepository(); + const questions = + await questionRepository.findAllByLearningObjects( + learningObjects + ); + + return questions.map(mapToQuestionDTO); +} + +export async function getQuestionsByTeacher(username: string): Promise { + return await fetchTeacherQuestions(username); +} + +export async function getQuestionIdsByTeacher(username: string): Promise { + const questions = await fetchTeacherQuestions(username); + + return questions.map(mapToQuestionId); +} diff --git a/backend/tests/data/assignments/submissions.test.ts b/backend/tests/data/assignments/submissions.test.ts index 8712a710..cd212b77 100644 --- a/backend/tests/data/assignments/submissions.test.ts +++ b/backend/tests/data/assignments/submissions.test.ts @@ -32,7 +32,7 @@ describe('SubmissionRepository', () => { }); it('should find the requested submission', async () => { - const id = new LearningObjectIdentifier('id03', Language.English, '1'); + const id = new LearningObjectIdentifier('id03', Language.English, 1); const submission = await submissionRepository.findSubmissionByLearningObjectAndSubmissionNumber(id, 1); expect(submission).toBeTruthy(); @@ -40,7 +40,7 @@ describe('SubmissionRepository', () => { }); it('should find the most recent submission for a student', async () => { - const id = new LearningObjectIdentifier('id02', Language.English, '1'); + const id = new LearningObjectIdentifier('id02', Language.English, 1); const student = await studentRepository.findByUsername('Noordkaap'); const submission = await submissionRepository.findMostRecentSubmissionForStudent(id, student!); @@ -49,7 +49,7 @@ describe('SubmissionRepository', () => { }); it('should find the most recent submission for a group', async () => { - const id = new LearningObjectIdentifier('id03', Language.English, '1'); + const id = new LearningObjectIdentifier('id03', Language.English, 1); const class_ = await classRepository.findById('id01'); const assignment = await assignmentRepository.findByClassAndId(class_!, 1); const group = await groupRepository.findByAssignmentAndGroupNumber(assignment!, 1); @@ -60,7 +60,7 @@ describe('SubmissionRepository', () => { }); it('should not find a deleted submission', async () => { - const id = new LearningObjectIdentifier('id01', Language.English, '1'); + const id = new LearningObjectIdentifier('id01', Language.English, 1); await submissionRepository.deleteSubmissionByLearningObjectAndSubmissionNumber(id, 1); const submission = await submissionRepository.findSubmissionByLearningObjectAndSubmissionNumber(id, 1); diff --git a/backend/tests/data/content/attachments.test.ts b/backend/tests/data/content/attachments.test.ts index a8bea88a..94e132a9 100644 --- a/backend/tests/data/content/attachments.test.ts +++ b/backend/tests/data/content/attachments.test.ts @@ -17,11 +17,11 @@ describe('AttachmentRepository', () => { }); it('should return the requested attachment', async () => { - const id = new LearningObjectIdentifier('id02', Language.English, '1'); + const id = new LearningObjectIdentifier('id02', Language.English, 1); const learningObject = await learningObjectRepository.findByIdentifier(id); const attachment = await attachmentRepository.findByMostRecentVersionOfLearningObjectAndName( - learningObject!, + learningObject!.hruid, Language.English, 'attachment01' ); diff --git a/backend/tests/data/content/learning-objects.test.ts b/backend/tests/data/content/learning-objects.test.ts index 51f9c98e..712f75c9 100644 --- a/backend/tests/data/content/learning-objects.test.ts +++ b/backend/tests/data/content/learning-objects.test.ts @@ -13,8 +13,8 @@ describe('LearningObjectRepository', () => { learningObjectRepository = getLearningObjectRepository(); }); - const id01 = new LearningObjectIdentifier('id01', Language.English, '1'); - const id02 = new LearningObjectIdentifier('test_id', Language.English, '1'); + const id01 = new LearningObjectIdentifier('id01', Language.English, 1); + const id02 = new LearningObjectIdentifier('test_id', Language.English, 1); it('should return the learning object that matches identifier 1', async () => { const learningObject = await learningObjectRepository.findByIdentifier(id01); diff --git a/backend/tests/data/questions/answers.test.ts b/backend/tests/data/questions/answers.test.ts index f15fed6a..bcc62cf6 100644 --- a/backend/tests/data/questions/answers.test.ts +++ b/backend/tests/data/questions/answers.test.ts @@ -20,7 +20,7 @@ describe('AnswerRepository', () => { }); it('should find all answers to a question', async () => { - const id = new LearningObjectIdentifier('id05', Language.English, '1'); + const id = new LearningObjectIdentifier('id05', Language.English, 1); const questions = await questionRepository.findAllQuestionsAboutLearningObject(id); const question = questions.filter((it) => it.sequenceNumber == 2)[0]; @@ -35,7 +35,7 @@ describe('AnswerRepository', () => { it('should create an answer to a question', async () => { const teacher = await teacherRepository.findByUsername('FooFighters'); - const id = new LearningObjectIdentifier('id05', Language.English, '1'); + const id = new LearningObjectIdentifier('id05', Language.English, 1); const questions = await questionRepository.findAllQuestionsAboutLearningObject(id); const question = questions[0]; @@ -54,7 +54,7 @@ describe('AnswerRepository', () => { }); it('should not find a removed answer', async () => { - const id = new LearningObjectIdentifier('id04', Language.English, '1'); + const id = new LearningObjectIdentifier('id04', Language.English, 1); const questions = await questionRepository.findAllQuestionsAboutLearningObject(id); await answerRepository.removeAnswerByQuestionAndSequenceNumber(questions[0], 1); diff --git a/backend/tests/data/questions/questions.test.ts b/backend/tests/data/questions/questions.test.ts index 1a1cb034..7b408df4 100644 --- a/backend/tests/data/questions/questions.test.ts +++ b/backend/tests/data/questions/questions.test.ts @@ -20,7 +20,7 @@ describe('QuestionRepository', () => { }); it('should return all questions part of the given learning object', async () => { - const id = new LearningObjectIdentifier('id05', Language.English, '1'); + const id = new LearningObjectIdentifier('id05', Language.English, 1); const questions = await questionRepository.findAllQuestionsAboutLearningObject(id); expect(questions).toBeTruthy(); @@ -28,7 +28,7 @@ describe('QuestionRepository', () => { }); it('should create new question', async () => { - const id = new LearningObjectIdentifier('id03', Language.English, '1'); + const id = new LearningObjectIdentifier('id03', Language.English, 1); const student = await studentRepository.findByUsername('Noordkaap'); await questionRepository.createQuestion({ loId: id, @@ -42,7 +42,7 @@ describe('QuestionRepository', () => { }); it('should not find removed question', async () => { - const id = new LearningObjectIdentifier('id04', Language.English, '1'); + const id = new LearningObjectIdentifier('id04', Language.English, 1); await questionRepository.removeQuestionByLearningObjectAndSequenceNumber(id, 1); const question = await questionRepository.findAllQuestionsAboutLearningObject(id); diff --git a/backend/tests/test_assets/assignments/submission.testdata.ts b/backend/tests/test_assets/assignments/submission.testdata.ts index 058af70f..95dd65df 100644 --- a/backend/tests/test_assets/assignments/submission.testdata.ts +++ b/backend/tests/test_assets/assignments/submission.testdata.ts @@ -12,7 +12,7 @@ export function makeTestSubmissions( const submission01 = em.create(Submission, { learningObjectHruid: 'id03', learningObjectLanguage: Language.English, - learningObjectVersion: '1', + learningObjectVersion: 1, submissionNumber: 1, submitter: students[0], submissionTime: new Date(2025, 2, 20), @@ -23,7 +23,7 @@ export function makeTestSubmissions( const submission02 = em.create(Submission, { learningObjectHruid: 'id03', learningObjectLanguage: Language.English, - learningObjectVersion: '1', + learningObjectVersion: 1, submissionNumber: 2, submitter: students[0], submissionTime: new Date(2025, 2, 25), @@ -34,7 +34,7 @@ export function makeTestSubmissions( const submission03 = em.create(Submission, { learningObjectHruid: 'id02', learningObjectLanguage: Language.English, - learningObjectVersion: '1', + learningObjectVersion: 1, submissionNumber: 1, submitter: students[0], submissionTime: new Date(2025, 2, 20), @@ -44,7 +44,7 @@ export function makeTestSubmissions( const submission04 = em.create(Submission, { learningObjectHruid: 'id02', learningObjectLanguage: Language.English, - learningObjectVersion: '1', + learningObjectVersion: 1, submissionNumber: 2, submitter: students[0], submissionTime: new Date(2025, 2, 25), @@ -54,7 +54,7 @@ export function makeTestSubmissions( const submission05 = em.create(Submission, { learningObjectHruid: 'id01', learningObjectLanguage: Language.English, - learningObjectVersion: '1', + learningObjectVersion: 1, submissionNumber: 1, submitter: students[1], submissionTime: new Date(2025, 2, 20), diff --git a/backend/tests/test_assets/content/learning-paths.testdata.ts b/backend/tests/test_assets/content/learning-paths.testdata.ts index d2e65c9e..10de885c 100644 --- a/backend/tests/test_assets/content/learning-paths.testdata.ts +++ b/backend/tests/test_assets/content/learning-paths.testdata.ts @@ -77,7 +77,7 @@ export function makeTestLearningPaths(em: EntityManager>, students: Array): Array { const question01 = em.create(Question, { learningObjectLanguage: Language.English, - learningObjectVersion: '1', + learningObjectVersion: 1, learningObjectHruid: 'id05', sequenceNumber: 1, author: students[0], @@ -16,7 +16,7 @@ export function makeTestQuestions(em: EntityManager> const question02 = em.create(Question, { learningObjectLanguage: Language.English, - learningObjectVersion: '1', + learningObjectVersion: 1, learningObjectHruid: 'id05', sequenceNumber: 2, author: students[2], @@ -26,7 +26,7 @@ export function makeTestQuestions(em: EntityManager> const question03 = em.create(Question, { learningObjectLanguage: Language.English, - learningObjectVersion: '1', + learningObjectVersion: 1, learningObjectHruid: 'id04', sequenceNumber: 1, author: students[0], @@ -36,7 +36,7 @@ export function makeTestQuestions(em: EntityManager> const question04 = em.create(Question, { learningObjectLanguage: Language.English, - learningObjectVersion: '1', + learningObjectVersion: 1, learningObjectHruid: 'id01', sequenceNumber: 1, author: students[1], diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..1b16ec55 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,9330 @@ +{ + "name": "dwengo-1-monorepo", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "dwengo-1-monorepo", + "version": "0.0.1", + "license": "MIT", + "workspaces": [ + "backend", + "frontend" + ], + "devDependencies": { + "@eslint/compat": "^1.2.6", + "@eslint/js": "^9.20.0", + "@types/eslint-config-prettier": "^6.11.3", + "@typescript-eslint/eslint-plugin": "^8.24.1", + "@typescript-eslint/parser": "^8.24.1", + "eslint": "^9.20.1", + "eslint-config-prettier": "^10.0.1", + "jiti": "^2.4.2", + "typescript-eslint": "^8.24.1" + } + }, + "backend": { + "name": "dwengo-1-backend", + "version": "0.0.1", + "dependencies": { + "@mikro-orm/core": "6.4.9", + "@mikro-orm/knex": "6.4.9", + "@mikro-orm/postgresql": "6.4.9", + "@mikro-orm/reflection": "6.4.9", + "@mikro-orm/sqlite": "6.4.9", + "axios": "^1.8.2", + "cors": "^2.8.5", + "cross": "^1.0.0", + "cross-env": "^7.0.3", + "dotenv": "^16.4.7", + "express": "^5.0.1", + "express-jwt": "^8.5.1", + "gift-pegjs": "^1.0.2", + "isomorphic-dompurify": "^2.22.0", + "js-yaml": "^4.1.0", + "jsonpath-plus": "^10.3.0", + "jwks-rsa": "^3.1.0", + "loki-logger-ts": "^1.0.2", + "marked": "^15.0.7", + "response-time": "^2.3.3", + "uuid": "^11.1.0", + "winston": "^3.17.0", + "winston-loki": "^6.1.3" + }, + "devDependencies": { + "@mikro-orm/cli": "6.4.9", + "@types/cors": "^2.8.17", + "@types/express": "^5.0.0", + "@types/js-yaml": "^4.0.9", + "@types/node": "^22.13.4", + "@types/response-time": "^2.3.8", + "globals": "^15.15.0", + "ts-node": "^10.9.2", + "tsx": "^4.19.3", + "typescript": "^5.7.3", + "vitest": "^3.0.6" + } + }, + "backend/node_modules/globals": { + "version": "15.15.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "frontend": { + "name": "dwengo-1-frontend", + "version": "0.0.1", + "dependencies": { + "axios": "^1.8.2", + "oidc-client-ts": "^3.1.0", + "vue": "^3.5.13", + "vue-router": "^4.5.0", + "vuetify": "^3.7.12" + }, + "devDependencies": { + "@playwright/test": "^1.50.1", + "@tsconfig/node22": "^22.0.0", + "@types/jsdom": "^21.1.7", + "@types/node": "^22.13.4", + "@vitejs/plugin-vue": "^5.2.1", + "@vitest/eslint-plugin": "1.1.31", + "@vue/eslint-config-prettier": "^10.2.0", + "@vue/eslint-config-typescript": "^14.4.0", + "@vue/test-utils": "^2.4.6", + "@vue/tsconfig": "^0.7.0", + "eslint": "^9.20.1", + "eslint-plugin-playwright": "^2.2.0", + "eslint-plugin-vue": "^9.32.0", + "jsdom": "^26.0.0", + "npm-run-all2": "^7.0.2", + "typescript": "~5.7.3", + "vite": "^6.1.0", + "vite-plugin-vue-devtools": "^7.7.2", + "vitest": "^3.0.5", + "vue-tsc": "^2.2.2" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@ampproject/remapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@antfu/utils": { + "version": "0.7.10", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "2.8.3", + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.1", + "@csstools/css-color-parser": "^3.0.7", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.8", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.9", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.9", + "@babel/parser": "^7.26.9", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.9", + "@babel/types": "^7.26.9", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.26.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.26.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/helper-replace-supers": "^7.26.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/traverse": "^7.26.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.25.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.25.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.26.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.26.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/traverse": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.25.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.26.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.9", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.9" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.25.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-syntax-decorators": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.25.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.26.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.25.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.26.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-syntax-typescript": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.26.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.26.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.9", + "@babel/parser": "^7.26.9", + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.26.9", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.0.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.0.7", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.0.1", + "@csstools/css-calc": "^2.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.4", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.3", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.0", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/compat": { + "version": "1.2.6", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.10.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/config-array": { + "version": "0.19.2", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/core": { + "version": "0.11.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.20.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.6", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.11.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "license": "MIT", + "optional": true + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.2", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jercle/yargonaut": { + "version": "1.1.5", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "chalk": "^4.1.2", + "figlet": "^1.5.2", + "parent-require": "^1.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@jsep-plugin/assignment": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz", + "integrity": "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@jsep-plugin/regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.4.tgz", + "integrity": "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@mikro-orm/cli": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@mikro-orm/cli/-/cli-6.4.9.tgz", + "integrity": "sha512-LQzVsmar/0DoJkPGyz3OpB8pa9BCQtvYreEC71h0O+RcizppJjgBQNTkj5tJd2Iqvh4hSaMv6qTv0l5UK6F2Vw==", + "dev": true, + "dependencies": { + "@jercle/yargonaut": "1.1.5", + "@mikro-orm/core": "6.4.9", + "@mikro-orm/knex": "6.4.9", + "fs-extra": "11.3.0", + "tsconfig-paths": "4.2.0", + "yargs": "17.7.2" + }, + "bin": { + "mikro-orm": "cli", + "mikro-orm-esm": "esm" + }, + "engines": { + "node": ">= 18.12.0" + } + }, + "node_modules/@mikro-orm/core": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@mikro-orm/core/-/core-6.4.9.tgz", + "integrity": "sha512-osB2TbvSH4ZL1s62LCBQFAnxPqLycX5fakPHOoztudixqfbVD5QQydeGizJXMMh2zKP6vRCwIJy3MeSuFxPjHg==", + "dependencies": { + "dataloader": "2.2.3", + "dotenv": "16.4.7", + "esprima": "4.0.1", + "fs-extra": "11.3.0", + "globby": "11.1.0", + "mikro-orm": "6.4.9", + "reflect-metadata": "0.2.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/b4nan" + } + }, + "node_modules/@mikro-orm/knex": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@mikro-orm/knex/-/knex-6.4.9.tgz", + "integrity": "sha512-iGXJfe/TziVOQsWuxMIqkOpurysWzQA6kj3+FDtOkHJAijZhqhjSBnfUVHHY/JzU9o0M0rgLrDVJFry/uEaJEA==", + "dependencies": { + "fs-extra": "11.3.0", + "knex": "3.1.0", + "sqlstring": "2.3.3" + }, + "engines": { + "node": ">= 18.12.0" + }, + "peerDependencies": { + "@mikro-orm/core": "^6.0.0", + "better-sqlite3": "*", + "libsql": "*", + "mariadb": "*" + }, + "peerDependenciesMeta": { + "better-sqlite3": { + "optional": true + }, + "libsql": { + "optional": true + }, + "mariadb": { + "optional": true + } + } + }, + "node_modules/@mikro-orm/postgresql": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@mikro-orm/postgresql/-/postgresql-6.4.9.tgz", + "integrity": "sha512-ZdVVFAL/TSbzpEmChGdH0oUpy2KiHLjNIeItZHRQgInn1X9p0qx28VVDR78p8qgRGkQ3LquxGTkvmWI0w7qi3A==", + "dependencies": { + "@mikro-orm/knex": "6.4.9", + "pg": "8.13.3", + "postgres-array": "3.0.4", + "postgres-date": "2.1.0", + "postgres-interval": "4.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "peerDependencies": { + "@mikro-orm/core": "^6.0.0" + } + }, + "node_modules/@mikro-orm/reflection": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@mikro-orm/reflection/-/reflection-6.4.9.tgz", + "integrity": "sha512-fgY7yLrcZm3J/8dv9reUC4PQo7C2muImU31jmzz1SxmNKPJFDJl7OzcDZlM5NOisXzsWUBrcNdCyuQiWViVc3A==", + "dependencies": { + "globby": "11.1.0", + "ts-morph": "25.0.1" + }, + "engines": { + "node": ">= 18.12.0" + }, + "peerDependencies": { + "@mikro-orm/core": "^6.0.0" + } + }, + "node_modules/@mikro-orm/sqlite": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@mikro-orm/sqlite/-/sqlite-6.4.9.tgz", + "integrity": "sha512-O7Jy/5DrTWpJI/3qkhRJHl+OcECx1N625LHDODAAauOK3+MJB/bj80TrvQhe6d/CHZMmvxZ7m2GzaL1NulKxRw==", + "dependencies": { + "@mikro-orm/knex": "6.4.9", + "fs-extra": "11.3.0", + "sqlite3": "5.1.7", + "sqlstring-sqlite": "0.1.1" + }, + "engines": { + "node": ">= 18.12.0" + }, + "peerDependencies": { + "@mikro-orm/core": "^6.0.0" + } + }, + "node_modules/@napi-rs/snappy-android-arm-eabi": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-android-arm-eabi/-/snappy-android-arm-eabi-7.2.2.tgz", + "integrity": "sha512-H7DuVkPCK5BlAr1NfSU8bDEN7gYs+R78pSHhDng83QxRnCLmVIZk33ymmIwurmoA1HrdTxbkbuNl+lMvNqnytw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-android-arm64": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-android-arm64/-/snappy-android-arm64-7.2.2.tgz", + "integrity": "sha512-2R/A3qok+nGtpVK8oUMcrIi5OMDckGYNoBLFyli3zp8w6IArPRfg1yOfVUcHvpUDTo9T7LOS1fXgMOoC796eQw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-darwin-arm64": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-darwin-arm64/-/snappy-darwin-arm64-7.2.2.tgz", + "integrity": "sha512-USgArHbfrmdbuq33bD5ssbkPIoT7YCXCRLmZpDS6dMDrx+iM7eD2BecNbOOo7/v1eu6TRmQ0xOzeQ6I/9FIi5g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-darwin-x64": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-darwin-x64/-/snappy-darwin-x64-7.2.2.tgz", + "integrity": "sha512-0APDu8iO5iT0IJKblk2lH0VpWSl9zOZndZKnBYIc+ei1npw2L5QvuErFOTeTdHBtzvUHASB+9bvgaWnQo4PvTQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-freebsd-x64": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-freebsd-x64/-/snappy-freebsd-x64-7.2.2.tgz", + "integrity": "sha512-mRTCJsuzy0o/B0Hnp9CwNB5V6cOJ4wedDTWEthsdKHSsQlO7WU9W1yP7H3Qv3Ccp/ZfMyrmG98Ad7u7lG58WXA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-linux-arm-gnueabihf": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-arm-gnueabihf/-/snappy-linux-arm-gnueabihf-7.2.2.tgz", + "integrity": "sha512-v1uzm8+6uYjasBPcFkv90VLZ+WhLzr/tnfkZ/iD9mHYiULqkqpRuC8zvc3FZaJy5wLQE9zTDkTJN1IvUcZ+Vcg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-linux-arm64-gnu": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-arm64-gnu/-/snappy-linux-arm64-gnu-7.2.2.tgz", + "integrity": "sha512-LrEMa5pBScs4GXWOn6ZYXfQ72IzoolZw5txqUHVGs8eK4g1HR9HTHhb2oY5ySNaKakG5sOgMsb1rwaEnjhChmQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-linux-arm64-musl": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-arm64-musl/-/snappy-linux-arm64-musl-7.2.2.tgz", + "integrity": "sha512-3orWZo9hUpGQcB+3aTLW7UFDqNCQfbr0+MvV67x8nMNYj5eAeUtMmUE/HxLznHO4eZ1qSqiTwLbVx05/Socdlw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-linux-x64-gnu": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-x64-gnu/-/snappy-linux-x64-gnu-7.2.2.tgz", + "integrity": "sha512-jZt8Jit/HHDcavt80zxEkDpH+R1Ic0ssiVCoueASzMXa7vwPJeF4ZxZyqUw4qeSy7n8UUExomu8G8ZbP6VKhgw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-linux-x64-musl": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-x64-musl/-/snappy-linux-x64-musl-7.2.2.tgz", + "integrity": "sha512-Dh96IXgcZrV39a+Tej/owcd9vr5ihiZ3KRix11rr1v0MWtVb61+H1GXXlz6+Zcx9y8jM1NmOuiIuJwkV4vZ4WA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-win32-arm64-msvc": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-win32-arm64-msvc/-/snappy-win32-arm64-msvc-7.2.2.tgz", + "integrity": "sha512-9No0b3xGbHSWv2wtLEn3MO76Yopn1U2TdemZpCaEgOGccz1V+a/1d16Piz3ofSmnA13HGFz3h9NwZH9EOaIgYA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-win32-ia32-msvc": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-win32-ia32-msvc/-/snappy-win32-ia32-msvc-7.2.2.tgz", + "integrity": "sha512-QiGe+0G86J74Qz1JcHtBwM3OYdTni1hX1PFyLRo3HhQUSpmi13Bzc1En7APn+6Pvo7gkrcy81dObGLDSxFAkQQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/snappy-win32-x64-msvc": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-win32-x64-msvc/-/snappy-win32-x64-msvc-7.2.2.tgz", + "integrity": "sha512-a43cyx1nK0daw6BZxVcvDEXxKMFLSBSDTAhsFD0VqSKcC7MGUBMaqyoWUcMiI7LBSz4bxUmxDWKfCYzpEmeb3w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/fs": { + "version": "1.1.1", + "license": "ISC", + "optional": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "node_modules/@npmcli/move-file": { + "version": "1.1.2", + "license": "MIT", + "optional": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@playwright/test": { + "version": "1.50.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.50.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.28", + "dev": true, + "license": "MIT" + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "4.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.34.8", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.34.8", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@ts-morph/common": { + "version": "0.26.1", + "license": "MIT", + "dependencies": { + "fast-glob": "^3.3.2", + "minimatch": "^9.0.4", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node22": { + "version": "22.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/eslint-config-prettier": { + "version": "6.11.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "license": "MIT" + }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsdom": { + "version": "21.1.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.9.tgz", + "integrity": "sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ==", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==" + }, + "node_modules/@types/node": { + "version": "22.13.4", + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/qs": { + "version": "6.9.18", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "license": "MIT" + }, + "node_modules/@types/response-time": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@types/response-time/-/response-time-2.3.8.tgz", + "integrity": "sha512-7qGaNYvdxc0zRab8oHpYx7AW17qj+G0xuag1eCrw3M2VWPJQ/HyKaaghWygiaOUl0y9x7QGQwppDpqLJ5V9pzw==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/node": "*" + } + }, + "node_modules/@types/send": { + "version": "0.17.4", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "optional": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.24.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.24.1", + "@typescript-eslint/type-utils": "8.24.1", + "@typescript-eslint/utils": "8.24.1", + "@typescript-eslint/visitor-keys": "8.24.1", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.24.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.24.1", + "@typescript-eslint/types": "8.24.1", + "@typescript-eslint/typescript-estree": "8.24.1", + "@typescript-eslint/visitor-keys": "8.24.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.24.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.24.1", + "@typescript-eslint/visitor-keys": "8.24.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.24.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.24.1", + "@typescript-eslint/utils": "8.24.1", + "debug": "^4.3.4", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.24.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.24.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.24.1", + "@typescript-eslint/visitor-keys": "8.24.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.24.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.24.1", + "@typescript-eslint/types": "8.24.1", + "@typescript-eslint/typescript-estree": "8.24.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.24.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.24.1", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vitest/eslint-plugin": { + "version": "1.1.31", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@typescript-eslint/utils": ">= 8.0", + "eslint": ">= 8.57.0", + "typescript": ">= 5.0.0", + "vitest": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "3.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.0.6", + "@vitest/utils": "3.0.6", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.0.6", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.0.6", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.0.6", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.0.6", + "loupe": "^3.1.3", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.11", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.11" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.11", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.11", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.11", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/babel-helper-vue-transform-on": { + "version": "1.2.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/babel-plugin-jsx": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/plugin-syntax-jsx": "^7.24.7", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.6", + "@babel/types": "^7.25.6", + "@vue/babel-helper-vue-transform-on": "1.2.5", + "@vue/babel-plugin-resolve-type": "1.2.5", + "html-tags": "^3.3.1", + "svg-tags": "^1.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + } + } + }, + "node_modules/@vue/babel-plugin-resolve-type": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/parser": "^7.25.6", + "@vue/compiler-sfc": "^3.5.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.13", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.13", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.13", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.13", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.13", + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.11", + "postcss": "^8.4.48", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.13", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "license": "MIT" + }, + "node_modules/@vue/devtools-core": { + "version": "7.7.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.2", + "@vue/devtools-shared": "^7.7.2", + "mitt": "^3.0.1", + "nanoid": "^5.0.9", + "pathe": "^2.0.2", + "vite-hot-client": "^0.2.4" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/@vue/devtools-core/node_modules/nanoid": { + "version": "5.1.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.2", + "birpc": "^0.2.19", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.1" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.2", + "dev": true, + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/eslint-config-prettier": { + "version": "10.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-prettier": "^5.2.2" + }, + "peerDependencies": { + "eslint": ">= 8.21.0", + "prettier": ">= 3.0.0" + } + }, + "node_modules/@vue/eslint-config-typescript": { + "version": "14.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.23.0", + "fast-glob": "^3.3.3", + "typescript-eslint": "^8.23.0", + "vue-eslint-parser": "^9.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.10.0", + "eslint-plugin-vue": "^9.28.0", + "typescript": ">=4.8.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core": { + "version": "2.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "~2.4.11", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^1.0.3", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.13", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.13", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.13", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.13", + "@vue/runtime-core": "3.5.13", + "@vue/shared": "3.5.13", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.13", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13" + }, + "peerDependencies": { + "vue": "3.5.13" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.13", + "license": "MIT" + }, + "node_modules/@vue/test-utils": { + "version": "2.4.6", + "dev": true, + "license": "MIT", + "dependencies": { + "js-beautify": "^1.14.9", + "vue-component-type-helpers": "^2.0.0" + } + }, + "node_modules/@vue/tsconfig": { + "version": "0.7.0", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": "5.x", + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/abbrev": { + "version": "3.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.14.0", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.3", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "license": "MIT", + "optional": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "license": "MIT", + "optional": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/alien-signals": { + "version": "1.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "license": "ISC", + "optional": true + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "license": "ISC", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "license": "Python-2.0" + }, + "node_modules/array-union": { + "version": "2.1.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz", + "integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bindings": { + "version": "1.5.0", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/birpc": { + "version": "0.2.19", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.5.2", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.14.0", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.4", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", + "bin": { + "btoa": "bin/btoa.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cacache": { + "version": "15.3.0", + "license": "ISC", + "optional": true, + "dependencies": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cacache/node_modules/brace-expansion": { + "version": "1.1.11", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "7.2.3", + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "6.0.0", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "3.1.2", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/cacache/node_modules/minipass": { + "version": "3.3.6", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacache/node_modules/yallist": { + "version": "4.0.0", + "license": "ISC", + "optional": true + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001700", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/code-block-writer": { + "version": "13.0.3", + "license": "MIT" + }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "license": "ISC", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/colorette": { + "version": "2.0.19", + "license": "MIT" + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "10.0.1", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "devOptional": true, + "license": "MIT" + }, + "node_modules/config-chain": { + "version": "1.1.13", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "license": "ISC", + "optional": true + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.1", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/copy-anything": { + "version": "3.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/cross": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cross/-/cross-1.0.0.tgz", + "integrity": "sha512-p6hXbCnjuIB4bhKWFeztQd7VwffgQP9zOBzUoiA8Lvi01RzQY0e7PbPFU/uqVPTM2stY7uCpVck1UTPpxhinMQ==" + }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssstyle": { + "version": "4.2.1", + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^2.8.2", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/dataloader": { + "version": "2.2.3", + "license": "MIT" + }, + "node_modules/de-indent": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.0", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.5.0", + "license": "MIT" + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/default-browser": { + "version": "5.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "license": "MIT", + "optional": true + }, + "node_modules/depd": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dompurify": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.4.tgz", + "integrity": "sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/dotenv": { + "version": "16.4.7", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/dwengo-1-backend": { + "resolved": "backend", + "link": true + }, + "node_modules/dwengo-1-frontend": { + "resolved": "frontend", + "link": true + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/editorconfig": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/minimatch": { + "version": "9.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.102", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "license": "MIT" + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding": { + "version": "0.1.13", + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "license": "MIT", + "optional": true + }, + "node_modules/error-stack-parser-es": { + "version": "0.1.5", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.6.0", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.0", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.0", + "@esbuild/android-arm": "0.25.0", + "@esbuild/android-arm64": "0.25.0", + "@esbuild/android-x64": "0.25.0", + "@esbuild/darwin-arm64": "0.25.0", + "@esbuild/darwin-x64": "0.25.0", + "@esbuild/freebsd-arm64": "0.25.0", + "@esbuild/freebsd-x64": "0.25.0", + "@esbuild/linux-arm": "0.25.0", + "@esbuild/linux-arm64": "0.25.0", + "@esbuild/linux-ia32": "0.25.0", + "@esbuild/linux-loong64": "0.25.0", + "@esbuild/linux-mips64el": "0.25.0", + "@esbuild/linux-ppc64": "0.25.0", + "@esbuild/linux-riscv64": "0.25.0", + "@esbuild/linux-s390x": "0.25.0", + "@esbuild/linux-x64": "0.25.0", + "@esbuild/netbsd-arm64": "0.25.0", + "@esbuild/netbsd-x64": "0.25.0", + "@esbuild/openbsd-arm64": "0.25.0", + "@esbuild/openbsd-x64": "0.25.0", + "@esbuild/sunos-x64": "0.25.0", + "@esbuild/win32-arm64": "0.25.0", + "@esbuild/win32-ia32": "0.25.0", + "@esbuild/win32-x64": "0.25.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.20.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.11.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.20.0", + "@eslint/plugin-kit": "^0.2.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.0.1", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "build/bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-playwright": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "workspaces": [ + "examples" + ], + "dependencies": { + "globals": "^13.23.0" + }, + "engines": { + "node": ">=16.6.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/eslint-plugin-playwright/node_modules/globals": { + "version": "13.24.0", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.9.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-vue": { + "version": "9.32.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "globals": "^13.24.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^6.0.15", + "semver": "^7.6.3", + "vue-eslint-parser": "^9.4.3", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-vue/node_modules/globals": { + "version": "13.24.0", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-scope": { + "version": "8.2.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/esm": { + "version": "3.2.25", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/execa": { + "version": "9.5.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.3", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.0", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/expect-type": { + "version": "1.1.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/express": { + "version": "5.0.1", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.0.1", + "content-disposition": "^1.0.0", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "^1.2.1", + "debug": "4.3.6", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "^2.0.0", + "fresh": "2.0.0", + "http-errors": "2.0.0", + "merge-descriptors": "^2.0.0", + "methods": "~1.1.2", + "mime-types": "^3.0.0", + "on-finished": "2.4.1", + "once": "1.4.0", + "parseurl": "~1.3.3", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "router": "^2.0.0", + "safe-buffer": "5.2.1", + "send": "^1.1.0", + "serve-static": "^2.1.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "^2.0.0", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/express-jwt": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/express-jwt/-/express-jwt-8.5.1.tgz", + "integrity": "sha512-Dv6QjDLpR2jmdb8M6XQXiCcpEom7mK8TOqnr0/TngDKsG2DHVkO8+XnVxkJVN7BuS1I3OrGw6N8j5DaaGgkDRQ==", + "dependencies": { + "@types/jsonwebtoken": "^9", + "express-unless": "^2.1.3", + "jsonwebtoken": "^9.0.0" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/express-unless": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/express-unless/-/express-unless-2.1.3.tgz", + "integrity": "sha512-wj4tLMyCVYuIIKHGt0FhCtIViBcwzWejX0EjNxveAa6dG+0XBCQhMbx+PnkLkFCxLC69qoFrxds4pIyL88inaQ==" + }, + "node_modules/express/node_modules/debug": { + "version": "4.3.6", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.1.2", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.0", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + }, + "node_modules/figlet": { + "version": "1.8.0", + "dev": true, + "license": "MIT", + "bin": { + "figlet": "bin/index.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/figures": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/encodeurl": { + "version": "1.0.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "dev": true, + "license": "ISC" + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/fs-extra": { + "version": "11.3.0", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "license": "ISC" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "license": "ISC", + "optional": true + }, + "node_modules/function-bind": { + "version": "1.1.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "4.0.4", + "license": "ISC", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/gauge/node_modules/ansi-regex": { + "version": "5.0.1", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/gauge/node_modules/emoji-regex": { + "version": "8.0.0", + "license": "MIT", + "optional": true + }, + "node_modules/gauge/node_modules/signal-exit": { + "version": "3.0.7", + "license": "ISC", + "optional": true + }, + "node_modules/gauge/node_modules/string-width": { + "version": "4.2.3", + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/gauge/node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.7", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "9.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.0", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/getopts": { + "version": "2.3.0", + "license": "MIT" + }, + "node_modules/gift-pegjs": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/gift-pegjs/-/gift-pegjs-1.0.2.tgz", + "integrity": "sha512-S/A2wBDdia2QWKpB5FtASx1gguep1wg5If5glDWJgUMiABICJT7ogArGfsdgozevhBdbdOiHhrykJP86hbgvRw==", + "dependencies": { + "pegjs": "^0.10.x" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "license": "MIT" + }, + "node_modules/glob": { + "version": "10.4.5", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "license": "ISC", + "optional": true + }, + "node_modules/hasown": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "dev": true, + "license": "MIT" + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/html-tags": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "license": "BSD-2-Clause", + "optional": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "8.0.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.5.2", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "license": "ISC", + "optional": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "license": "ISC", + "optional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "license": "ISC" + }, + "node_modules/interpret": { + "version": "2.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "license": "MIT", + "optional": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "license": "MIT", + "optional": true + }, + "node_modules/is-number": { + "version": "7.0.0", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/is-promise": { + "version": "4.0.0", + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-what": { + "version": "4.1.16", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "license": "ISC" + }, + "node_modules/isomorphic-dompurify": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/isomorphic-dompurify/-/isomorphic-dompurify-2.22.0.tgz", + "integrity": "sha512-A2xsDNST1yB94rErEnwqlzSvGllCJ4e8lDMe1OWBH2hvpfc/2qzgMEiDshTO1HwO+PIDTiYeOc7ZDB7Ds49BOg==", + "dependencies": { + "dompurify": "^3.2.4", + "jsdom": "^26.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "2.4.2", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-beautify": { + "version": "1.15.3", + "dev": true, + "license": "MIT", + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.4.2", + "js-cookie": "^3.0.5", + "nopt": "^8.0.0" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "license": "MIT", + "optional": true + }, + "node_modules/jsdom": { + "version": "26.0.0", + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.1", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/xml-name-validator": { + "version": "5.0.0", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/jsep": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", + "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", + "engines": { + "node": ">= 10.16.0" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonpath-plus": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.3.0.tgz", + "integrity": "sha512-8TNmfeTCk2Le33A3vRRwtuworG/L5RrgMvdjhKZxvyShO+mBu2fP50OWUjRLNtvw344DdDarFh9buFAZs5ujeA==", + "dependencies": { + "@jsep-plugin/assignment": "^1.3.0", + "@jsep-plugin/regex": "^1.0.4", + "jsep": "^1.4.0" + }, + "bin": { + "jsonpath": "bin/jsonpath-cli.js", + "jsonpath-plus": "bin/jsonpath-cli.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwks-rsa": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.1.0.tgz", + "integrity": "sha512-v7nqlfezb9YfHHzYII3ef2a2j1XnGeSE/bK3WfumaYCqONAIstJbrEGapz4kadScZzEt7zYCN7bucj8C0Mv/Rg==", + "dependencies": { + "@types/express": "^4.17.17", + "@types/jsonwebtoken": "^9.0.2", + "debug": "^4.3.4", + "jose": "^4.14.6", + "limiter": "^1.1.5", + "lru-memoizer": "^2.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/jwks-rsa/node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/jwks-rsa/node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/knex": { + "version": "3.1.0", + "license": "MIT", + "dependencies": { + "colorette": "2.0.19", + "commander": "^10.0.0", + "debug": "4.3.4", + "escalade": "^3.1.1", + "esm": "^3.2.25", + "get-package-type": "^0.1.0", + "getopts": "2.3.0", + "interpret": "^2.2.0", + "lodash": "^4.17.21", + "pg-connection-string": "2.6.2", + "rechoir": "^0.8.0", + "resolve-from": "^5.0.0", + "tarn": "^3.0.2", + "tildify": "2.0.0" + }, + "bin": { + "knex": "bin/cli.js" + }, + "engines": { + "node": ">=16" + }, + "peerDependenciesMeta": { + "better-sqlite3": { + "optional": true + }, + "mysql": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-native": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "tedious": { + "optional": true + } + } + }, + "node_modules/knex/node_modules/debug": { + "version": "4.3.4", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/knex/node_modules/ms": { + "version": "2.1.2", + "license": "MIT" + }, + "node_modules/knex/node_modules/resolve-from": { + "version": "5.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/kolorist": { + "version": "1.8.0", + "dev": true, + "license": "MIT" + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, + "node_modules/levn": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "license": "MIT" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/loki-logger-ts": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/loki-logger-ts/-/loki-logger-ts-1.0.2.tgz", + "integrity": "sha512-SV/B5o+9jaxiThcU5N3LUxCNTx20IgR9xjCjx/ED/pVc/097mqKSRpmvSjvx9ezFcjJlUF7GBkrBBpR6veNp7Q==", + "dependencies": { + "axios": "^1.4.0" + } + }, + "node_modules/long": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.1.tgz", + "integrity": "sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng==" + }, + "node_modules/loupe": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "license": "ISC" + }, + "node_modules/lru-memoizer": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz", + "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "6.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lru-memoizer/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/magic-string": { + "version": "0.30.17", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "dev": true, + "license": "ISC" + }, + "node_modules/make-fetch-happen": { + "version": "9.1.0", + "license": "ISC", + "optional": true, + "dependencies": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/make-fetch-happen/node_modules/agent-base": { + "version": "6.0.2", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/http-proxy-agent": { + "version": "4.0.1", + "license": "MIT", + "optional": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/make-fetch-happen/node_modules/https-proxy-agent": { + "version": "5.0.1", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "6.0.0", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-fetch-happen/node_modules/minipass": { + "version": "3.3.6", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/make-fetch-happen/node_modules/negotiator": { + "version": "0.6.4", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/make-fetch-happen/node_modules/yallist": { + "version": "4.0.0", + "license": "ISC", + "optional": true + }, + "node_modules/marked": { + "version": "15.0.7", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.7.tgz", + "integrity": "sha512-dgLIeKGLx5FwziAnsk4ONoGwHwGPJzselimvlVskE9XLN4Orv9u2VA3GWw/lYUqjfA0rUT/6fqKwfZJapP9BEg==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/memorystream": { + "version": "0.3.1", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mikro-orm": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/mikro-orm/-/mikro-orm-6.4.9.tgz", + "integrity": "sha512-XwVrWNT4NNwS6kHIKFNDfvy8L1eWcBBEHeTVzFFYcnb2ummATaLxqeVkNEmKA68jmdtfQdUmWBqGdbcIPwtL2Q==", + "engines": { + "node": ">= 18.12.0" + } + }, + "node_modules/mime-db": { + "version": "1.53.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "mime-db": "^1.53.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-collect/node_modules/minipass": { + "version": "3.3.6", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect/node_modules/yallist": { + "version": "4.0.0", + "license": "ISC", + "optional": true + }, + "node_modules/minipass-fetch": { + "version": "1.4.1", + "license": "MIT", + "optional": true, + "dependencies": { + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "optionalDependencies": { + "encoding": "^0.1.12" + } + }, + "node_modules/minipass-fetch/node_modules/minipass": { + "version": "3.3.6", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-fetch/node_modules/yallist": { + "version": "4.0.0", + "license": "ISC", + "optional": true + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "license": "ISC", + "optional": true + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "license": "ISC", + "optional": true + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "license": "ISC", + "optional": true + }, + "node_modules/minizlib": { + "version": "2.1.2", + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "license": "ISC" + }, + "node_modules/mitt": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "license": "MIT" + }, + "node_modules/mrmime": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.8", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abi": { + "version": "3.74.0", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "license": "MIT" + }, + "node_modules/node-gyp": { + "version": "8.4.1", + "license": "MIT", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/node-gyp/node_modules/abbrev": { + "version": "1.1.1", + "license": "ISC", + "optional": true + }, + "node_modules/node-gyp/node_modules/brace-expansion": { + "version": "1.1.11", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/node-gyp/node_modules/glob": { + "version": "7.2.3", + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/minimatch": { + "version": "3.1.2", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/node-gyp/node_modules/nopt": { + "version": "5.0.0", + "license": "ISC", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "dev": true, + "license": "MIT" + }, + "node_modules/nopt": { + "version": "8.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^3.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-run-all2": { + "version": "7.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "cross-spawn": "^7.0.6", + "memorystream": "^0.3.1", + "minimatch": "^9.0.0", + "pidtree": "^0.6.0", + "read-package-json-fast": "^4.0.0", + "shell-quote": "^1.7.3", + "which": "^5.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "npm-run-all2": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0", + "npm": ">= 9" + } + }, + "node_modules/npm-run-all2/node_modules/ansi-styles": { + "version": "6.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm-run-all2/node_modules/isexe": { + "version": "3.1.1", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/npm-run-all2/node_modules/which": { + "version": "5.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-run-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npmlog": { + "version": "6.0.2", + "license": "ISC", + "optional": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nwsapi": { + "version": "2.2.16", + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/oidc-client-ts": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-3.2.0.tgz", + "integrity": "sha512-wUvVcG3SXzZDKHxi/VGQGaTUk9qguMKfYh26Y1zOVrQsu1zp85JWx/SjZzKSXK5j3NA1RcasgMoaHe6gt1WNtw==", + "dependencies": { + "jwt-decode": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/open": { + "version": "10.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "license": "MIT", + "optional": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-require": { + "version": "1.0.0", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/parse-ms": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.2.1", + "license": "MIT", + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/pegjs": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz", + "integrity": "sha512-qI5+oFNEGi3L5HAxDwN2LA4Gg7irF70Zs25edhjld9QemOgp0CbvMtbFcMvFtEo1OityPrcCzkQFB8JP/hxgow==", + "bin": { + "pegjs": "bin/pegjs" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/pg": { + "version": "8.13.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.13.3.tgz", + "integrity": "sha512-P6tPt9jXbL9HVu/SSRERNYaYG++MjnscnegFh9pPHihfoBSujsrka0hyuymMzeJKFWrcG8wvCKy8rCe8e5nDUQ==", + "dependencies": { + "pg-connection-string": "^2.7.0", + "pg-pool": "^3.7.1", + "pg-protocol": "^1.7.1", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.1.1" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz", + "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.6.2", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.8.0.tgz", + "integrity": "sha512-VBw3jiVm6ZOdLBTIcXLNdSotb6Iy3uOCwDGFAksZCXmi10nyRvnP2v3jl4d+IsLYRyXf6o9hIm/ZtUzlByNUdw==", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.8.0.tgz", + "integrity": "sha512-jvuYlEkL03NRvOoyoRktBK7+qU5kOvlAwvmrH8sr3wbLrOdVWsRxQfz8mMy9sZFsqJ1hEWNfdWKI4SAmoL+j7g==" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pg-types/node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/pg-types/node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pg-types/node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pg/node_modules/pg-connection-string": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.7.0.tgz", + "integrity": "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==" + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.0", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/playwright": { + "version": "1.50.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.50.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.50.1", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-array": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz", + "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "2.1.0", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-interval": { + "version": "4.0.2", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.5.1", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-ms": { + "version": "9.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "license": "ISC", + "optional": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "license": "MIT", + "optional": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "dev": true, + "license": "ISC" + }, + "node_modules/protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.2", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.6.3", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-package-json-fast": { + "version": "4.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "license": "Apache-2.0" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/response-time": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/response-time/-/response-time-2.3.3.tgz", + "integrity": "sha512-SsjjOPHl/FfrTQNgmc5oen8Hr1Jxpn6LlHNXxCIFdYMHuK1kMeYMobb9XN3mvxaGQm3dbegqYFMX4+GDORfbWg==", + "dependencies": { + "depd": "~2.0.0", + "on-headers": "~1.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/rimraf": { + "version": "3.0.2", + "license": "ISC", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/rollup": { + "version": "4.34.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.34.8", + "@rollup/rollup-android-arm64": "4.34.8", + "@rollup/rollup-darwin-arm64": "4.34.8", + "@rollup/rollup-darwin-x64": "4.34.8", + "@rollup/rollup-freebsd-arm64": "4.34.8", + "@rollup/rollup-freebsd-x64": "4.34.8", + "@rollup/rollup-linux-arm-gnueabihf": "4.34.8", + "@rollup/rollup-linux-arm-musleabihf": "4.34.8", + "@rollup/rollup-linux-arm64-gnu": "4.34.8", + "@rollup/rollup-linux-arm64-musl": "4.34.8", + "@rollup/rollup-linux-loongarch64-gnu": "4.34.8", + "@rollup/rollup-linux-powerpc64le-gnu": "4.34.8", + "@rollup/rollup-linux-riscv64-gnu": "4.34.8", + "@rollup/rollup-linux-s390x-gnu": "4.34.8", + "@rollup/rollup-linux-x64-gnu": "4.34.8", + "@rollup/rollup-linux-x64-musl": "4.34.8", + "@rollup/rollup-win32-arm64-msvc": "4.34.8", + "@rollup/rollup-win32-ia32-msvc": "4.34.8", + "@rollup/rollup-win32-x64-msvc": "4.34.8", + "fsevents": "~2.3.2" + } + }, + "node_modules/router": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "license": "MIT" + }, + "node_modules/run-applescript": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "7.7.1", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "destroy": "^1.2.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^0.5.2", + "http-errors": "^2.0.0", + "mime-types": "^2.1.35", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/send/node_modules/fresh": { + "version": "0.5.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "license": "ISC", + "optional": true + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/sirv": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/snappy": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/snappy/-/snappy-7.2.2.tgz", + "integrity": "sha512-iADMq1kY0v3vJmGTuKcFWSXt15qYUz7wFkArOrsSg0IFfI3nJqIJvK2/ZbEIndg7erIJLtAVX2nSOqPz7DcwbA==", + "optional": true, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/snappy-android-arm-eabi": "7.2.2", + "@napi-rs/snappy-android-arm64": "7.2.2", + "@napi-rs/snappy-darwin-arm64": "7.2.2", + "@napi-rs/snappy-darwin-x64": "7.2.2", + "@napi-rs/snappy-freebsd-x64": "7.2.2", + "@napi-rs/snappy-linux-arm-gnueabihf": "7.2.2", + "@napi-rs/snappy-linux-arm64-gnu": "7.2.2", + "@napi-rs/snappy-linux-arm64-musl": "7.2.2", + "@napi-rs/snappy-linux-x64-gnu": "7.2.2", + "@napi-rs/snappy-linux-x64-musl": "7.2.2", + "@napi-rs/snappy-win32-arm64-msvc": "7.2.2", + "@napi-rs/snappy-win32-ia32-msvc": "7.2.2", + "@napi-rs/snappy-win32-x64-msvc": "7.2.2" + } + }, + "node_modules/socks": { + "version": "2.8.4", + "license": "MIT", + "optional": true, + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "6.2.1", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/socks-proxy-agent/node_modules/agent-base": { + "version": "6.0.2", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/sqlite3": { + "version": "5.1.7", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.1", + "tar": "^6.1.11" + }, + "optionalDependencies": { + "node-gyp": "8.x" + }, + "peerDependencies": { + "node-gyp": "8.x" + }, + "peerDependenciesMeta": { + "node-gyp": { + "optional": true + } + } + }, + "node_modules/sqlstring": { + "version": "2.3.3", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/sqlstring-sqlite": { + "version": "0.1.1", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ssri": { + "version": "8.0.1", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/ssri/node_modules/minipass": { + "version": "3.3.6", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ssri/node_modules/yallist": { + "version": "4.0.0", + "license": "ISC", + "optional": true + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "engines": { + "node": "*" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.8.0", + "dev": true, + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/superjson": { + "version": "2.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-tags": { + "version": "1.0.0", + "dev": true + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "license": "MIT" + }, + "node_modules/synckit": { + "version": "0.9.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-fs": { + "version": "2.1.2", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "license": "ISC" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "license": "ISC" + }, + "node_modules/tarn": { + "version": "3.0.2", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, + "node_modules/tildify": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "6.1.77", + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.77" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.77", + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie": { + "version": "5.1.1", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-morph": { + "version": "25.0.1", + "license": "MIT", + "dependencies": { + "@ts-morph/common": "~0.26.0", + "code-block-writer": "^13.0.3" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.19.3", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.7.3", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.24.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.24.1", + "@typescript-eslint/parser": "8.24.1", + "@typescript-eslint/utils": "8.24.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unique-filename": { + "version": "1.1.1", + "license": "ISC", + "optional": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "license": "ISC", + "optional": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.2", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-polyfill": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/url-polyfill/-/url-polyfill-1.1.13.tgz", + "integrity": "sha512-tXzkojrv2SujumYthZ/WjF7jaSfNhSXlYMpE5AYdL2I3D7DCeo+mch8KtW2rUuKjDg+3VXODXHVgipt8yGY/eQ==" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "11.1.0", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "6.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.24.2", + "postcss": "^8.5.2", + "rollup": "^4.30.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-hot-client": { + "version": "0.2.4", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0" + } + }, + "node_modules/vite-node": { + "version": "3.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.0", + "es-module-lexer": "^1.6.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-plugin-inspect": { + "version": "0.8.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/utils": "^0.7.10", + "@rollup/pluginutils": "^5.1.3", + "debug": "^4.3.7", + "error-stack-parser-es": "^0.1.5", + "fs-extra": "^11.2.0", + "open": "^10.1.0", + "perfect-debounce": "^1.0.0", + "picocolors": "^1.1.1", + "sirv": "^3.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.1" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/vite-plugin-vue-devtools": { + "version": "7.7.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-core": "^7.7.2", + "@vue/devtools-kit": "^7.7.2", + "@vue/devtools-shared": "^7.7.2", + "execa": "^9.5.1", + "sirv": "^3.0.0", + "vite-plugin-inspect": "0.8.9", + "vite-plugin-vue-inspector": "^5.3.1" + }, + "engines": { + "node": ">=v14.21.3" + }, + "peerDependencies": { + "vite": "^3.1.0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0" + } + }, + "node_modules/vite-plugin-vue-inspector": { + "version": "5.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.0", + "@babel/plugin-proposal-decorators": "^7.23.0", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-transform-typescript": "^7.22.15", + "@vue/babel-plugin-jsx": "^1.1.5", + "@vue/compiler-dom": "^3.3.4", + "kolorist": "^1.8.0", + "magic-string": "^0.30.4" + }, + "peerDependencies": { + "vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.24.2", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.24.2", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" + } + }, + "node_modules/vitest": { + "version": "3.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "3.0.6", + "@vitest/mocker": "3.0.6", + "@vitest/pretty-format": "^3.0.6", + "@vitest/runner": "3.0.6", + "@vitest/snapshot": "3.0.6", + "@vitest/spy": "3.0.6", + "@vitest/utils": "3.0.6", + "chai": "^5.2.0", + "debug": "^4.4.0", + "expect-type": "^1.1.0", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinypool": "^1.0.2", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0", + "vite-node": "3.0.6", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.0.6", + "@vitest/ui": "3.0.6", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.13", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-sfc": "3.5.13", + "@vue/runtime-dom": "3.5.13", + "@vue/server-renderer": "3.5.13", + "@vue/shared": "3.5.13" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-component-type-helpers": { + "version": "2.2.2", + "dev": true, + "license": "MIT" + }, + "node_modules/vue-eslint-parser": { + "version": "9.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^7.3.6" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/eslint-scope": { + "version": "7.2.2", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/vue-eslint-parser/node_modules/espree": { + "version": "9.6.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/vue-router": { + "version": "4.5.0", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/vue-tsc": { + "version": "2.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "~2.4.11", + "@vue/language-core": "2.2.2" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/vuetify": { + "version": "3.7.12", + "license": "MIT", + "engines": { + "node": "^12.20 || >=14.13" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/johnleider" + }, + "peerDependencies": { + "typescript": ">=4.7", + "vite-plugin-vuetify": ">=1.0.0", + "vue": "^3.3.0", + "webpack-plugin-vuetify": ">=2.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vite-plugin-vuetify": { + "optional": true + }, + "webpack-plugin-vuetify": { + "optional": true + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/w3c-xmlserializer/node_modules/xml-name-validator": { + "version": "5.0.0", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.1.1", + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wide-align/node_modules/ansi-regex": { + "version": "5.0.1", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wide-align/node_modules/emoji-regex": { + "version": "8.0.0", + "license": "MIT", + "optional": true + }, + "node_modules/wide-align/node_modules/string-width": { + "version": "4.2.3", + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wide-align/node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/winston": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-loki": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/winston-loki/-/winston-loki-6.1.3.tgz", + "integrity": "sha512-DjWtJ230xHyYQWr9mZJa93yhwHttn3JEtSYWP8vXZWJOahiQheUhf+88dSIidbGXB3u0oLweV6G1vkL/ouT62Q==", + "dependencies": { + "async-exit-hook": "2.0.1", + "btoa": "^1.2.1", + "protobufjs": "^7.2.4", + "url-polyfill": "^1.1.12", + "winston-transport": "^4.3.0" + }, + "optionalDependencies": { + "snappy": "^7.2.2" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.0", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "license": "MIT" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} From e78849f5683f3c2edda03ff1d26f091d7ca600b7 Mon Sep 17 00:00:00 2001 From: Lint Action Date: Thu, 13 Mar 2025 17:45:28 +0000 Subject: [PATCH 082/106] style: fix linting issues met ESLint --- backend/src/controllers/groups.ts | 2 +- backend/src/controllers/questions.ts | 30 +++++++++---------- backend/src/controllers/submissions.ts | 16 +++++----- .../src/data/questions/question-repository.ts | 6 ++-- backend/src/data/users/student-repository.ts | 4 +-- .../entities/assignments/assignment.entity.ts | 16 +++------- .../src/entities/assignments/group.entity.ts | 4 +-- .../classes/class-join-request.entity.ts | 4 +-- backend/src/entities/classes/class.entity.ts | 4 +-- .../src/entities/content/attachment.entity.ts | 8 ++--- backend/src/interfaces/class.ts | 8 ++--- backend/src/interfaces/group.ts | 4 +-- backend/src/interfaces/submission.ts | 6 ++-- backend/src/routes/questions.ts | 2 +- backend/src/services/class.ts | 10 ++----- backend/src/services/learning-objects.ts | 16 +++------- backend/src/services/questions.ts | 10 +++---- backend/src/services/students.ts | 8 ++--- backend/src/services/submissions.ts | 2 +- backend/src/services/teachers.ts | 16 +++------- backend/src/services/users.ts | 4 +-- 21 files changed, 64 insertions(+), 116 deletions(-) diff --git a/backend/src/controllers/groups.ts b/backend/src/controllers/groups.ts index 5fd90553..9ef5b93c 100644 --- a/backend/src/controllers/groups.ts +++ b/backend/src/controllers/groups.ts @@ -83,7 +83,7 @@ export async function getGroupSubmissionsHandler( res: Response, ): Promise { const classId = req.params.classid; - // const full = req.query.full === 'true'; + // Const full = req.query.full === 'true'; const assignmentId = +req.params.assignmentid; diff --git a/backend/src/controllers/questions.ts b/backend/src/controllers/questions.ts index 2b57f4c9..b4451aa2 100644 --- a/backend/src/controllers/questions.ts +++ b/backend/src/controllers/questions.ts @@ -32,7 +32,7 @@ function getQuestionId(req: Request, res: Response): QuestionId | null { const learningObjectIdentifier = getObjectId(req,res); if (!learningObjectIdentifier) - return null + {return null} return { learningObjectIdentifier, @@ -48,28 +48,28 @@ export async function getAllQuestionsHandler( const full = req.query.full === 'true'; if (!objectId) - return + {return} const questions = await getAllQuestions(objectId, full); if (!questions) - res.status(404).json({ error: `Questions not found.` }); + {res.status(404).json({ error: `Questions not found.` });} else - res.json(questions); + {res.json(questions);} } export async function getQuestionHandler(req: Request, res: Response): Promise { const questionId = getQuestionId(req, res); if (!questionId) - return + {return} const question = await getQuestion(questionId); if (!question) - res.status(404).json({ error: `Question not found.` }); + {res.status(404).json({ error: `Question not found.` });} else - res.json(question) + {res.json(question)} } export async function getQuestionAnswersHandler(req: Request, res: Response): Promise { @@ -77,14 +77,14 @@ export async function getQuestionAnswersHandler(req: Request, res: Response): Pr const full = req.query.full === 'true'; if (!questionId) - return + {return} const answers = getAnswersByQuestion(questionId, full); if (!answers) - res.status(404).json({ error: `Questions not found.` }); + {res.status(404).json({ error: `Questions not found.` });} else - res.json(answers) + {res.json(answers)} } export async function createQuestionHandler(req: Request, res: Response): Promise { @@ -98,23 +98,23 @@ export async function createQuestionHandler(req: Request, res: Response): Promis const question = await createQuestion(questionDTO); if (!question) - res.status(400).json({error: 'Could not add question'}); + {res.status(400).json({error: 'Could not add question'});} else - res.json(question) + {res.json(question)} } export async function deleteQuestionHandler(req: Request, res: Response): Promise { const questionId = getQuestionId(req, res); if (!questionId) - return + {return} const question = await deleteQuestion(questionId); if (!question) - res.status(400).json({error: 'Could not find nor delete question'}); + {res.status(400).json({error: 'Could not find nor delete question'});} else - res.json(question) + {res.json(question)} } diff --git a/backend/src/controllers/submissions.ts b/backend/src/controllers/submissions.ts index 4080d1e7..96f0185c 100644 --- a/backend/src/controllers/submissions.ts +++ b/backend/src/controllers/submissions.ts @@ -20,8 +20,8 @@ export async function getSubmissionHandler( return; } - let lang = languageMap[req.query.language as string] || Language.Dutch; - let version = (req.query.version || 1) as number; + const lang = languageMap[req.query.language as string] || Language.Dutch; + const version = (req.query.version || 1) as number; const submission = await getSubmission(lohruid, lang, version, submissionNumber); @@ -39,22 +39,22 @@ export async function createSubmissionHandler(req: Request, res: Response){ const submission = await createSubmission(submissionDTO); if (!submission) - res.status(404).json({ error: 'Submission not added' }); + {res.status(404).json({ error: 'Submission not added' });} else - res.json(submission) + {res.json(submission)} } export async function deleteSubmissionHandler(req: Request, res: Response){ const hruid = req.params.hruid; const submissionNumber = +req.params.id; - let lang = languageMap[req.query.language as string] || Language.Dutch; - let version = (req.query.version || 1) as number; + const lang = languageMap[req.query.language as string] || Language.Dutch; + const version = (req.query.version || 1) as number; const submission = await deleteSubmission(hruid, lang, version, submissionNumber); if (!submission) - res.status(404).json({ error: 'Submission not found' }); + {res.status(404).json({ error: 'Submission not found' });} else - res.json(submission) + {res.json(submission)} } diff --git a/backend/src/data/questions/question-repository.ts b/backend/src/data/questions/question-repository.ts index dc8502d1..e24d48b8 100644 --- a/backend/src/data/questions/question-repository.ts +++ b/backend/src/data/questions/question-repository.ts @@ -50,13 +50,11 @@ export class QuestionRepository extends DwengoEntityRepository { public async findAllByLearningObjects( learningObjects: LearningObject[] ): Promise { - const objectIdentifiers = learningObjects.map((lo) => { - return { + const objectIdentifiers = learningObjects.map((lo) => ({ learningObjectHruid: lo.hruid, learningObjectLanguage: lo.language, learningObjectVersion: lo.version, - }; - }); + })); return this.findAll({ where: { $or: objectIdentifiers }, diff --git a/backend/src/data/users/student-repository.ts b/backend/src/data/users/student-repository.ts index d3be9081..f3cf7b6a 100644 --- a/backend/src/data/users/student-repository.ts +++ b/backend/src/data/users/student-repository.ts @@ -1,9 +1,9 @@ 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'; +// Import { UserRepository } from './user-repository.js'; -// export class StudentRepository extends UserRepository {} +// Export class StudentRepository extends UserRepository {} export class StudentRepository 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 4992cca4..a1f53386 100644 --- a/backend/src/entities/assignments/assignment.entity.ts +++ b/backend/src/entities/assignments/assignment.entity.ts @@ -13,15 +13,11 @@ import { Language } from '../content/language.js'; import { AssignmentRepository } from '../../data/assignments/assignment-repository.js'; @Entity({ - repository: () => { - return AssignmentRepository; - }, + repository: () => AssignmentRepository, }) export class Assignment { @ManyToOne({ - entity: () => { - return Class; - }, + entity: () => Class, primary: true, }) within!: Class; @@ -39,16 +35,12 @@ export class Assignment { learningPathHruid!: string; @Enum({ - items: () => { - return Language; - }, + items: () => Language, }) learningPathLanguage!: Language; @OneToMany({ - entity: () => { - return Group; - }, + entity: () => Group, mappedBy: 'assignment', }) groups!: Group[]; diff --git a/backend/src/entities/assignments/group.entity.ts b/backend/src/entities/assignments/group.entity.ts index a961ed53..213e0f38 100644 --- a/backend/src/entities/assignments/group.entity.ts +++ b/backend/src/entities/assignments/group.entity.ts @@ -4,9 +4,7 @@ import { Student } from '../users/student.entity.js'; import { GroupRepository } from '../../data/assignments/group-repository.js'; @Entity({ - repository: () => { - return GroupRepository; - }, + repository: () => GroupRepository, }) export class Group { @ManyToOne({ diff --git a/backend/src/entities/classes/class-join-request.entity.ts b/backend/src/entities/classes/class-join-request.entity.ts index bf7e6355..bdef1f52 100644 --- a/backend/src/entities/classes/class-join-request.entity.ts +++ b/backend/src/entities/classes/class-join-request.entity.ts @@ -4,9 +4,7 @@ import { Class } from './class.entity.js'; import { ClassJoinRequestRepository } from '../../data/classes/class-join-request-repository.js'; @Entity({ - repository: () => { - return ClassJoinRequestRepository; - }, + repository: () => ClassJoinRequestRepository, }) export class ClassJoinRequest { @ManyToOne({ diff --git a/backend/src/entities/classes/class.entity.ts b/backend/src/entities/classes/class.entity.ts index a2c016ef..a484deff 100644 --- a/backend/src/entities/classes/class.entity.ts +++ b/backend/src/entities/classes/class.entity.ts @@ -11,9 +11,7 @@ import { Student } from '../users/student.entity.js'; import { ClassRepository } from '../../data/classes/class-repository.js'; @Entity({ - repository: () => { - return ClassRepository; - }, + repository: () => ClassRepository, }) export class Class { @PrimaryKey() diff --git a/backend/src/entities/content/attachment.entity.ts b/backend/src/entities/content/attachment.entity.ts index 3903f055..80104f28 100644 --- a/backend/src/entities/content/attachment.entity.ts +++ b/backend/src/entities/content/attachment.entity.ts @@ -3,15 +3,11 @@ import { LearningObject } from './learning-object.entity.js'; import { AttachmentRepository } from '../../data/content/attachment-repository.js'; @Entity({ - repository: () => { - return AttachmentRepository; - }, + repository: () => AttachmentRepository, }) export class Attachment { @ManyToOne({ - entity: () => { - return LearningObject; - }, + entity: () => LearningObject, primary: true, }) learningObject!: LearningObject; diff --git a/backend/src/interfaces/class.ts b/backend/src/interfaces/class.ts index 379a8635..e04ce2d4 100644 --- a/backend/src/interfaces/class.ts +++ b/backend/src/interfaces/class.ts @@ -21,12 +21,8 @@ export function mapToClassDTO(cls: Class): ClassDTO { return { id: cls.classId!, displayName: cls.displayName, - teachers: cls.teachers.map((teacher) => { - return teacher.username; - }), - students: cls.students.map((student) => { - return student.username; - }), + teachers: cls.teachers.map((teacher) => teacher.username), + students: cls.students.map((student) => student.username), joinRequests: [], // TODO }; } diff --git a/backend/src/interfaces/group.ts b/backend/src/interfaces/group.ts index 2dd3f2c1..a25c5b8e 100644 --- a/backend/src/interfaces/group.ts +++ b/backend/src/interfaces/group.ts @@ -20,8 +20,6 @@ export function mapToGroupDTOId(group: Group): GroupDTO { return { assignment: group.assignment.id!, groupNumber: group.groupNumber!, - members: group.members.map((member) => { - return member.username; - }), + members: group.members.map((member) => member.username), }; } diff --git a/backend/src/interfaces/submission.ts b/backend/src/interfaces/submission.ts index 2400075f..40b76fad 100644 --- a/backend/src/interfaces/submission.ts +++ b/backend/src/interfaces/submission.ts @@ -36,10 +36,10 @@ export function mapToSubmission(submissionDTO: SubmissionDTO): Submission { submission.learningObjectHruid = submissionDTO.learningObjectHruid; submission.learningObjectLanguage = submissionDTO.learningObjectLanguage; submission.learningObjectVersion = submissionDTO.learningObjectVersion; - // submission.submissionNumber = submissionDTO.submissionNumber; + // Submission.submissionNumber = submissionDTO.submissionNumber; submission.submitter = mapToStudent(submissionDTO.submitter) ; - // submission.submissionTime = submissionDTO.time; - // submission.onBehalfOf = submissionDTO.group!; + // Submission.submissionTime = submissionDTO.time; + // Submission.onBehalfOf = submissionDTO.group!; // TODO fix group submission.content = submissionDTO.content; diff --git a/backend/src/routes/questions.ts b/backend/src/routes/questions.ts index d635a85e..d9a4dd9d 100644 --- a/backend/src/routes/questions.ts +++ b/backend/src/routes/questions.ts @@ -7,7 +7,7 @@ import { } from "../controllers/questions.js"; const router = express.Router({ mergeParams: true }); -// query language +// Query language // Root endpoint used to search objects router.get('/', getAllQuestionsHandler); diff --git a/backend/src/services/class.ts b/backend/src/services/class.ts index a7f5f7ea..4b76d0cd 100644 --- a/backend/src/services/class.ts +++ b/backend/src/services/class.ts @@ -29,9 +29,7 @@ export async function getAllClasses( if (full) { return classes.map(mapToClassDTO); } - return classes.map((cls) => { - return cls.classId!; - }); + return classes.map((cls) => cls.classId!); } export async function createClass(classData: ClassDTO): Promise { @@ -45,7 +43,7 @@ export async function createClass(classData: ClassDTO): Promise { const students = (await Promise.all(studentUsernames.map(id => studentRepository.findByUsername(id)))) .filter(student => student != null); - //const cls = mapToClass(classData, teachers, students); + //Const cls = mapToClass(classData, teachers, students); const classRepository = getClassRepository(); @@ -91,9 +89,7 @@ export async function getClassStudents(classId: string): Promise { export async function getClassStudentsIds(classId: string): Promise { const students: StudentDTO[] = await fetchClassStudents(classId); - return students.map((student) => { - return student.username; - }); + return students.map((student) => student.username); } export async function getClassTeacherInvitations( diff --git a/backend/src/services/learning-objects.ts b/backend/src/services/learning-objects.ts index 4f7d66a8..dfde4da7 100644 --- a/backend/src/services/learning-objects.ts +++ b/backend/src/services/learning-objects.ts @@ -85,23 +85,15 @@ async function fetchLearningObjects( const nodes: LearningObjectNode[] = learningPathResponse.data[0].nodes; if (!full) { - return nodes.map((node) => { - return node.learningobject_hruid; - }); + return nodes.map((node) => node.learningobject_hruid); } return await Promise.all( - nodes.map(async (node) => { - return getLearningObjectById( + nodes.map(async (node) => getLearningObjectById( node.learningobject_hruid, language - ); - }) - ).then((objects) => { - return objects.filter((obj): obj is FilteredLearningObject => { - return obj !== null; - }); - }); + )) + ).then((objects) => objects.filter((obj): obj is FilteredLearningObject => obj !== null)); } catch (error) { console.error('❌ Error fetching learning objects:', error); return []; diff --git a/backend/src/services/questions.ts b/backend/src/services/questions.ts index 6be684f9..238ebd45 100644 --- a/backend/src/services/questions.ts +++ b/backend/src/services/questions.ts @@ -45,7 +45,7 @@ export async function getQuestion(questionId: QuestionId): Promise { export async function getAllStudentIds(): Promise { const users = await getAllStudents(); - return users.map((user) => { - return user.username; - }); + return users.map((user) => user.username); } export async function getStudent(username: string): Promise { @@ -98,9 +96,7 @@ export async function getStudentAssignments(username: string, full: boolean): Pr const assignments = ( await Promise.all( - classes.map(async (cls) => { - return await getAllAssignments(cls.classId!, full); - }) + classes.map(async (cls) => await getAllAssignments(cls.classId!, full)) ) ).flat(); diff --git a/backend/src/services/submissions.ts b/backend/src/services/submissions.ts index ce6f7bff..c9135001 100644 --- a/backend/src/services/submissions.ts +++ b/backend/src/services/submissions.ts @@ -46,7 +46,7 @@ export async function deleteSubmission( const submission = getSubmission(learningObjectHruid, language, version, submissionNumber); if (!submission) - return null + {return null} const loId = new LearningObjectIdentifier(learningObjectHruid, language, version); await submissionRepository.deleteSubmissionByLearningObjectAndSubmissionNumber(loId, submissionNumber); diff --git a/backend/src/services/teachers.ts b/backend/src/services/teachers.ts index 12325857..2f462ed9 100644 --- a/backend/src/services/teachers.ts +++ b/backend/src/services/teachers.ts @@ -27,9 +27,7 @@ export async function getAllTeachers(): Promise { export async function getAllTeacherIds(): Promise { const users = await getAllTeachers(); - return users.map((user) => { - return user.username; - }); + return users.map((user) => user.username); } export async function getTeacher(username: string): Promise { @@ -89,9 +87,7 @@ export async function getClassesByTeacher(username: string): Promise export async function getClassIdsByTeacher(username: string): Promise { const classes = await fetchClassesByTeacher(username); - return classes.map((cls) => { - return cls.id; - }); + return classes.map((cls) => cls.id); } export async function fetchStudentsByTeacher(username: string) { @@ -99,9 +95,7 @@ export async function fetchStudentsByTeacher(username: string) { return ( await Promise.all( - classes.map(async (id) => { - return getClassStudents(id); - }) + classes.map(async (id) => getClassStudents(id)) ) ).flat(); } @@ -112,9 +106,7 @@ export async function getStudentsByTeacher(username: string): Promise { const students = await fetchStudentsByTeacher(username); - return students.map((student) => { - return student.username; - }); + return students.map((student) => student.username); } export async function fetchTeacherQuestions(username: string): Promise { diff --git a/backend/src/services/users.ts b/backend/src/services/users.ts index fc1ea599..65ed5d4c 100644 --- a/backend/src/services/users.ts +++ b/backend/src/services/users.ts @@ -16,9 +16,7 @@ export class UserService { async getAllUserIds(): Promise { const users = await this.getAllUsers(); - return users.map((user) => { - return user.username; - }); + return users.map((user) => user.username); } async getUserByUsername(username: string): Promise { From 400a955850e66dd5bbf00af548a40fb8c150e785 Mon Sep 17 00:00:00 2001 From: Lint Action Date: Thu, 13 Mar 2025 17:45:32 +0000 Subject: [PATCH 083/106] style: fix linting issues met Prettier --- backend/src/config.ts | 2 +- backend/src/controllers/assignments.ts | 28 +--- backend/src/controllers/classes.ts | 42 ++---- backend/src/controllers/groups.ts | 26 +--- backend/src/controllers/questions.ts | 120 +++++++++--------- backend/src/controllers/students.ts | 52 ++------ backend/src/controllers/submissions.ts | 37 +++--- backend/src/controllers/teachers.ts | 66 ++++------ backend/src/controllers/users.ts | 29 +---- .../src/data/assignments/group-repository.ts | 23 +--- .../data/assignments/submission-repository.ts | 36 ++---- backend/src/data/classes/class-repository.ts | 10 +- .../src/data/questions/answer-repository.ts | 5 +- .../src/data/questions/question-repository.ts | 21 +-- backend/src/data/repositories.ts | 4 +- backend/src/data/users/student-repository.ts | 2 +- backend/src/data/users/teacher-repository.ts | 2 +- .../entities/assignments/assignment.entity.ts | 10 +- backend/src/entities/classes/class.entity.ts | 8 +- backend/src/entities/content/language.ts | 2 +- .../content/learning-object.entity.ts | 11 +- backend/src/interfaces/answer.ts | 10 +- backend/src/interfaces/class.ts | 8 +- backend/src/interfaces/question.ts | 9 +- backend/src/interfaces/student.ts | 6 +- backend/src/interfaces/submission.ts | 32 ++--- backend/src/interfaces/teacher-invitation.ts | 8 +- backend/src/interfaces/teacher.ts | 6 +- backend/src/interfaces/user.ts | 5 +- backend/src/routes/learning-objects.ts | 10 +- backend/src/routes/questions.ts | 7 +- backend/src/routes/submissions.ts | 4 +- backend/src/services/assignments.ts | 47 ++----- backend/src/services/class.ts | 38 ++---- backend/src/services/groups.ts | 63 ++------- backend/src/services/learning-objects.ts | 65 ++-------- backend/src/services/questions.ts | 87 ++++++------- backend/src/services/students.ts | 28 ++-- backend/src/services/submissions.ts | 26 ++-- backend/src/services/teachers.ts | 26 +--- 40 files changed, 321 insertions(+), 700 deletions(-) diff --git a/backend/src/config.ts b/backend/src/config.ts index 65582c8b..69af5d74 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -1,5 +1,5 @@ import { EnvVars, getEnvVar } from './util/envvars.js'; -import {Language} from "./entities/content/language.js"; +import { Language } from './entities/content/language.js'; // API export const DWENGO_API_BASE = getEnvVar(EnvVars.LearningContentRepoApiBaseUrl); diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index dcaa07a2..b0695b85 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -9,10 +9,7 @@ interface AssignmentParams { id: string; } -export async function getAllAssignmentsHandler( - req: Request, - res: Response -): Promise { +export async function getAllAssignmentsHandler(req: Request, res: Response): Promise { const classid = req.params.classid; const full = req.query.full === 'true'; @@ -23,18 +20,11 @@ export async function getAllAssignmentsHandler( }); } -export async function createAssignmentHandler( - req: Request, - res: Response, -): Promise { +export async function createAssignmentHandler(req: Request, res: Response): Promise { const classid = req.params.classid; const assignmentData = req.body as AssignmentDTO; - if (!assignmentData.description - || !assignmentData.language - || !assignmentData.learningPath - || !assignmentData.title - ) { + if (!assignmentData.description || !assignmentData.language || !assignmentData.learningPath || !assignmentData.title) { res.status(400).json({ error: 'Missing one or more required fields: title, description, learningPath, title', }); @@ -44,17 +34,14 @@ export async function createAssignmentHandler( const assignment = createAssignment(classid, assignmentData); if (!assignment) { - res.status(500).json({ error: "Could not create assignment "}); + res.status(500).json({ error: 'Could not create assignment ' }); return; } res.status(201).json({ assignment: assignment }); } -export async function getAssignmentHandler( - req: Request, - res: Response -): Promise { +export async function getAssignmentHandler(req: Request, res: Response): Promise { const id = +req.params.id; const classid = req.params.classid; @@ -73,10 +60,7 @@ export async function getAssignmentHandler( res.json(assignment); } -export async function getAssignmentsSubmissionsHandler( - req: Request, - res: Response, -): Promise { +export async function getAssignmentsSubmissionsHandler(req: Request, res: Response): Promise { const classid = req.params.classid; const assignmentNumber = +req.params.id; diff --git a/backend/src/controllers/classes.ts b/backend/src/controllers/classes.ts index 5fd4ce9d..6fb58fb9 100644 --- a/backend/src/controllers/classes.ts +++ b/backend/src/controllers/classes.ts @@ -1,19 +1,9 @@ import { Request, Response } from 'express'; -import { - createClass, - getAllClasses, - getClass, - getClassStudents, - getClassStudentsIds, - getClassTeacherInvitations, -} from '../services/class.js'; +import { createClass, getAllClasses, getClass, getClassStudents, getClassStudentsIds, getClassTeacherInvitations } from '../services/class.js'; import { ClassDTO, mapToClass } from '../interfaces/class.js'; import { getClassRepository, getStudentRepository, getTeacherRepository } from '../data/repositories.js'; -export async function getAllClassesHandler( - req: Request, - res: Response -): Promise { +export async function getAllClassesHandler(req: Request, res: Response): Promise { const full = req.query.full === 'true'; const classes = await getAllClasses(full); @@ -22,10 +12,7 @@ export async function getAllClassesHandler( }); } -export async function createClassHandler( - req: Request, - res: Response, -): Promise { +export async function createClassHandler(req: Request, res: Response): Promise { const classData = req.body as ClassDTO; if (!classData.displayName) { @@ -38,17 +25,14 @@ export async function createClassHandler( const cls = await createClass(classData); if (!cls) { - res.status(500).json({ error: "Something went wrong while creating class" }); - return + res.status(500).json({ error: 'Something went wrong while creating class' }); + return; } res.status(201).json({ class: cls }); } -export async function getClassHandler( - req: Request, - res: Response -): Promise { +export async function getClassHandler(req: Request, res: Response): Promise { try { const classId = req.params.id; const cls = await getClass(classId); @@ -71,26 +55,18 @@ export async function getClassHandler( } } -export async function getClassStudentsHandler( - req: Request, - res: Response -): Promise { +export async function getClassStudentsHandler(req: Request, res: Response): Promise { const classId = req.params.id; const full = req.query.full === 'true'; - const students = full - ? await getClassStudents(classId) - : await getClassStudentsIds(classId); + const students = full ? await getClassStudents(classId) : await getClassStudentsIds(classId); res.json({ students: students, }); } -export async function getTeacherInvitationsHandler( - req: Request, - res: Response -): Promise { +export async function getTeacherInvitationsHandler(req: Request, res: Response): Promise { const classId = req.params.id; const full = req.query.full === 'true'; // TODO: not implemented yet diff --git a/backend/src/controllers/groups.ts b/backend/src/controllers/groups.ts index 9ef5b93c..dbe83060 100644 --- a/backend/src/controllers/groups.ts +++ b/backend/src/controllers/groups.ts @@ -9,10 +9,7 @@ interface GroupParams { groupid?: string; } -export async function getGroupHandler( - req: Request, - res: Response -): Promise { +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; @@ -34,10 +31,7 @@ export async function getGroupHandler( res.json(group); } -export async function getAllGroupsHandler( - req: Request, - res: Response -): Promise { +export async function getAllGroupsHandler(req: Request, res: Response): Promise { const classId = req.params.classid; const full = req.query.full === 'true'; @@ -55,10 +49,7 @@ export async function getAllGroupsHandler( }); } -export async function createGroupHandler( - req: Request, - res: Response, -): Promise { +export async function createGroupHandler(req: Request, res: Response): Promise { const classid = req.params.classid; const assignmentId = +req.params.assignmentid; @@ -71,17 +62,14 @@ export async function createGroupHandler( const group = createGroup(groupData, classid, assignmentId); if (!group) { - res.status(500).json({ error: "Something went wrong while creating group" }); - return + res.status(500).json({ error: 'Something went wrong while creating group' }); + return; } res.status(201).json({ group: group }); } -export async function getGroupSubmissionsHandler( - req: Request, - res: Response, -): Promise { +export async function getGroupSubmissionsHandler(req: Request, res: Response): Promise { const classId = req.params.classid; // Const full = req.query.full === 'true'; @@ -104,4 +92,4 @@ export async function getGroupSubmissionsHandler( res.json({ submissions: submissions, }); -} \ No newline at end of file +} diff --git a/backend/src/controllers/questions.ts b/backend/src/controllers/questions.ts index b4451aa2..917b48ae 100644 --- a/backend/src/controllers/questions.ts +++ b/backend/src/controllers/questions.ts @@ -1,121 +1,119 @@ -import {Request, Response} from "express"; -import { - createQuestion, - deleteQuestion, - getAllQuestions, - getAnswersByQuestion, - getQuestion -} from "../services/questions.js"; -import {QuestionDTO, QuestionId} from "../interfaces/question.js"; -import {FALLBACK_LANG, FALLBACK_SEQ_NUM} from "../config.js"; -import {LearningObjectIdentifier} from "../entities/content/learning-object-identifier.js"; -import {Language} from "../entities/content/language.js"; +import { Request, Response } from 'express'; +import { createQuestion, deleteQuestion, getAllQuestions, getAnswersByQuestion, getQuestion } from '../services/questions.js'; +import { QuestionDTO, QuestionId } from '../interfaces/question.js'; +import { FALLBACK_LANG, FALLBACK_SEQ_NUM } from '../config.js'; +import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; +import { Language } from '../entities/content/language.js'; function getObjectId(req: Request, res: Response): LearningObjectIdentifier | null { - const { hruid, version } = req.params - const lang = req.query.lang + const { hruid, version } = req.params; + const lang = req.query.lang; - if (!hruid || !version ) { - res.status(400).json({ error: "Missing required parameters." }); + if (!hruid || !version) { + res.status(400).json({ error: 'Missing required parameters.' }); return null; } return { hruid, - language: lang as Language || FALLBACK_LANG, - version: +version - } + language: (lang as Language) || FALLBACK_LANG, + version: +version, + }; } function getQuestionId(req: Request, res: Response): QuestionId | null { - const seq = req.params.seq - const learningObjectIdentifier = getObjectId(req,res); + const seq = req.params.seq; + const learningObjectIdentifier = getObjectId(req, res); - if (!learningObjectIdentifier) - {return null} + if (!learningObjectIdentifier) { + return null; + } return { learningObjectIdentifier, - sequenceNumber: seq ? Number(seq) : FALLBACK_SEQ_NUM - } + sequenceNumber: seq ? Number(seq) : FALLBACK_SEQ_NUM, + }; } -export async function getAllQuestionsHandler( - req: Request, - res: Response -): Promise { +export async function getAllQuestionsHandler(req: Request, res: Response): Promise { const objectId = getObjectId(req, res); const full = req.query.full === 'true'; - if (!objectId) - {return} + if (!objectId) { + return; + } const questions = await getAllQuestions(objectId, full); - if (!questions) - {res.status(404).json({ error: `Questions not found.` });} - else - {res.json(questions);} + if (!questions) { + res.status(404).json({ error: `Questions not found.` }); + } else { + res.json(questions); + } } export async function getQuestionHandler(req: Request, res: Response): Promise { const questionId = getQuestionId(req, res); - if (!questionId) - {return} + if (!questionId) { + return; + } const question = await getQuestion(questionId); - if (!question) - {res.status(404).json({ error: `Question not found.` });} - else - {res.json(question)} + if (!question) { + res.status(404).json({ error: `Question not found.` }); + } else { + res.json(question); + } } export async function getQuestionAnswersHandler(req: Request, res: Response): Promise { const questionId = getQuestionId(req, res); const full = req.query.full === 'true'; - if (!questionId) - {return} + if (!questionId) { + return; + } const answers = getAnswersByQuestion(questionId, full); - if (!answers) - {res.status(404).json({ error: `Questions not found.` });} - else - {res.json(answers)} + if (!answers) { + res.status(404).json({ error: `Questions not found.` }); + } else { + res.json(answers); + } } export async function createQuestionHandler(req: Request, res: Response): Promise { const questionDTO = req.body as QuestionDTO; if (!questionDTO.learningObjectIdentifier || !questionDTO.author || !questionDTO.content) { - res.status(400).json({error: 'Missing required fields: identifier and content'}); + res.status(400).json({ error: 'Missing required fields: identifier and content' }); return; } const question = await createQuestion(questionDTO); - if (!question) - {res.status(400).json({error: 'Could not add question'});} - else - {res.json(question)} + if (!question) { + res.status(400).json({ error: 'Could not add question' }); + } else { + res.json(question); + } } export async function deleteQuestionHandler(req: Request, res: Response): Promise { const questionId = getQuestionId(req, res); - if (!questionId) - {return} + if (!questionId) { + return; + } const question = await deleteQuestion(questionId); - if (!question) - {res.status(400).json({error: 'Could not find nor delete question'});} - else - {res.json(question)} + if (!question) { + res.status(400).json({ error: 'Could not find nor delete question' }); + } else { + res.json(question); + } } - - - diff --git a/backend/src/controllers/students.ts b/backend/src/controllers/students.ts index aae37b50..5c17513d 100644 --- a/backend/src/controllers/students.ts +++ b/backend/src/controllers/students.ts @@ -10,9 +10,7 @@ import { } from '../services/students.js'; import { ClassDTO } from '../interfaces/class.js'; import { getAllAssignments } from '../services/assignments.js'; -import { - getUserHandler, -} from './users.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'; @@ -20,17 +18,12 @@ import { UserDTO } from '../interfaces/user.js'; // TODO: accept arguments (full, ...) // TODO: endpoints -export async function getAllStudentsHandler( - req: Request, - res: Response, -): Promise { +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 getAllStudents(); if (!students) { res.status(404).json({ error: `Student not found.` }); @@ -40,11 +33,7 @@ export async function getAllStudentsHandler( res.status(201).json(students); } - -export async function getStudentHandler( - req: Request, - res: Response, -): Promise { +export async function getStudentHandler(req: Request, res: Response): Promise { const username = req.params.username; if (!username) { @@ -64,10 +53,7 @@ export async function getStudentHandler( res.status(201).json(user); } -export async function createStudentHandler( - req: Request, - res: Response, -) { +export async function createStudentHandler(req: Request, res: Response) { const userData = req.body as StudentDTO; if (!userData.username || !userData.firstName || !userData.lastName) { @@ -81,10 +67,7 @@ export async function createStudentHandler( res.status(201).json(newUser); } -export async function deleteStudentHandler( - req: Request, - res: Response, -) { +export async function deleteStudentHandler(req: Request, res: Response) { const username = req.params.username; if (!username) { @@ -103,10 +86,7 @@ export async function deleteStudentHandler( res.status(200).json(deletedUser); } -export async function getStudentClassesHandler( - req: Request, - res: Response -): Promise { +export async function getStudentClassesHandler(req: Request, res: Response): Promise { try { const full = req.query.full === 'true'; const username = req.params.id; @@ -132,10 +112,7 @@ export async function getStudentClassesHandler( // Might not be fully correct depending on if // A class has an assignment, that all students // Have this assignment. -export async function getStudentAssignmentsHandler( - req: Request, - res: Response -): Promise { +export async function getStudentAssignmentsHandler(req: Request, res: Response): Promise { const full = req.query.full === 'true'; const username = req.params.id; @@ -146,24 +123,18 @@ export async function getStudentAssignmentsHandler( }); } -export async function getStudentGroupsHandler( - req: Request, - res: Response, -): Promise { +export async function getStudentGroupsHandler(req: Request, res: Response): Promise { const full = req.query.full === 'true'; const username = req.params.id; const groups = await getStudentGroups(username, full); - + res.json({ groups: groups, }); } -export async function getStudentSubmissionsHandler( - req: Request, - res: Response, -): Promise { +export async function getStudentSubmissionsHandler(req: Request, res: Response): Promise { const username = req.params.id; const submissions = await getStudentSubmissions(username); @@ -175,4 +146,3 @@ export async function getStudentSubmissionsHandler( function getAllStudents(): StudentDTO[] | string[] | PromiseLike { throw new Error('Function not implemented.'); } - diff --git a/backend/src/controllers/submissions.ts b/backend/src/controllers/submissions.ts index 96f0185c..1e66dbe9 100644 --- a/backend/src/controllers/submissions.ts +++ b/backend/src/controllers/submissions.ts @@ -1,17 +1,14 @@ -import { Request, Response } from "express"; -import {createSubmission, deleteSubmission, getSubmission} from "../services/submissions.js"; -import { Language, languageMap } from "../entities/content/language.js"; -import {SubmissionDTO} from "../interfaces/submission"; +import { Request, Response } from 'express'; +import { createSubmission, deleteSubmission, getSubmission } from '../services/submissions.js'; +import { Language, languageMap } from '../entities/content/language.js'; +import { SubmissionDTO } from '../interfaces/submission'; interface SubmissionParams { - hruid: string, + hruid: string; id: number; } -export async function getSubmissionHandler( - req: Request, - res: Response, -): Promise { +export async function getSubmissionHandler(req: Request, res: Response): Promise { const lohruid = req.params.hruid; const submissionNumber = +req.params.id; @@ -33,18 +30,19 @@ export async function getSubmissionHandler( res.json(submission); } -export async function createSubmissionHandler(req: Request, res: Response){ +export async function createSubmissionHandler(req: Request, res: Response) { const submissionDTO = req.body as SubmissionDTO; const submission = await createSubmission(submissionDTO); - if (!submission) - {res.status(404).json({ error: 'Submission not added' });} - else - {res.json(submission)} + if (!submission) { + res.status(404).json({ error: 'Submission not added' }); + } else { + res.json(submission); + } } -export async function deleteSubmissionHandler(req: Request, res: Response){ +export async function deleteSubmissionHandler(req: Request, res: Response) { const hruid = req.params.hruid; const submissionNumber = +req.params.id; @@ -53,8 +51,9 @@ export async function deleteSubmissionHandler(req: Request, res: Response){ const submission = await deleteSubmission(hruid, lang, version, submissionNumber); - if (!submission) - {res.status(404).json({ error: 'Submission not found' });} - else - {res.json(submission)} + if (!submission) { + res.status(404).json({ error: 'Submission not found' }); + } else { + res.json(submission); + } } diff --git a/backend/src/controllers/teachers.ts b/backend/src/controllers/teachers.ts index 02846fd9..52e5e713 100644 --- a/backend/src/controllers/teachers.ts +++ b/backend/src/controllers/teachers.ts @@ -1,5 +1,16 @@ import { Request, Response } from 'express'; -import { createTeacher, deleteTeacher, getAllTeachers, getClassesByTeacher, getClassIdsByTeacher, getQuestionIdsByTeacher, getQuestionsByTeacher, getStudentIdsByTeacher, getStudentsByTeacher, getTeacher } from '../services/teachers.js'; +import { + createTeacher, + 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'; @@ -7,17 +18,12 @@ import { Teacher } from '../entities/users/teacher.entity.js'; import { TeacherDTO } from '../interfaces/teacher.js'; import { getTeacherRepository } from '../data/repositories.js'; -export async function getAllTeachersHandler( - req: Request, - res: Response, -): Promise { +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[] = full ? await getAllTeachers() : await getAllTeachers(); if (!teachers) { res.status(404).json({ error: `Teacher not found.` }); @@ -27,11 +33,7 @@ export async function getAllTeachersHandler( res.status(201).json(teachers); } - -export async function getTeacherHandler( - req: Request, - res: Response, -): Promise { +export async function getTeacherHandler(req: Request, res: Response): Promise { const username = req.params.username; if (!username) { @@ -51,10 +53,7 @@ export async function getTeacherHandler( res.status(201).json(user); } -export async function createTeacherHandler( - req: Request, - res: Response, -) { +export async function createTeacherHandler(req: Request, res: Response) { const userData = req.body as TeacherDTO; if (!userData.username || !userData.firstName || !userData.lastName) { @@ -68,10 +67,7 @@ export async function createTeacherHandler( res.status(201).json(newUser); } -export async function deleteTeacherHandler( - req: Request, - res: Response, -) { +export async function deleteTeacherHandler(req: Request, res: Response) { const username = req.params.username; if (!username) { @@ -90,11 +86,7 @@ export async function deleteTeacherHandler( res.status(200).json(deletedUser); } - -export async function getTeacherClassHandler( - req: Request, - res: Response -): Promise { +export async function getTeacherClassHandler(req: Request, res: Response): Promise { try { const username = req.params.username as string; const full = req.query.full === 'true'; @@ -104,9 +96,7 @@ export async function getTeacherClassHandler( return; } - const classes: ClassDTO[] | string[] = full - ? await getClassesByTeacher(username) - : await getClassIdsByTeacher(username); + const classes: ClassDTO[] | string[] = full ? await getClassesByTeacher(username) : await getClassIdsByTeacher(username); res.status(201).json(classes); } catch (error) { @@ -115,10 +105,7 @@ export async function getTeacherClassHandler( } } -export async function getTeacherStudentHandler( - req: Request, - res: Response -): Promise { +export async function getTeacherStudentHandler(req: Request, res: Response): Promise { try { const username = req.params.username as string; const full = req.query.full === 'true'; @@ -128,9 +115,7 @@ export async function getTeacherStudentHandler( return; } - const students: StudentDTO[] | string[] = full - ? await getStudentsByTeacher(username) - : await getStudentIdsByTeacher(username); + const students: StudentDTO[] | string[] = full ? await getStudentsByTeacher(username) : await getStudentIdsByTeacher(username); res.status(201).json(students); } catch (error) { @@ -139,10 +124,7 @@ export async function getTeacherStudentHandler( } } -export async function getTeacherQuestionHandler( - req: Request, - res: Response -): Promise { +export async function getTeacherQuestionHandler(req: Request, res: Response): Promise { try { const username = req.params.username as string; const full = req.query.full === 'true'; @@ -152,9 +134,7 @@ export async function getTeacherQuestionHandler( return; } - const questions: QuestionDTO[] | QuestionId[] = full - ? await getQuestionsByTeacher(username) - : await getQuestionIdsByTeacher(username); + const questions: QuestionDTO[] | QuestionId[] = full ? await getQuestionsByTeacher(username) : await getQuestionIdsByTeacher(username); res.status(201).json(questions); } catch (error) { diff --git a/backend/src/controllers/users.ts b/backend/src/controllers/users.ts index 8b256a0e..850c6549 100644 --- a/backend/src/controllers/users.ts +++ b/backend/src/controllers/users.ts @@ -3,17 +3,11 @@ import { UserService } from '../services/users.js'; import { UserDTO } from '../interfaces/user.js'; import { User } from '../entities/users/user.entity.js'; -export async function getAllUsersHandler( - req: Request, - res: Response, - service: UserService -): Promise { +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(); + const users: UserDTO[] | string[] = full ? await service.getAllUsers() : await service.getAllUserIds(); if (!users) { res.status(404).json({ error: `Users not found.` }); @@ -27,11 +21,7 @@ export async function getAllUsersHandler( } } -export async function getUserHandler( - req: Request, - res: Response, - service: UserService -): Promise { +export async function getUserHandler(req: Request, res: Response, service: UserService): Promise { try { const username = req.params.username as string; @@ -56,12 +46,7 @@ export async function getUserHandler( } } -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 { console.log('req', req); const userData = req.body as UserDTO; @@ -81,11 +66,7 @@ export async function createUserHandler( } } -export async function deleteUserHandler( - req: Request, - res: Response, - service: UserService -) { +export async function deleteUserHandler(req: Request, res: Response, service: UserService) { try { const username = req.params.username; diff --git a/backend/src/data/assignments/group-repository.ts b/backend/src/data/assignments/group-repository.ts index 56736787..eb1b09e2 100644 --- a/backend/src/data/assignments/group-repository.ts +++ b/backend/src/data/assignments/group-repository.ts @@ -4,10 +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 findByAssignmentAndGroupNumber(assignment: Assignment, groupNumber: number): Promise { return this.findOne( { assignment: assignment, @@ -16,26 +13,16 @@ export class GroupRepository extends DwengoEntityRepository { { populate: ['members'] } ); } - public findAllGroupsForAssignment( - assignment: Assignment - ): Promise { + public findAllGroupsForAssignment(assignment: Assignment): Promise { return this.findAll({ where: { assignment: assignment }, populate: ['members'], }); } - public findAllGroupsWithStudent( - student: Student - ): Promise { - return this.find( - { members: student }, - { populate: ['members'] } - ) + public findAllGroupsWithStudent(student: Student): Promise { + return this.find({ members: student }, { populate: ['members'] }); } - public deleteByAssignmentAndGroupNumber( - assignment: Assignment, - groupNumber: number - ) { + public deleteByAssignmentAndGroupNumber(assignment: Assignment, groupNumber: number) { 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 6ec1217f..251823fa 100644 --- a/backend/src/data/assignments/submission-repository.ts +++ b/backend/src/data/assignments/submission-repository.ts @@ -5,10 +5,7 @@ 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 findSubmissionByLearningObjectAndSubmissionNumber(loId: LearningObjectIdentifier, submissionNumber: number): Promise { return this.findOne({ learningObjectHruid: loId.hruid, learningObjectLanguage: loId.language, @@ -17,10 +14,7 @@ export class SubmissionRepository extends DwengoEntityRepository { }); } - public findMostRecentSubmissionForStudent( - loId: LearningObjectIdentifier, - submitter: Student - ): Promise { + public findMostRecentSubmissionForStudent(loId: LearningObjectIdentifier, submitter: Student): Promise { return this.findOne( { learningObjectHruid: loId.hruid, @@ -32,10 +26,7 @@ export class SubmissionRepository extends DwengoEntityRepository { ); } - public findMostRecentSubmissionForGroup( - loId: LearningObjectIdentifier, - group: Group - ): Promise { + public findMostRecentSubmissionForGroup(loId: LearningObjectIdentifier, group: Group): Promise { return this.findOne( { learningObjectHruid: loId.hruid, @@ -47,26 +38,15 @@ export class SubmissionRepository extends DwengoEntityRepository { ); } - public findAllSubmissionsForGroup( - group: Group, - ): Promise { - return this.find( - { onBehalfOf: group }, - ); + public findAllSubmissionsForGroup(group: Group): Promise { + return this.find({ onBehalfOf: group }); } - public findAllSubmissionsForStudent( - student: Student, - ): Promise { - return this.find( - { submitter: student }, - ); + public findAllSubmissionsForStudent(student: Student): Promise { + return this.find({ submitter: student }); } - public deleteSubmissionByLearningObjectAndSubmissionNumber( - loId: LearningObjectIdentifier, - submissionNumber: number - ): Promise { + public deleteSubmissionByLearningObjectAndSubmissionNumber(loId: LearningObjectIdentifier, submissionNumber: number): Promise { return this.deleteWhere({ learningObjectHruid: loId.hruid, learningObjectLanguage: loId.language, diff --git a/backend/src/data/classes/class-repository.ts b/backend/src/data/classes/class-repository.ts index 4f2f5b0e..0ceed98e 100644 --- a/backend/src/data/classes/class-repository.ts +++ b/backend/src/data/classes/class-repository.ts @@ -5,10 +5,7 @@ import { Teacher } from '../../entities/users/teacher.entity'; export class ClassRepository extends DwengoEntityRepository { public findById(id: string): Promise { - return this.findOne( - { classId: id }, - { populate: ['students', 'teachers'] } - ); + return this.findOne({ classId: id }, { populate: ['students', 'teachers'] }); } public deleteById(id: string): Promise { return this.deleteWhere({ classId: id }); @@ -21,9 +18,6 @@ export class ClassRepository extends DwengoEntityRepository { } public findByTeacher(teacher: Teacher): Promise { - return this.find( - { teachers: teacher }, - { populate: ['students', 'teachers'] } - ); + return this.find({ teachers: teacher }, { populate: ['students', 'teachers'] }); } } diff --git a/backend/src/data/questions/answer-repository.ts b/backend/src/data/questions/answer-repository.ts index 0e20e479..a28342bd 100644 --- a/backend/src/data/questions/answer-repository.ts +++ b/backend/src/data/questions/answer-repository.ts @@ -19,10 +19,7 @@ export class AnswerRepository extends DwengoEntityRepository { orderBy: { sequenceNumber: 'ASC' }, }); } - public removeAnswerByQuestionAndSequenceNumber( - question: Question, - sequenceNumber: number - ): Promise { + public 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 e24d48b8..9207e1dd 100644 --- a/backend/src/data/questions/question-repository.ts +++ b/backend/src/data/questions/question-repository.ts @@ -21,9 +21,7 @@ export class QuestionRepository extends DwengoEntityRepository { questionEntity.content = question.content; return this.insert(questionEntity); } - public findAllQuestionsAboutLearningObject( - loId: LearningObjectIdentifier - ): Promise { + public findAllQuestionsAboutLearningObject(loId: LearningObjectIdentifier): Promise { return this.findAll({ where: { learningObjectHruid: loId.hruid, @@ -35,10 +33,7 @@ export class QuestionRepository extends DwengoEntityRepository { }, }); } - public removeQuestionByLearningObjectAndSequenceNumber( - loId: LearningObjectIdentifier, - sequenceNumber: number - ): Promise { + public removeQuestionByLearningObjectAndSequenceNumber(loId: LearningObjectIdentifier, sequenceNumber: number): Promise { return this.deleteWhere({ learningObjectHruid: loId.hruid, learningObjectLanguage: loId.language, @@ -47,14 +42,12 @@ export class QuestionRepository extends DwengoEntityRepository { }); } - public async findAllByLearningObjects( - learningObjects: LearningObject[] - ): Promise { + public async findAllByLearningObjects(learningObjects: LearningObject[]): Promise { const objectIdentifiers = learningObjects.map((lo) => ({ - learningObjectHruid: lo.hruid, - learningObjectLanguage: lo.language, - learningObjectVersion: lo.version, - })); + learningObjectHruid: lo.hruid, + learningObjectLanguage: lo.language, + learningObjectVersion: lo.version, + })); return this.findAll({ where: { $or: objectIdentifiers }, diff --git a/backend/src/data/repositories.ts b/backend/src/data/repositories.ts index 0c091aa9..02385109 100644 --- a/backend/src/data/repositories.ts +++ b/backend/src/data/repositories.ts @@ -40,9 +40,7 @@ export function transactional(f: () => Promise) { entityManager?.transactional(f); } -function repositoryGetter>( - entity: EntityName -): () => R { +function repositoryGetter>(entity: EntityName): () => R { let cachedRepo: R | undefined; return (): R => { if (!cachedRepo) { diff --git a/backend/src/data/users/student-repository.ts b/backend/src/data/users/student-repository.ts index f3cf7b6a..0792678d 100644 --- a/backend/src/data/users/student-repository.ts +++ b/backend/src/data/users/student-repository.ts @@ -12,4 +12,4 @@ export class StudentRepository extends DwengoEntityRepository { public deleteByUsername(username: string): Promise { return this.deleteWhere({ username: username }); } -} \ No newline at end of file +} diff --git a/backend/src/data/users/teacher-repository.ts b/backend/src/data/users/teacher-repository.ts index 23ba559e..2b2bee75 100644 --- a/backend/src/data/users/teacher-repository.ts +++ b/backend/src/data/users/teacher-repository.ts @@ -9,4 +9,4 @@ export class TeacherRepository extends DwengoEntityRepository { public deleteByUsername(username: string): Promise { return this.deleteWhere({ username: username }); } -} \ No newline at end of file +} diff --git a/backend/src/entities/assignments/assignment.entity.ts b/backend/src/entities/assignments/assignment.entity.ts index a1f53386..692e2112 100644 --- a/backend/src/entities/assignments/assignment.entity.ts +++ b/backend/src/entities/assignments/assignment.entity.ts @@ -1,12 +1,4 @@ -import { - Collection, - Entity, - Enum, - ManyToOne, - OneToMany, - PrimaryKey, - Property, -} from '@mikro-orm/core'; +import { Collection, 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/classes/class.entity.ts b/backend/src/entities/classes/class.entity.ts index a484deff..63315304 100644 --- a/backend/src/entities/classes/class.entity.ts +++ b/backend/src/entities/classes/class.entity.ts @@ -1,10 +1,4 @@ -import { - Collection, - Entity, - ManyToMany, - PrimaryKey, - Property, -} from '@mikro-orm/core'; +import { Collection, Entity, ManyToMany, PrimaryKey, Property } from '@mikro-orm/core'; import { v4 } from 'uuid'; import { Teacher } from '../users/teacher.entity.js'; import { Student } from '../users/student.entity.js'; diff --git a/backend/src/entities/content/language.ts b/backend/src/entities/content/language.ts index 40a00919..7e7b42d2 100644 --- a/backend/src/entities/content/language.ts +++ b/backend/src/entities/content/language.ts @@ -190,4 +190,4 @@ export const languageMap: Record = { fr: Language.French, en: Language.English, de: Language.German, -}; \ No newline at end of file +}; diff --git a/backend/src/entities/content/learning-object.entity.ts b/backend/src/entities/content/learning-object.entity.ts index 4a3641bb..9eda22ba 100644 --- a/backend/src/entities/content/learning-object.entity.ts +++ b/backend/src/entities/content/learning-object.entity.ts @@ -1,13 +1,4 @@ -import { - Embeddable, - Embedded, - Entity, - Enum, - ManyToMany, - OneToMany, - PrimaryKey, - Property, -} from '@mikro-orm/core'; +import { Embeddable, Embedded, Entity, Enum, ManyToMany, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; import { Language } from './language.js'; import { Attachment } from './attachment.entity.js'; import { Teacher } from '../users/teacher.entity.js'; diff --git a/backend/src/interfaces/answer.ts b/backend/src/interfaces/answer.ts index 0b86a4d1..493fd3c0 100644 --- a/backend/src/interfaces/answer.ts +++ b/backend/src/interfaces/answer.ts @@ -1,6 +1,6 @@ -import {mapToUserDTO, UserDTO} from "./user.js"; -import {mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId} from "./question.js"; -import {Answer} from "../entities/questions/answer.entity.js"; +import { mapToUserDTO, UserDTO } from './user.js'; +import { mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId } from './question.js'; +import { Answer } from '../entities/questions/answer.entity.js'; export interface AnswerDTO { author: UserDTO; @@ -33,6 +33,6 @@ export function mapToAnswerId(answer: AnswerDTO): AnswerId { return { author: answer.author.username, toQuestion: mapToQuestionId(answer.toQuestion), - sequenceNumber: answer.sequenceNumber - } + sequenceNumber: answer.sequenceNumber, + }; } diff --git a/backend/src/interfaces/class.ts b/backend/src/interfaces/class.ts index e04ce2d4..371e3cae 100644 --- a/backend/src/interfaces/class.ts +++ b/backend/src/interfaces/class.ts @@ -27,15 +27,11 @@ export function mapToClassDTO(cls: Class): ClassDTO { }; } -export function mapToClass( - classData: ClassDTO, - students: Collection, - teachers: Collection -): Class { +export function mapToClass(classData: ClassDTO, students: Collection, teachers: Collection): Class { const cls = new Class(); cls.displayName = classData.displayName; cls.students = students; cls.teachers = teachers; - + return cls; } diff --git a/backend/src/interfaces/question.ts b/backend/src/interfaces/question.ts index 69039ee9..8cca42f6 100644 --- a/backend/src/interfaces/question.ts +++ b/backend/src/interfaces/question.ts @@ -1,6 +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 { UserDTO } from './user.js'; +import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; import { mapToStudentDTO, StudentDTO } from './student.js'; import { TeacherDTO } from './teacher.js'; @@ -19,8 +19,8 @@ export function mapToQuestionDTO(question: Question): QuestionDTO { const learningObjectIdentifier = { hruid: question.learningObjectHruid, language: question.learningObjectLanguage, - version: question.learningObjectVersion - } + version: question.learningObjectVersion, + }; return { learningObjectIdentifier, @@ -42,4 +42,3 @@ export function mapToQuestionId(question: QuestionDTO): QuestionId { sequenceNumber: question.sequenceNumber!, }; } - diff --git a/backend/src/interfaces/student.ts b/backend/src/interfaces/student.ts index 9790eb35..079b355b 100644 --- a/backend/src/interfaces/student.ts +++ b/backend/src/interfaces/student.ts @@ -23,11 +23,7 @@ export function mapToStudentDTO(student: Student): StudentDTO { } export function mapToStudent(studentData: StudentDTO): Student { - const student = new Student( - studentData.username, - studentData.firstName, - studentData.lastName, - ); + const student = new Student(studentData.username, studentData.firstName, studentData.lastName); return student; } diff --git a/backend/src/interfaces/submission.ts b/backend/src/interfaces/submission.ts index 40b76fad..fbaf520d 100644 --- a/backend/src/interfaces/submission.ts +++ b/backend/src/interfaces/submission.ts @@ -1,20 +1,20 @@ -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"; +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, - learningObjectLanguage: Language, - learningObjectVersion: number, + learningObjectHruid: string; + learningObjectLanguage: Language; + learningObjectVersion: number; - submissionNumber?: number, - submitter: StudentDTO, - time?: Date, - group?: GroupDTO, - content: string, + submissionNumber?: number; + submitter: StudentDTO; + time?: Date; + group?: GroupDTO; + content: string; } export function mapToSubmissionDTO(submission: Submission): SubmissionDTO { @@ -28,7 +28,7 @@ export function mapToSubmissionDTO(submission: Submission): SubmissionDTO { time: submission.submissionTime, group: submission.onBehalfOf ? mapToGroupDTO(submission.onBehalfOf) : undefined, content: submission.content, - } + }; } export function mapToSubmission(submissionDTO: SubmissionDTO): Submission { @@ -37,7 +37,7 @@ export function mapToSubmission(submissionDTO: SubmissionDTO): Submission { submission.learningObjectLanguage = submissionDTO.learningObjectLanguage; submission.learningObjectVersion = submissionDTO.learningObjectVersion; // Submission.submissionNumber = submissionDTO.submissionNumber; - submission.submitter = mapToStudent(submissionDTO.submitter) ; + submission.submitter = mapToStudent(submissionDTO.submitter); // Submission.submissionTime = submissionDTO.time; // Submission.onBehalfOf = submissionDTO.group!; // TODO fix group diff --git a/backend/src/interfaces/teacher-invitation.ts b/backend/src/interfaces/teacher-invitation.ts index d29e7476..cddef566 100644 --- a/backend/src/interfaces/teacher-invitation.ts +++ b/backend/src/interfaces/teacher-invitation.ts @@ -8,9 +8,7 @@ export interface TeacherInvitationDTO { class: string | ClassDTO; } -export function mapToTeacherInvitationDTO( - invitation: TeacherInvitation -): TeacherInvitationDTO { +export function mapToTeacherInvitationDTO(invitation: TeacherInvitation): TeacherInvitationDTO { return { sender: mapToUserDTO(invitation.sender), receiver: mapToUserDTO(invitation.receiver), @@ -18,9 +16,7 @@ export function mapToTeacherInvitationDTO( }; } -export function mapToTeacherInvitationDTOIds( - invitation: TeacherInvitation -): TeacherInvitationDTO { +export function mapToTeacherInvitationDTOIds(invitation: TeacherInvitation): TeacherInvitationDTO { return { sender: invitation.sender.username, receiver: invitation.receiver.username, diff --git a/backend/src/interfaces/teacher.ts b/backend/src/interfaces/teacher.ts index 93dbf5fd..4dd6adb4 100644 --- a/backend/src/interfaces/teacher.ts +++ b/backend/src/interfaces/teacher.ts @@ -23,11 +23,7 @@ export function mapToTeacherDTO(teacher: Teacher): TeacherDTO { } export function mapToTeacher(TeacherData: TeacherDTO): Teacher { - const teacher = new Teacher( - TeacherData.username, - TeacherData.firstName, - TeacherData.lastName, - ); + const teacher = new Teacher(TeacherData.username, TeacherData.firstName, TeacherData.lastName); return teacher; } diff --git a/backend/src/interfaces/user.ts b/backend/src/interfaces/user.ts index 64081d48..58f0dd5a 100644 --- a/backend/src/interfaces/user.ts +++ b/backend/src/interfaces/user.ts @@ -22,10 +22,7 @@ export function mapToUserDTO(user: User): UserDTO { }; } -export function mapToUser( - userData: UserDTO, - userInstance: T -): T { +export function mapToUser(userData: UserDTO, userInstance: T): T { userInstance.username = userData.username; userInstance.firstName = userData.firstName; userInstance.lastName = userData.lastName; diff --git a/backend/src/routes/learning-objects.ts b/backend/src/routes/learning-objects.ts index 45e0df4c..7532765b 100644 --- a/backend/src/routes/learning-objects.ts +++ b/backend/src/routes/learning-objects.ts @@ -1,15 +1,9 @@ import express from 'express'; -import { - getAllLearningObjects, - getAttachment, - getLearningObject, - getLearningObjectHTML, -} from '../controllers/learning-objects.js'; +import { getAllLearningObjects, getAttachment, getLearningObject, getLearningObjectHTML } from '../controllers/learning-objects.js'; import submissionRoutes from './submissions.js'; import questionRoutes from './questions.js'; - const router = express.Router(); // DWENGO learning objects @@ -32,7 +26,7 @@ router.get('/:hruid', getLearningObject); router.use('/:hruid/submissions', submissionRoutes); -router.use('/:hruid/:version/questions', questionRoutes) +router.use('/:hruid/:version/questions', questionRoutes); // Parameter: hruid of learning object // Query: language, version (optional) diff --git a/backend/src/routes/questions.ts b/backend/src/routes/questions.ts index d9a4dd9d..31a71f3b 100644 --- a/backend/src/routes/questions.ts +++ b/backend/src/routes/questions.ts @@ -1,10 +1,11 @@ import express from 'express'; import { - createQuestionHandler, deleteQuestionHandler, + createQuestionHandler, + deleteQuestionHandler, getAllQuestionsHandler, getQuestionAnswersHandler, - getQuestionHandler -} from "../controllers/questions.js"; + getQuestionHandler, +} from '../controllers/questions.js'; const router = express.Router({ mergeParams: true }); // Query language diff --git a/backend/src/routes/submissions.ts b/backend/src/routes/submissions.ts index e431260f..4db93027 100644 --- a/backend/src/routes/submissions.ts +++ b/backend/src/routes/submissions.ts @@ -1,9 +1,7 @@ import express from 'express'; -import {createSubmissionHandler, deleteSubmissionHandler, getSubmissionHandler} from '../controllers/submissions.js'; +import { createSubmissionHandler, deleteSubmissionHandler, getSubmissionHandler } from '../controllers/submissions.js'; const router = express.Router({ mergeParams: true }); - - // Root endpoint used to search objects router.get('/', (req, res) => { res.json({ diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts index e67c2c0c..be121810 100644 --- a/backend/src/services/assignments.ts +++ b/backend/src/services/assignments.ts @@ -1,22 +1,9 @@ -import { - getAssignmentRepository, - getClassRepository, - getGroupRepository, - getSubmissionRepository, -} from '../data/repositories.js'; +import { getAssignmentRepository, getClassRepository, getGroupRepository, getSubmissionRepository } from '../data/repositories.js'; import { Assignment } from '../entities/assignments/assignment.entity.js'; -import { - AssignmentDTO, - mapToAssignment, - mapToAssignmentDTO, - mapToAssignmentDTOId, -} from '../interfaces/assignment.js'; +import { AssignmentDTO, mapToAssignment, mapToAssignmentDTO, mapToAssignmentDTOId } from '../interfaces/assignment.js'; import { mapToSubmissionDTO, SubmissionDTO } from '../interfaces/submission.js'; -export async function getAllAssignments( - classid: string, - full: boolean -): Promise { +export async function getAllAssignments(classid: string, full: boolean): Promise { const classRepository = getClassRepository(); const cls = await classRepository.findById(classid); @@ -25,8 +12,7 @@ export async function getAllAssignments( } const assignmentRepository = getAssignmentRepository(); - const assignments = - await assignmentRepository.findAllAssignmentsInClass(cls); + const assignments = await assignmentRepository.findAllAssignmentsInClass(cls); if (full) { return assignments.map(mapToAssignmentDTO); @@ -35,14 +21,11 @@ export async function getAllAssignments( return assignments.map(mapToAssignmentDTOId); } -export async function createAssignment( - classid: string, - assignmentData: AssignmentDTO, -): Promise { +export async function createAssignment(classid: string, assignmentData: AssignmentDTO): Promise { const classRepository = getClassRepository(); const cls = await classRepository.findById(classid); - if (!cls) { + if (!cls) { return null; } @@ -54,16 +37,12 @@ export async function createAssignment( await assignmentRepository.save(newAssignment); return newAssignment; - } catch(e) { + } catch (e) { return null; } - } -export async function getAssignment( - classid: string, - id: number -): Promise { +export async function getAssignment(classid: string, id: number): Promise { const classRepository = getClassRepository(); const cls = await classRepository.findById(classid); @@ -81,10 +60,7 @@ export async function getAssignment( return mapToAssignmentDTO(assignment); } -export async function getAssignmentsSubmissions( - classid: string, - assignmentNumber: number, -): Promise { +export async function getAssignmentsSubmissions(classid: string, assignmentNumber: number): Promise { const classRepository = getClassRepository(); const cls = await classRepository.findById(classid); @@ -103,8 +79,7 @@ export async function getAssignmentsSubmissions( 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((group) => submissionRepository.findAllSubmissionsForGroup(group)))).flat(); return submissions.map(mapToSubmissionDTO); -} \ No newline at end of file +} diff --git a/backend/src/services/class.ts b/backend/src/services/class.ts index 4b76d0cd..46530327 100644 --- a/backend/src/services/class.ts +++ b/backend/src/services/class.ts @@ -1,26 +1,12 @@ -import { - getClassRepository, - getStudentRepository, - getTeacherInvitationRepository, - getTeacherRepository, -} from '../data/repositories.js'; +import { getClassRepository, getStudentRepository, getTeacherInvitationRepository, getTeacherRepository } from '../data/repositories.js'; import { Class } from '../entities/classes/class.entity.js'; import { ClassDTO, mapToClassDTO } from '../interfaces/class.js'; import { mapToStudentDTO, StudentDTO } from '../interfaces/student.js'; -import { - mapToTeacherInvitationDTO, - mapToTeacherInvitationDTOIds, - TeacherInvitationDTO, -} from '../interfaces/teacher-invitation.js'; +import { mapToTeacherInvitationDTO, mapToTeacherInvitationDTOIds, TeacherInvitationDTO } from '../interfaces/teacher-invitation.js'; -export async function getAllClasses( - full: boolean -): Promise { +export async function getAllClasses(full: boolean): Promise { const classRepository = getClassRepository(); - const classes = await classRepository.find( - {}, - { populate: ['students', 'teachers'] } - ); + const classes = await classRepository.find({}, { populate: ['students', 'teachers'] }); if (!classes) { return []; @@ -35,13 +21,11 @@ export async function getAllClasses( export async function createClass(classData: ClassDTO): 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((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((id) => studentRepository.findByUsername(id)))).filter((student) => student != null); //Const cls = mapToClass(classData, teachers, students); @@ -56,7 +40,7 @@ export async function createClass(classData: ClassDTO): Promise { await classRepository.save(newClass); return newClass; - } catch(e) { + } catch (e) { return null; } } @@ -92,10 +76,7 @@ export async function getClassStudentsIds(classId: string): Promise { return students.map((student) => student.username); } -export async function getClassTeacherInvitations( - classId: string, - full: boolean -): Promise { +export async function getClassTeacherInvitations(classId: string, full: boolean): Promise { const classRepository = getClassRepository(); const cls = await classRepository.findById(classId); @@ -104,8 +85,7 @@ export async function getClassTeacherInvitations( } const teacherInvitationRepository = getTeacherInvitationRepository(); - const invitations = - await teacherInvitationRepository.findAllInvitationsForClass(cls); + const invitations = await teacherInvitationRepository.findAllInvitationsForClass(cls); if (full) { return invitations.map(mapToTeacherInvitationDTO); diff --git a/backend/src/services/groups.ts b/backend/src/services/groups.ts index 2f2d0a14..91091703 100644 --- a/backend/src/services/groups.ts +++ b/backend/src/services/groups.ts @@ -7,19 +7,10 @@ import { getSubmissionRepository, } from '../data/repositories.js'; import { Group } from '../entities/assignments/group.entity.js'; -import { - GroupDTO, - mapToGroupDTO, - mapToGroupDTOId, -} from '../interfaces/group.js'; +import { GroupDTO, mapToGroupDTO, mapToGroupDTOId } from '../interfaces/group.js'; import { mapToSubmissionDTO, SubmissionDTO } from '../interfaces/submission.js'; -export async function getGroup( - classId: string, - assignmentNumber: number, - groupNumber: number, - full: boolean -): Promise { +export async function getGroup(classId: string, assignmentNumber: number, groupNumber: number, full: boolean): Promise { const classRepository = getClassRepository(); const cls = await classRepository.findById(classId); @@ -28,20 +19,14 @@ export async function getGroup( } const assignmentRepository = getAssignmentRepository(); - const assignment = await assignmentRepository.findByClassAndId( - cls, - assignmentNumber - ); + const assignment = await assignmentRepository.findByClassAndId(cls, assignmentNumber); if (!assignment) { return null; } const groupRepository = getGroupRepository(); - const group = await groupRepository.findByAssignmentAndGroupNumber( - assignment, - groupNumber - ); + const group = await groupRepository.findByAssignmentAndGroupNumber(assignment, groupNumber); if (!group) { return null; @@ -54,16 +39,11 @@ export async function getGroup( return mapToGroupDTOId(group); } -export async function createGroup( - groupData: GroupDTO, - classid: string, - assignmentNumber: number, -): Promise { +export async function createGroup(groupData: GroupDTO, classid: string, assignmentNumber: number): Promise { 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 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); @@ -90,17 +70,13 @@ export async function createGroup( await groupRepository.save(newGroup); return newGroup; - } catch(e) { + } catch (e) { console.log(e); return null; } } -export async function getAllGroups( - classId: string, - assignmentNumber: number, - full: boolean -): Promise { +export async function getAllGroups(classId: string, assignmentNumber: number, full: boolean): Promise { const classRepository = getClassRepository(); const cls = await classRepository.findById(classId); @@ -109,10 +85,7 @@ export async function getAllGroups( } const assignmentRepository = getAssignmentRepository(); - const assignment = await assignmentRepository.findByClassAndId( - cls, - assignmentNumber - ); + const assignment = await assignmentRepository.findByClassAndId(cls, assignmentNumber); if (!assignment) { return []; @@ -130,11 +103,7 @@ export async function getAllGroups( return groups.map(mapToGroupDTOId); } -export async function getGroupSubmissions( - classId: string, - assignmentNumber: number, - groupNumber: number, -): Promise { +export async function getGroupSubmissions(classId: string, assignmentNumber: number, groupNumber: number): Promise { const classRepository = getClassRepository(); const cls = await classRepository.findById(classId); @@ -143,20 +112,14 @@ export async function getGroupSubmissions( } const assignmentRepository = getAssignmentRepository(); - const assignment = await assignmentRepository.findByClassAndId( - cls, - assignmentNumber - ); + const assignment = await assignmentRepository.findByClassAndId(cls, assignmentNumber); if (!assignment) { return []; } const groupRepository = getGroupRepository(); - const group = await groupRepository.findByAssignmentAndGroupNumber( - assignment, - groupNumber - ); + const group = await groupRepository.findByAssignmentAndGroupNumber(assignment, groupNumber); if (!group) { return []; diff --git a/backend/src/services/learning-objects.ts b/backend/src/services/learning-objects.ts index dfde4da7..fb579471 100644 --- a/backend/src/services/learning-objects.ts +++ b/backend/src/services/learning-objects.ts @@ -1,16 +1,8 @@ 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 { FilteredLearningObject, LearningObjectMetadata, LearningObjectNode, LearningPathResponse } from '../interfaces/learning-content.js'; -function filterData( - data: LearningObjectMetadata, - htmlUrl: string -): FilteredLearningObject { +function filterData(data: LearningObjectMetadata, htmlUrl: string): FilteredLearningObject { return { key: data.hruid, // Hruid learningObject (not path) _id: data._id, @@ -37,10 +29,7 @@ function filterData( /** * Fetches a single learning object by its HRUID */ -export async function getLearningObjectById( - hruid: string, - language: string -): Promise { +export async function getLearningObjectById(hruid: string, language: string): Promise { const metadataUrl = `${DWENGO_API_BASE}/learningObject/getMetadata?hruid=${hruid}&language=${language}`; const metadata = await fetchWithLogging( metadataUrl, @@ -59,26 +48,12 @@ export async function getLearningObjectById( /** * Generic function to fetch learning objects (full data or just HRUIDs) */ -async function fetchLearningObjects( - hruid: string, - full: boolean, - language: string -): Promise { +async function fetchLearningObjects(hruid: string, full: boolean, language: string): Promise { try { - const learningPathResponse: LearningPathResponse = - await fetchLearningPaths( - [hruid], - language, - `Learning path for HRUID "${hruid}"` - ); + 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.` - ); + if (!learningPathResponse.success || !learningPathResponse.data?.length) { + console.error(`⚠️ WARNING: Learning path "${hruid}" exists but contains no learning objects.`); return []; } @@ -88,12 +63,9 @@ async function fetchLearningObjects( return nodes.map((node) => node.learningobject_hruid); } - return await Promise.all( - nodes.map(async (node) => getLearningObjectById( - node.learningobject_hruid, - language - )) - ).then((objects) => objects.filter((obj): obj is FilteredLearningObject => obj !== null)); + return await Promise.all(nodes.map(async (node) => getLearningObjectById(node.learningobject_hruid, language))).then((objects) => + objects.filter((obj): obj is FilteredLearningObject => obj !== null) + ); } catch (error) { console.error('❌ Error fetching learning objects:', error); return []; @@ -103,27 +75,16 @@ async function fetchLearningObjects( /** * Fetch full learning object data (metadata) */ -export async function getLearningObjectsFromPath( - hruid: string, - language: string -): Promise { - return (await fetchLearningObjects( - hruid, - true, - language - )) as FilteredLearningObject[]; +export async function getLearningObjectsFromPath(hruid: string, language: string): Promise { + return (await fetchLearningObjects(hruid, true, language)) as FilteredLearningObject[]; } /** * Fetch only learning object HRUIDs */ -export async function getLearningObjectIdsFromPath( - hruid: string, - language: string -): Promise { +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 { throw new Error('Function not implemented.'); } - diff --git a/backend/src/services/questions.ts b/backend/src/services/questions.ts index 238ebd45..ee003bcd 100644 --- a/backend/src/services/questions.ts +++ b/backend/src/services/questions.ts @@ -1,17 +1,15 @@ -import {getAnswerRepository, getQuestionRepository} from "../data/repositories.js"; -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 {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"; +import { getAnswerRepository, getQuestionRepository } from '../data/repositories.js'; +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 { 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 { +export async function getAllQuestions(id: LearningObjectIdentifier, full: boolean): Promise { const questionRepository: QuestionRepository = getQuestionRepository(); const questions = await questionRepository.findAllQuestionsAboutLearningObject(id); @@ -19,7 +17,7 @@ export async function getAllQuestions( return []; } - const questionsDTO: QuestionDTO[] = questions.map(mapToQuestionDTO); + const questionsDTO: QuestionDTO[] = questions.map(mapToQuestionDTO); if (full) { return questionsDTO; @@ -31,21 +29,20 @@ export async function getAllQuestions( async function fetchQuestion(questionId: QuestionId): Promise { const questionRepository = getQuestionRepository(); - return await questionRepository.findOne( - { - learningObjectHruid: questionId.learningObjectIdentifier.hruid, - learningObjectLanguage: questionId.learningObjectIdentifier.language, - learningObjectVersion: questionId.learningObjectIdentifier.version, - sequenceNumber: questionId.sequenceNumber - } - ) + return await questionRepository.findOne({ + learningObjectHruid: questionId.learningObjectIdentifier.hruid, + learningObjectLanguage: questionId.learningObjectIdentifier.language, + learningObjectVersion: questionId.learningObjectIdentifier.version, + sequenceNumber: questionId.sequenceNumber, + }); } export async function getQuestion(questionId: QuestionId): Promise { const question = await fetchQuestion(questionId); - if (!question) - {return null} + if (!question) { + return null; + } return mapToQuestionDTO(question); } @@ -54,18 +51,21 @@ export async function getAnswersByQuestion(questionId: QuestionId, full: boolean const answerRepository = getAnswerRepository(); const question = await fetchQuestion(questionId); - if (!question) - {return [];} + if (!question) { + return []; + } const answers: Answer[] = await answerRepository.findAllAnswersToQuestion(question); - if (!answers) - {return []} + if (!answers) { + return []; + } const answersDTO = answers.map(mapToAnswerDTO); - if (full) - {return answersDTO} + if (full) { + return answersDTO; + } return answersDTO.map(mapToAnswerId); } @@ -79,10 +79,10 @@ export async function createQuestion(questionDTO: QuestionDTO) { await questionRepository.createQuestion({ loId: questionDTO.learningObjectIdentifier, author, - content: questionDTO.content } - ); + content: questionDTO.content, + }); } catch (e) { - return null + return null; } return questionDTO; @@ -93,18 +93,15 @@ export async function deleteQuestion(questionId: QuestionId) { const question = await fetchQuestion(questionId); - if (!question) - {return null} - - try { - await questionRepository.removeQuestionByLearningObjectAndSequenceNumber( - questionId.learningObjectIdentifier, questionId.sequenceNumber - ); - } catch (e) { - return null + if (!question) { + return null; } - return question + try { + await questionRepository.removeQuestionByLearningObjectAndSequenceNumber(questionId.learningObjectIdentifier, questionId.sequenceNumber); + } catch (e) { + return null; + } + + return question; } - - diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts index 3b677a10..5099a18d 100644 --- a/backend/src/services/students.ts +++ b/backend/src/services/students.ts @@ -1,9 +1,4 @@ -import { - getClassRepository, - getGroupRepository, - getStudentRepository, - getSubmissionRepository, -} from '../data/repositories.js'; +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'; @@ -14,7 +9,6 @@ import { mapToSubmissionDTO, SubmissionDTO } from '../interfaces/submission.js'; import { getAllAssignments } from './assignments.js'; import { UserService } from './users.js'; - export async function getAllStudents(): Promise { const studentRepository = getStudentRepository(); const users = await studentRepository.findAll(); @@ -38,9 +32,9 @@ export async function createStudent(userData: StudentDTO): Promise await getAllAssignments(cls.classId!, full)) - ) - ).flat(); + const assignments = (await Promise.all(classes.map(async (cls) => await getAllAssignments(cls.classId!, full)))).flat(); return assignments; } @@ -121,9 +111,7 @@ export async function getStudentGroups(username: string, full: boolean): Promise return groups.map(mapToGroupDTOId); } -export async function getStudentSubmissions( - username: string -): Promise { +export async function getStudentSubmissions(username: string): Promise { const studentRepository = getStudentRepository(); const student = await studentRepository.findByUsername(username); @@ -135,4 +123,4 @@ export async function getStudentSubmissions( const submissions = await submissionRepository.findAllSubmissionsForStudent(student); return submissions.map(mapToSubmissionDTO); -} \ No newline at end of file +} diff --git a/backend/src/services/submissions.ts b/backend/src/services/submissions.ts index c9135001..a8fa96c7 100644 --- a/backend/src/services/submissions.ts +++ b/backend/src/services/submissions.ts @@ -1,13 +1,13 @@ -import {getGroupRepository, 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 { getGroupRepository, 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'; export async function getSubmission( learningObjectHruid: string, language: Language, version: number, - submissionNumber: number, + submissionNumber: number ): Promise { const loId = new LearningObjectIdentifier(learningObjectHruid, language, version); @@ -29,29 +29,23 @@ export async function createSubmission(submissionDTO: SubmissionDTO) { const newSubmission = await submissionRepository.create(submission); await submissionRepository.save(newSubmission); } catch (e) { - return null + return null; } 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) { const submissionRepository = getSubmissionRepository(); const submission = getSubmission(learningObjectHruid, language, version, submissionNumber); - if (!submission) - {return null} + if (!submission) { + return null; + } const loId = new LearningObjectIdentifier(learningObjectHruid, language, version); await submissionRepository.deleteSubmissionByLearningObjectAndSubmissionNumber(loId, submissionNumber); return submission; } - - diff --git a/backend/src/services/teachers.ts b/backend/src/services/teachers.ts index 2f462ed9..f4dbedfe 100644 --- a/backend/src/services/teachers.ts +++ b/backend/src/services/teachers.ts @@ -9,12 +9,7 @@ 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 { 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'; @@ -42,9 +37,9 @@ export async function createTeacher(userData: TeacherDTO): Promise export async function fetchStudentsByTeacher(username: string) { const classes = await getClassIdsByTeacher(username); - return ( - await Promise.all( - classes.map(async (id) => getClassStudents(id)) - ) - ).flat(); + return (await Promise.all(classes.map(async (id) => getClassStudents(id)))).flat(); } export async function getStudentsByTeacher(username: string): Promise { @@ -122,10 +113,7 @@ export async function fetchTeacherQuestions(username: string): Promise Date: Thu, 13 Mar 2025 19:22:29 +0100 Subject: [PATCH 084/106] chore: PostgreSQL op 5431 --- backend/.env.development.example | 2 +- backend/.env.example | 2 +- backend/.env.production.example | 2 +- compose.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/.env.development.example b/backend/.env.development.example index c9f3cdbf..466e1b7b 100644 --- a/backend/.env.development.example +++ b/backend/.env.development.example @@ -4,7 +4,7 @@ DWENGO_PORT=3000 # The port the backend will listen on DWENGO_DB_HOST=localhost -DWENGO_DB_PORT=5432 +DWENGO_DB_PORT=5431 DWENGO_DB_USERNAME=postgres DWENGO_DB_PASSWORD=postgres DWENGO_DB_UPDATE=true diff --git a/backend/.env.example b/backend/.env.example index 0a8f3994..bd13b54c 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,6 +1,6 @@ DWENGO_PORT=3000 # The port the backend will listen on DWENGO_DB_HOST=domain-or-ip-of-database -DWENGO_DB_PORT=5432 +DWENGO_DB_PORT=5431 # Change this to the actual credentials of the user Dwengo should use in the backend DWENGO_DB_USERNAME=postgres diff --git a/backend/.env.production.example b/backend/.env.production.example index 1adf7d95..390409d1 100644 --- a/backend/.env.production.example +++ b/backend/.env.production.example @@ -1,6 +1,6 @@ DWENGO_PORT=3000 # The port the backend will listen on DWENGO_DB_HOST=db # Name of the database container -DWENGO_DB_PORT=5432 +DWENGO_DB_PORT=5431 # Change this to the actual credentials of the user Dwengo should use in the backend DWENGO_DB_NAME=postgres diff --git a/compose.yml b/compose.yml index 2bb736bf..3c4bff9a 100644 --- a/compose.yml +++ b/compose.yml @@ -7,7 +7,7 @@ services: db: image: postgres:latest ports: - - '5432:5432' + - '5431:5432' restart: unless-stopped volumes: - dwengo_postgres_data:/var/lib/postgresql/data From 8c7d5e965cdff1b282365a68555d550b24111f28 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Thu, 13 Mar 2025 19:27:34 +0100 Subject: [PATCH 085/106] fix: DELETE en POST endpoints toegevoegd --- backend/config.js | 8 ++++++++ backend/src/app.ts | 2 ++ backend/src/controllers/assignments.ts | 2 +- backend/src/controllers/students.ts | 6 ++---- backend/src/routes/students.ts | 2 ++ backend/src/routes/teachers.ts | 2 ++ 6 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 backend/config.js diff --git a/backend/config.js b/backend/config.js new file mode 100644 index 00000000..b9776fa9 --- /dev/null +++ b/backend/config.js @@ -0,0 +1,8 @@ +// Can be placed in dotenv but found it redundant +// Import dotenv from "dotenv"; +// Load .env file +// Dotenv.config(); +export const DWENGO_API_BASE = 'https://dwengo.org/backend/api'; +export const FALLBACK_LANG = 'nl'; +export const FALLBACK_SEQ_NUM = 1; + diff --git a/backend/src/app.ts b/backend/src/app.ts index abb2bbbc..ede3dddf 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -6,6 +6,7 @@ import learningPathRoutes from './routes/learning-paths.js'; import learningObjectRoutes from './routes/learning-objects.js'; import studentRouter from './routes/students.js'; +import teacherRouter from './routes/teachers.js'; import groupRouter from './routes/groups.js'; import assignmentRouter from './routes/assignments.js'; import submissionRouter from './routes/submissions.js'; @@ -38,6 +39,7 @@ app.get('/', (_, res: Response) => { }); app.use('/student', studentRouter); +app.use('/teacher', teacherRouter); app.use('/group', groupRouter); app.use('/assignment', assignmentRouter); app.use('/submission', submissionRouter); diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index b0695b85..3db0e3f1 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -26,7 +26,7 @@ export async function createAssignmentHandler(req: Request, re if (!assignmentData.description || !assignmentData.language || !assignmentData.learningPath || !assignmentData.title) { res.status(400).json({ - error: 'Missing one or more required fields: title, description, learningPath, title', + error: 'Missing one or more required fields: title, description, learningPath, language', }); return; } diff --git a/backend/src/controllers/students.ts b/backend/src/controllers/students.ts index 5c17513d..274c6c55 100644 --- a/backend/src/controllers/students.ts +++ b/backend/src/controllers/students.ts @@ -2,6 +2,7 @@ import { Request, Response } from 'express'; import { createStudent, deleteStudent, + getAllStudents, getStudent, getStudentAssignments, getStudentClasses, @@ -142,7 +143,4 @@ export async function getStudentSubmissionsHandler(req: Request, res: Response): res.json({ submissions: submissions, }); -} -function getAllStudents(): StudentDTO[] | string[] | PromiseLike { - throw new Error('Function not implemented.'); -} +} \ No newline at end of file diff --git a/backend/src/routes/students.ts b/backend/src/routes/students.ts index 0c8babce..7ed7a666 100644 --- a/backend/src/routes/students.ts +++ b/backend/src/routes/students.ts @@ -17,6 +17,8 @@ router.get('/', getAllStudentsHandler); router.post('/', createStudentHandler); +router.delete('/', deleteStudentHandler); + router.delete('/:username', deleteStudentHandler); // Information about a student's profile diff --git a/backend/src/routes/teachers.ts b/backend/src/routes/teachers.ts index 8e7f709d..c04e1575 100644 --- a/backend/src/routes/teachers.ts +++ b/backend/src/routes/teachers.ts @@ -15,6 +15,8 @@ router.get('/', getAllTeachersHandler); router.post('/', createTeacherHandler); +router.delete('/', deleteTeacherHandler); + router.get('/:username', getTeacherHandler); router.delete('/:username', deleteTeacherHandler); From e1e8d475f48ca211bd79f78e265dfe0d9b055c08 Mon Sep 17 00:00:00 2001 From: Lint Action Date: Thu, 13 Mar 2025 18:28:43 +0000 Subject: [PATCH 086/106] style: fix linting issues met Prettier --- backend/config.js | 1 - backend/src/controllers/students.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/config.js b/backend/config.js index b9776fa9..be42027c 100644 --- a/backend/config.js +++ b/backend/config.js @@ -5,4 +5,3 @@ export const DWENGO_API_BASE = 'https://dwengo.org/backend/api'; export const FALLBACK_LANG = 'nl'; export const FALLBACK_SEQ_NUM = 1; - diff --git a/backend/src/controllers/students.ts b/backend/src/controllers/students.ts index 274c6c55..6c253cff 100644 --- a/backend/src/controllers/students.ts +++ b/backend/src/controllers/students.ts @@ -143,4 +143,4 @@ export async function getStudentSubmissionsHandler(req: Request, res: Response): res.json({ submissions: submissions, }); -} \ No newline at end of file +} From f84c716fd346c97a55d30f38442afe84322da068 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Thu, 13 Mar 2025 19:40:41 +0100 Subject: [PATCH 087/106] fix: database-learning-path-provider type errors gefixt --- .../database-learning-path-provider.ts | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) 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 fa8f42c6..8cd6807b 100644 --- a/backend/src/services/learning-paths/database-learning-path-provider.ts +++ b/backend/src/services/learning-paths/database-learning-path-provider.ts @@ -29,7 +29,7 @@ async function getLearningObjectsForNodes(nodes: LearningPathNode[]): Promise it === null)) { + if (Array.from(nullableNodesToLearningObjects.values()).some((it) => it === null)) { throw new Error('At least one of the learning objects on this path could not be found.'); } return nullableNodesToLearningObjects as Map; @@ -41,15 +41,11 @@ async function getLearningObjectsForNodes(nodes: LearningPathNode[]): Promise { const nodesToLearningObjects: Map = await getLearningObjectsForNodes(learningPath.nodes); - const targetAges = nodesToLearningObjects - .values() - .flatMap((it) => it.targetAges || []) - .toArray(); + const targetAges = Array.from(nodesToLearningObjects.values()) + .flatMap((it) => it.targetAges || []); - const keywords = nodesToLearningObjects - .values() - .flatMap((it) => it.keywords || []) - .toArray(); + const keywords = Array.from(nodesToLearningObjects.values()) + .flatMap((it) => it.keywords || []); const image = learningPath.image ? learningPath.image.toString('base64') : undefined; @@ -83,8 +79,7 @@ async function convertNodes( nodesToLearningObjects: Map, personalizedFor?: PersonalizationTarget ): Promise { - const nodesPromise = nodesToLearningObjects - .entries() + const nodesPromise = Array.from(nodesToLearningObjects.entries()) .map(async (entry) => { const [node, learningObject] = entry; const lastSubmission = personalizedFor ? await getLastSubmissionForCustomizationTarget(node, personalizedFor) : null; @@ -102,8 +97,7 @@ async function convertNodes( ) .map((trans, i) => convertTransition(trans, i, nodesToLearningObjects)), // Then convert all the transition }; - }) - .toArray(); + }); return await Promise.all(nodesPromise); } From bec69863b422b7ada9e8d26120ec7829da48dc8d Mon Sep 17 00:00:00 2001 From: Lint Action Date: Thu, 13 Mar 2025 18:41:54 +0000 Subject: [PATCH 088/106] style: fix linting issues met Prettier --- .../database-learning-path-provider.ts | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) 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 8cd6807b..68986885 100644 --- a/backend/src/services/learning-paths/database-learning-path-provider.ts +++ b/backend/src/services/learning-paths/database-learning-path-provider.ts @@ -41,11 +41,9 @@ async function getLearningObjectsForNodes(nodes: LearningPathNode[]): Promise { const nodesToLearningObjects: Map = await getLearningObjectsForNodes(learningPath.nodes); - const targetAges = Array.from(nodesToLearningObjects.values()) - .flatMap((it) => it.targetAges || []); + const targetAges = Array.from(nodesToLearningObjects.values()).flatMap((it) => it.targetAges || []); - const keywords = Array.from(nodesToLearningObjects.values()) - .flatMap((it) => it.keywords || []); + const keywords = Array.from(nodesToLearningObjects.values()).flatMap((it) => it.keywords || []); const image = learningPath.image ? learningPath.image.toString('base64') : undefined; @@ -79,25 +77,24 @@ async function convertNodes( nodesToLearningObjects: Map, personalizedFor?: PersonalizationTarget ): Promise { - const nodesPromise = Array.from(nodesToLearningObjects.entries()) - .map(async (entry) => { - const [node, learningObject] = entry; - const lastSubmission = personalizedFor ? await getLastSubmissionForCustomizationTarget(node, personalizedFor) : null; - return { - _id: learningObject.uuid, - language: learningObject.language, - start_node: node.startNode, - created_at: node.createdAt.toISOString(), - updatedAt: node.updatedAt.toISOString(), - learningobject_hruid: node.learningObjectHruid, - version: learningObject.version, - transitions: node.transitions - .filter( - (trans) => !personalizedFor || isTransitionPossible(trans, optionalJsonStringToObject(lastSubmission?.content)) // If we want a personalized learning path, remove all transitions that aren't possible. - ) - .map((trans, i) => convertTransition(trans, i, nodesToLearningObjects)), // Then convert all the transition - }; - }); + const nodesPromise = Array.from(nodesToLearningObjects.entries()).map(async (entry) => { + const [node, learningObject] = entry; + const lastSubmission = personalizedFor ? await getLastSubmissionForCustomizationTarget(node, personalizedFor) : null; + return { + _id: learningObject.uuid, + language: learningObject.language, + start_node: node.startNode, + created_at: node.createdAt.toISOString(), + updatedAt: node.updatedAt.toISOString(), + learningobject_hruid: node.learningObjectHruid, + version: learningObject.version, + transitions: node.transitions + .filter( + (trans) => !personalizedFor || isTransitionPossible(trans, optionalJsonStringToObject(lastSubmission?.content)) // If we want a personalized learning path, remove all transitions that aren't possible. + ) + .map((trans, i) => convertTransition(trans, i, nodesToLearningObjects)), // Then convert all the transition + }; + }); return await Promise.all(nodesPromise); } From 1be2e23b7fbfb5d1be7d28f3ee951c7cee993d8b Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 13 Mar 2025 19:49:18 +0100 Subject: [PATCH 089/106] fix: frontend routeren --- compose.prod.yml | 2 +- config/nginx/nginx.conf | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compose.prod.yml b/compose.prod.yml index 2063323e..075f75b9 100644 --- a/compose.prod.yml +++ b/compose.prod.yml @@ -14,7 +14,7 @@ services: labels: - 'traefik.enable=true' - 'traefik.http.routers.web.rule=PathPrefix(`/`)' - - 'traefik.http.services.web.loadbalancer.server.port=80' + - 'traefik.http.services.web.loadbalancer.server.port=8080' api: build: diff --git a/config/nginx/nginx.conf b/config/nginx/nginx.conf index 975b9580..dc9317f6 100644 --- a/config/nginx/nginx.conf +++ b/config/nginx/nginx.conf @@ -10,8 +10,8 @@ http { default_type application/octet-stream; types { - application/javascript js mjs; - text/css css; + application/javascript mjs; + text/css; } server { From 6e7a03f55b681166fd76ac157fa1fa0eb78dd696 Mon Sep 17 00:00:00 2001 From: Gerald Schmittinger Date: Thu, 13 Mar 2025 20:45:34 +0100 Subject: [PATCH 090/106] fix(backend): Aangemaakt assignment werd niet teruggegeven. Hier ontbreekte gewoon een await. --- backend/src/controllers/assignments.ts | 5 ++--- backend/src/services/class.ts | 4 ++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index 3db0e3f1..03332469 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -1,7 +1,6 @@ import { Request, Response } from 'express'; import { createAssignment, getAllAssignments, getAssignment, getAssignmentsSubmissions } from '../services/assignments.js'; -import { AssignmentDTO, mapToAssignment, mapToAssignmentDTO } from '../interfaces/assignment.js'; -import { getAssignmentRepository, getClassRepository } from '../data/repositories.js'; +import { AssignmentDTO } from '../interfaces/assignment.js'; // Typescript is annoy with with parameter forwarding from class.ts interface AssignmentParams { @@ -31,7 +30,7 @@ export async function createAssignmentHandler(req: Request, re return; } - const assignment = createAssignment(classid, assignmentData); + const assignment = await createAssignment(classid, assignmentData); if (!assignment) { res.status(500).json({ error: 'Could not create assignment ' }); diff --git a/backend/src/services/class.ts b/backend/src/services/class.ts index 46530327..390c6a32 100644 --- a/backend/src/services/class.ts +++ b/backend/src/services/class.ts @@ -3,6 +3,9 @@ import { Class } from '../entities/classes/class.entity.js'; import { ClassDTO, mapToClassDTO } from '../interfaces/class.js'; import { mapToStudentDTO, StudentDTO } from '../interfaces/student.js'; import { mapToTeacherInvitationDTO, mapToTeacherInvitationDTOIds, TeacherInvitationDTO } from '../interfaces/teacher-invitation.js'; +import {getLogger} from "../logging/initalize"; + +const logger = getLogger(); export async function getAllClasses(full: boolean): Promise { const classRepository = getClassRepository(); @@ -41,6 +44,7 @@ export async function createClass(classData: ClassDTO): Promise { return newClass; } catch (e) { + logger.error(e); return null; } } From d9bd12f2577f2dea59d94795eaccdd2c047ef968 Mon Sep 17 00:00:00 2001 From: Lint Action Date: Thu, 13 Mar 2025 19:46:17 +0000 Subject: [PATCH 091/106] style: fix linting issues met Prettier --- backend/src/services/class.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/services/class.ts b/backend/src/services/class.ts index 390c6a32..117bffec 100644 --- a/backend/src/services/class.ts +++ b/backend/src/services/class.ts @@ -3,7 +3,7 @@ import { Class } from '../entities/classes/class.entity.js'; import { ClassDTO, mapToClassDTO } from '../interfaces/class.js'; import { mapToStudentDTO, StudentDTO } from '../interfaces/student.js'; import { mapToTeacherInvitationDTO, mapToTeacherInvitationDTOIds, TeacherInvitationDTO } from '../interfaces/teacher-invitation.js'; -import {getLogger} from "../logging/initalize"; +import { getLogger } from '../logging/initalize'; const logger = getLogger(); From a2be18d816e4d1f105d9d44a84faf630389d7d96 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 13 Mar 2025 20:56:24 +0100 Subject: [PATCH 092/106] fix: IDP insecure cookies --- compose.prod.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compose.prod.yml b/compose.prod.yml index 075f75b9..9dd5b066 100644 --- a/compose.prod.yml +++ b/compose.prod.yml @@ -54,6 +54,8 @@ services: environment: KC_HOSTNAME: 'sel2-1.ugent.be' PROXY_ADDRESS_FORWARDING: 'true' + KC_PROXY_HEADERS: 'xforwarded' + KC_HTTP_ENABLED: 'true' KC_HTTP_RELATIVE_PATH: '/idp' reverse-proxy: From 4c5f6196f6becdac977c37cefd458730385ecc0b Mon Sep 17 00:00:00 2001 From: Lint Action Date: Thu, 13 Mar 2025 20:12:09 +0000 Subject: [PATCH 093/106] style: fix linting issues met Prettier --- compose.prod.yml | 28 ++++++++++++++-------------- compose.yml | 2 +- config/idp/student-realm.json | 10 +++++++++- config/idp/teacher-realm.json | 10 +++++++++- frontend/src/config.ts | 5 ++++- 5 files changed, 37 insertions(+), 18 deletions(-) diff --git a/compose.prod.yml b/compose.prod.yml index 9dd5b066..8825796e 100644 --- a/compose.prod.yml +++ b/compose.prod.yml @@ -50,7 +50,7 @@ services: - 'traefik.http.routers.idp.rule=PathPrefix(`/idp`)' - 'traefik.http.services.idp.loadbalancer.server.port=7080' env_file: - - ./config/idp/.env + - ./config/idp/.env environment: KC_HOSTNAME: 'sel2-1.ugent.be' PROXY_ADDRESS_FORWARDING: 'true' @@ -65,25 +65,25 @@ services: - '443:443/tcp' command: # Add Docker provider - - "--providers.docker=true" - - "--providers.docker.exposedbydefault=false" + - '--providers.docker=true' + - '--providers.docker.exposedbydefault=false' # Add web entrypoint - - "--entrypoints.web.address=:80/tcp" - - "--entrypoints.web.http.redirections.entryPoint.to=websecure" - - "--entrypoints.web.http.redirections.entryPoint.scheme=https" + - '--entrypoints.web.address=:80/tcp' + - '--entrypoints.web.http.redirections.entryPoint.to=websecure' + - '--entrypoints.web.http.redirections.entryPoint.scheme=https' # Add websecure entrypoint - - "--entrypoints.websecure.address=:443/tcp" - - "--entrypoints.websecure.http.tls=true" - - "--entrypoints.websecure.http.tls.certResolver=letsencrypt" - - "--entrypoints.websecure.http.tls.domains[0].main=sel2-1.ugent.be" + - '--entrypoints.websecure.address=:443/tcp' + - '--entrypoints.websecure.http.tls=true' + - '--entrypoints.websecure.http.tls.certResolver=letsencrypt' + - '--entrypoints.websecure.http.tls.domains[0].main=sel2-1.ugent.be' # Certificates - - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true" - - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web" - - "--certificatesresolvers.letsencrypt.acme.email=timo.demeyst@ugent.be" - - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" + - '--certificatesresolvers.letsencrypt.acme.httpchallenge=true' + - '--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web' + - '--certificatesresolvers.letsencrypt.acme.email=timo.demeyst@ugent.be' + - '--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json' restart: unless-stopped volumes: - /var/run/docker.sock:/var/run/docker.sock:ro diff --git a/compose.yml b/compose.yml index 3c4bff9a..1276c1af 100644 --- a/compose.yml +++ b/compose.yml @@ -21,7 +21,7 @@ services: ports: - '7080:7080' # - '7443:7443' - command: [ 'start-dev', '--http-port', '7080', '--https-port', '7443', '--import-realm' ] + command: ['start-dev', '--http-port', '7080', '--https-port', '7443', '--import-realm'] restart: unless-stopped volumes: - ./config/idp:/opt/keycloak/data/import diff --git a/config/idp/student-realm.json b/config/idp/student-realm.json index 0c1d45f5..32107e4e 100644 --- a/config/idp/student-realm.json +++ b/config/idp/student-realm.json @@ -620,7 +620,15 @@ "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-jwt", - "redirectUris": ["urn:ietf:wg:oauth:2.0:oob", "http://localhost:5173/*", "http://localhost:5173", "http://localhost/*", "http://localhost", "https://sel2-1.ugent.be/*", "https://sel2-1.ugent.be"], + "redirectUris": [ + "urn:ietf:wg:oauth:2.0:oob", + "http://localhost:5173/*", + "http://localhost:5173", + "http://localhost/*", + "http://localhost", + "https://sel2-1.ugent.be/*", + "https://sel2-1.ugent.be" + ], "webOrigins": ["+"], "notBefore": 0, "bearerOnly": false, diff --git a/config/idp/teacher-realm.json b/config/idp/teacher-realm.json index 6bfd0c6b..b9d29dcb 100644 --- a/config/idp/teacher-realm.json +++ b/config/idp/teacher-realm.json @@ -620,7 +620,15 @@ "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "redirectUris": ["urn:ietf:wg:oauth:2.0:oob", "http://localhost:5173/*", "http://localhost:5173", "http://localhost/*", "http://localhost", "https://sel2-1.ugent.be/*", "https://sel2-1.ugent.be"], + "redirectUris": [ + "urn:ietf:wg:oauth:2.0:oob", + "http://localhost:5173/*", + "http://localhost:5173", + "http://localhost/*", + "http://localhost", + "https://sel2-1.ugent.be/*", + "https://sel2-1.ugent.be" + ], "webOrigins": ["+"], "notBefore": 0, "bearerOnly": false, diff --git a/frontend/src/config.ts b/frontend/src/config.ts index 3f303846..53d6f253 100644 --- a/frontend/src/config.ts +++ b/frontend/src/config.ts @@ -1,5 +1,8 @@ export const apiConfig = { - baseUrl: (window.location.hostname === "localhost" && !(window.location.port === '80' || window.location.port === '')) ? "http://localhost:3000/api" : window.location.origin + "/api", + baseUrl: + window.location.hostname === "localhost" && !(window.location.port === "80" || window.location.port === "") + ? "http://localhost:3000/api" + : window.location.origin + "/api", }; export const loginRoute = "/login"; From c9e95f4429796c17c55ce5b8cfaf8fd756739107 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 13 Mar 2025 21:22:05 +0100 Subject: [PATCH 094/106] refactor(backend): Gebruik /api router --- backend/src/app.ts | 33 +++------------------------------ backend/src/routes/router.ts | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 30 deletions(-) create mode 100644 backend/src/routes/router.ts diff --git a/backend/src/app.ts b/backend/src/app.ts index 436af86f..cad68420 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -1,23 +1,13 @@ -import express, { Express, Response } from 'express'; +import express, { Express } from 'express'; import { initORM } from './orm.js'; -import themeRoutes from './routes/themes.js'; -import learningPathRoutes from './routes/learning-paths.js'; -import learningObjectRoutes from './routes/learning-objects.js'; - -import studentRouter from './routes/student.js'; -import groupRouter from './routes/group.js'; -import assignmentRouter from './routes/assignment.js'; -import submissionRouter from './routes/submission.js'; -import classRouter from './routes/class.js'; -import questionRouter from './routes/question.js'; -import authRouter from './routes/auth.js'; import { authenticateUser } from './middleware/auth/auth.js'; 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 apiRouter from './routes/router.js'; const logger: Logger = getLogger(); @@ -29,24 +19,7 @@ app.use(express.json()); app.use(responseTime(responseTimeLogger)); app.use(authenticateUser); -// TODO Replace with Express routes -app.get('/api/', (_, res: Response) => { - logger.debug('GET /api/'); - res.json({ - message: 'Hello Dwengo!🚀', - }); -}); - -app.use('/api/student', studentRouter); -app.use('/api/group', groupRouter); -app.use('/api/assignment', assignmentRouter); -app.use('/api/submission', submissionRouter); -app.use('/api/class', classRouter); -app.use('/api/question', questionRouter); -app.use('/api/auth', authRouter); -app.use('/api/theme', themeRoutes); -app.use('/api/learningPath', learningPathRoutes); -app.use('/api/learningObject', learningObjectRoutes); +app.get('/api', apiRouter); async function startServer() { await initORM(); diff --git a/backend/src/routes/router.ts b/backend/src/routes/router.ts new file mode 100644 index 00000000..391f3ab5 --- /dev/null +++ b/backend/src/routes/router.ts @@ -0,0 +1,35 @@ +import { Response, Router } from 'express'; +import studentRouter from './student'; +import groupRouter from './group'; +import assignmentRouter from './assignment'; +import submissionRouter from './submission'; +import classRouter from './class'; +import questionRouter from './question'; +import authRouter from './auth'; +import themeRoutes from './themes'; +import learningPathRoutes from './learning-paths'; +import learningObjectRoutes from './learning-objects'; +import { getLogger, Logger } from '../logging/initalize'; + +const router = Router(); +const logger: Logger = getLogger(); + +router.get('/', (_, res: Response) => { + logger.debug('GET /'); + res.json({ + message: 'Hello Dwengo!🚀', + }); +}); + +router.use('/student', studentRouter); +router.use('/group', groupRouter); +router.use('/assignment', assignmentRouter); +router.use('/submission', submissionRouter); +router.use('/class', classRouter); +router.use('/question', questionRouter); +router.use('/auth', authRouter); +router.use('/theme', themeRoutes); +router.use('/learningPath', learningPathRoutes); +router.use('/learningObject', learningObjectRoutes); + +export default router; From f199ceb945c7d5127229faf3cfbda348310c3e8f Mon Sep 17 00:00:00 2001 From: Gerald Schmittinger Date: Thu, 13 Mar 2025 21:35:55 +0100 Subject: [PATCH 095/106] fix(backend): Kleinere fouten opgelost. --- backend/src/controllers/classes.ts | 5 ++--- backend/src/controllers/groups.ts | 2 +- backend/src/entities/assignments/submission.entity.ts | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/backend/src/controllers/classes.ts b/backend/src/controllers/classes.ts index 6fb58fb9..ca2f5698 100644 --- a/backend/src/controllers/classes.ts +++ b/backend/src/controllers/classes.ts @@ -1,7 +1,6 @@ import { Request, Response } from 'express'; import { createClass, getAllClasses, getClass, getClassStudents, getClassStudentsIds, getClassTeacherInvitations } from '../services/class.js'; -import { ClassDTO, mapToClass } from '../interfaces/class.js'; -import { getClassRepository, getStudentRepository, getTeacherRepository } from '../data/repositories.js'; +import { ClassDTO } from '../interfaces/class.js'; export async function getAllClassesHandler(req: Request, res: Response): Promise { const full = req.query.full === 'true'; @@ -38,7 +37,7 @@ export async function getClassHandler(req: Request, res: Response): Promise Date: Thu, 13 Mar 2025 21:46:41 +0100 Subject: [PATCH 096/106] fix(backend): Verloren Swagger setup --- backend/src/app.ts | 5 +++++ backend/src/routes/auth.ts | 6 +----- docs/api/generate.ts | 6 ++---- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/backend/src/app.ts b/backend/src/app.ts index 1f518a4a..a69d3f10 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -7,6 +7,8 @@ import { responseTimeLogger } from './logging/responseTimeLogger.js'; import responseTime from 'response-time'; import { EnvVars, getNumericEnvVar } from './util/envvars.js'; import apiRouter from './routes/router.js'; +import swaggerMiddleware from './swagger'; +import swaggerUi from 'swagger-ui-express'; const logger: Logger = getLogger(); @@ -19,8 +21,11 @@ app.use(authenticateUser); // Add response time logging app.use(responseTime(responseTimeLogger)); +// Swagger app.get('/api', apiRouter); +app.use('/api-docs', swaggerUi.serve, swaggerMiddleware); + async function startServer() { await initORM(); diff --git a/backend/src/routes/auth.ts b/backend/src/routes/auth.ts index 35c805e9..778e51fd 100644 --- a/backend/src/routes/auth.ts +++ b/backend/src/routes/auth.ts @@ -1,10 +1,6 @@ import express from 'express'; import { getFrontendAuthConfig } from '../controllers/auth.js'; -import { - authenticatedOnly, - studentsOnly, - teachersOnly, -} from '../middleware/auth/auth.js'; +import { authenticatedOnly, studentsOnly, teachersOnly } from '../middleware/auth/auth.js'; const router = express.Router(); // Returns auth configuration for frontend diff --git a/docs/api/generate.ts b/docs/api/generate.ts index 32f3922d..87814c19 100644 --- a/docs/api/generate.ts +++ b/docs/api/generate.ts @@ -26,8 +26,7 @@ const doc = { type: 'oauth2', flows: { implicit: { - authorizationUrl: - 'http://localhost:7080/realms/student/protocol/openid-connect/auth', + authorizationUrl: 'http://localhost:7080/realms/student/protocol/openid-connect/auth', scopes: { openid: 'openid', profile: 'profile', @@ -40,8 +39,7 @@ const doc = { type: 'oauth2', flows: { implicit: { - authorizationUrl: - 'http://localhost:7080/realms/teacher/protocol/openid-connect/auth', + authorizationUrl: 'http://localhost:7080/realms/teacher/protocol/openid-connect/auth', scopes: { openid: 'openid', profile: 'profile', From d9c01f1143fe77066bf54e0ac3d09d06cf22ea34 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 13 Mar 2025 21:51:25 +0100 Subject: [PATCH 097/106] fix(backend): Swagger niet bereikbaar --- backend/Dockerfile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/backend/Dockerfile b/backend/Dockerfile index 5f26847c..784e22b7 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -14,6 +14,12 @@ RUN npm install --silent # Root tsconfig.json COPY tsconfig.json ./ +WORKDIR /app/docs + +COPY docs ./ + +RUN npm run swagger + WORKDIR /app/backend COPY backend ./ @@ -29,6 +35,7 @@ COPY package-lock.json backend/package.json ./ RUN npm install --silent --only=production COPY --from=build-stage /app/backend/dist ./dist/ +COPY --from=build-stage /app/docs/swagger ./dist/swagger EXPOSE 3000 From 942df9634fa5b36848341705138db6bad9fdd47f Mon Sep 17 00:00:00 2001 From: Laure Jablonski Date: Thu, 13 Mar 2025 21:56:02 +0100 Subject: [PATCH 098/106] fix: onnodige imports weg --- backend/src/app.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/backend/src/app.ts b/backend/src/app.ts index b6678af3..cad68420 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -1,19 +1,6 @@ import express, { Express } from 'express'; import { initORM } from './orm.js'; -import themeRoutes from './routes/themes.js'; -import learningPathRoutes from './routes/learning-paths.js'; -import learningObjectRoutes from './routes/learning-objects.js'; - -import studentRouter from './routes/students.js'; -import teacherRouter from './routes/teachers.js'; -import groupRouter from './routes/groups.js'; -import assignmentRouter from './routes/assignments.js'; -import submissionRouter from './routes/submissions.js'; -import classRouter from './routes/classes.js'; -import questionRouter from './routes/questions.js'; -import authRouter from './routes/auth.js'; - import { authenticateUser } from './middleware/auth/auth.js'; import cors from './middleware/cors.js'; import { getLogger, Logger } from './logging/initalize.js'; From 069a003d31fbcfb6739918ca6bdd0df98e00589a Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 13 Mar 2025 21:57:24 +0100 Subject: [PATCH 099/106] fix(backend): Swagger niet bereikbaar --- backend/Dockerfile | 2 +- backend/src/app.ts | 2 +- backend/src/routes/router.ts | 22 +++++++++++----------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 784e22b7..2d50437c 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -35,7 +35,7 @@ COPY package-lock.json backend/package.json ./ RUN npm install --silent --only=production COPY --from=build-stage /app/backend/dist ./dist/ -COPY --from=build-stage /app/docs/swagger ./dist/swagger +COPY --from=build-stage /app/docs/api ./docs/swagger EXPOSE 3000 diff --git a/backend/src/app.ts b/backend/src/app.ts index a69d3f10..eb715a1c 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -7,7 +7,7 @@ import { responseTimeLogger } from './logging/responseTimeLogger.js'; import responseTime from 'response-time'; import { EnvVars, getNumericEnvVar } from './util/envvars.js'; import apiRouter from './routes/router.js'; -import swaggerMiddleware from './swagger'; +import swaggerMiddleware from './swagger.js'; import swaggerUi from 'swagger-ui-express'; const logger: Logger = getLogger(); diff --git a/backend/src/routes/router.ts b/backend/src/routes/router.ts index d40b54c9..ef24000e 100644 --- a/backend/src/routes/router.ts +++ b/backend/src/routes/router.ts @@ -1,15 +1,15 @@ import { Response, Router } from 'express'; -import studentRouter from './student'; -import groupRouter from './group'; -import assignmentRouter from './assignment'; -import submissionRouter from './submission'; -import classRouter from './class'; -import questionRouter from './question'; -import authRouter from './auth'; -import themeRoutes from './themes'; -import learningPathRoutes from './learning-paths'; -import learningObjectRoutes from './learning-objects'; -import { getLogger, Logger } from '../logging/initalize'; +import studentRouter from './student.js'; +import groupRouter from './group.js'; +import assignmentRouter from './assignment.js'; +import submissionRouter from './submission.js'; +import classRouter from './class.js'; +import questionRouter from './question.js'; +import authRouter from './auth.js'; +import themeRoutes from './themes.js'; +import learningPathRoutes from './learning-paths.js'; +import learningObjectRoutes from './learning-objects.js'; +import { getLogger, Logger } from '../logging/initalize.js'; const router = Router(); const logger: Logger = getLogger(); From 9f67e7783b72194c22bd64787587a48eed52a219 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 13 Mar 2025 23:28:51 +0100 Subject: [PATCH 100/106] fix(docs): Swagger configuratie --- docs/api/generate.ts | 6 +- docs/api/swagger.json | 2827 ++++++++++++++++++++++++++++++----------- 2 files changed, 2097 insertions(+), 736 deletions(-) diff --git a/docs/api/generate.ts b/docs/api/generate.ts index 87814c19..baed224d 100644 --- a/docs/api/generate.ts +++ b/docs/api/generate.ts @@ -16,7 +16,7 @@ const doc = { description: 'Development server', }, { - url: 'https://sel2-1.ugent.be/api', + url: 'https://sel2-1.ugent.be/', description: 'Production server', }, ], @@ -26,7 +26,7 @@ const doc = { type: 'oauth2', flows: { implicit: { - authorizationUrl: 'http://localhost:7080/realms/student/protocol/openid-connect/auth', + authorizationUrl: 'https://sel2-1.ugent.be/idp/realms/student/protocol/openid-connect/auth', scopes: { openid: 'openid', profile: 'profile', @@ -39,7 +39,7 @@ const doc = { type: 'oauth2', flows: { implicit: { - authorizationUrl: 'http://localhost:7080/realms/teacher/protocol/openid-connect/auth', + authorizationUrl: 'https://sel2-1.ugent.be/idp/realms/teacher/protocol/openid-connect/auth', scopes: { openid: 'openid', profile: 'profile', diff --git a/docs/api/swagger.json b/docs/api/swagger.json index 22337c4b..8d21d094 100644 --- a/docs/api/swagger.json +++ b/docs/api/swagger.json @@ -1,735 +1,2096 @@ { - "openapi": "3.1.0", - "info": { - "version": "0.1.0", - "title": "Dwengo-1 Backend API", - "description": "Dwengo-1 Backend API using Express, based on VZW Dwengo", - "license": { - "name": "MIT", - "url": "https://github.com/SELab-2/Dwengo-1/blob/336496ab6352ee3f8bf47490c90b5cf81526cef6/LICENSE" - } - }, - "servers": [ - { - "url": "http://localhost:3000/", - "description": "Development server" - }, - { - "url": "https://sel2-1.ugent.be/api", - "description": "Production server" - } - ], - "paths": { - "/": { - "get": { - "description": "", - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/student/": { - "get": { - "tags": ["Student"], - "description": "", - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/student/{id}": { - "get": { - "tags": ["Student"], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/student/{id}/classes": { - "get": { - "tags": ["Student"], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/student/{id}/submissions": { - "get": { - "tags": ["Student"], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/student/{id}/assignments": { - "get": { - "tags": ["Student"], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/student/{id}/groups": { - "get": { - "tags": ["Student"], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/group/": { - "get": { - "tags": ["Group"], - "description": "", - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/group/{id}": { - "get": { - "tags": ["Group"], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/group/{id}/question": { - "get": { - "tags": ["Group"], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/assignment/": { - "get": { - "tags": ["Assignment"], - "description": "", - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/assignment/{id}": { - "get": { - "tags": ["Assignment"], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/assignment/{id}/submissions": { - "get": { - "tags": ["Assignment"], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/assignment/{id}/groups": { - "get": { - "tags": ["Assignment"], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/assignment/{id}/questions": { - "get": { - "tags": ["Assignment"], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/submission/": { - "get": { - "tags": ["Submission"], - "description": "", - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/submission/{id}": { - "get": { - "tags": ["Submission"], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/class/": { - "get": { - "tags": ["Class"], - "description": "", - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/class/{id}": { - "get": { - "tags": ["Class"], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/class/{id}/invitations": { - "get": { - "tags": ["Class"], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/class/{id}/assignments": { - "get": { - "tags": ["Class"], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/class/{id}/students": { - "get": { - "tags": ["Class"], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/question/": { - "get": { - "tags": ["Question"], - "description": "", - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/question/{id}": { - "get": { - "tags": ["Question"], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/question/{id}/answers": { - "get": { - "tags": ["Question"], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/auth/config": { - "get": { - "tags": ["Auth"], - "description": "", - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/auth/testAuthenticatedOnly": { - "get": { - "tags": ["Auth"], - "description": "", - "responses": { - "200": { - "description": "OK" - } - }, - "security": [ - { - "student": [] - }, - { - "teacher": [] - } - ] - } - }, - "/auth/testStudentsOnly": { - "get": { - "tags": ["Auth"], - "description": "", - "responses": { - "200": { - "description": "OK" - } - }, - "security": [ - { - "student": [] - } - ] - } - }, - "/auth/testTeachersOnly": { - "get": { - "tags": ["Auth"], - "description": "", - "responses": { - "200": { - "description": "OK" - } - }, - "security": [ - { - "teacher": [] - } - ] - } - }, - "/theme/": { - "get": { - "tags": ["Theme"], - "description": "", - "parameters": [ - { - "name": "language", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/theme/{theme}": { - "get": { - "tags": ["Theme"], - "description": "", - "parameters": [ - { - "name": "theme", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "404": { - "description": "Not Found" - } - } - } - }, - "/learningPath/": { - "get": { - "tags": ["Learning Path"], - "description": "", - "parameters": [ - { - "name": "hruid", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "theme", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "search", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "language", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "404": { - "description": "Not Found" - }, - "500": { - "description": "Internal Server Error" - } - } - } - }, - "/learningObject/": { - "get": { - "tags": ["Learning Object"], - "description": "", - "parameters": [ - { - "name": "hruid", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "full", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "language", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "400": { - "description": "Bad Request" - }, - "500": { - "description": "Internal Server Error" - } - } - } - }, - "/learningObject/{hruid}": { - "get": { - "tags": ["Learning Object"], - "description": "", - "parameters": [ - { - "name": "hruid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "language", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "400": { - "description": "Bad Request" - }, - "500": { - "description": "Internal Server Error" - } - } - } - } - }, - "components": { - "securitySchemes": { - "student": { - "type": "oauth2", - "flows": { - "implicit": { - "authorizationUrl": "http://localhost:7080/realms/student/protocol/openid-connect/auth", - "scopes": { - "openid": "openid", - "profile": "profile", - "email": "email" - } - } - } - }, - "teacher": { - "type": "oauth2", - "flows": { - "implicit": { - "authorizationUrl": "http://localhost:7080/realms/teacher/protocol/openid-connect/auth", - "scopes": { - "openid": "openid", - "profile": "profile", - "email": "email" - } - } - } - } - } + "openapi": "3.1.0", + "info": { + "version": "0.1.0", + "title": "Dwengo-1 Backend API", + "description": "Dwengo-1 Backend API using Express, based on VZW Dwengo", + "license": { + "name": "MIT", + "url": "https://github.com/SELab-2/Dwengo-1/blob/336496ab6352ee3f8bf47490c90b5cf81526cef6/LICENSE" } -} + }, + "servers": [ + { + "url": "http://localhost:3000/", + "description": "Development server" + }, + { + "url": "https://sel2-1.ugent.be/", + "description": "Production server" + } + ], + "paths": { + "/api/": { + "get": { + "description": "", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/student/": { + "get": { + "tags": [ + "Student" + ], + "description": "", + "parameters": [ + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "Created" + }, + "404": { + "description": "Not Found" + } + } + }, + "post": { + "tags": [ + "Student" + ], + "description": "", + "responses": { + "201": { + "description": "Created" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "username": { + "example": "any" + }, + "firstName": { + "example": "any" + }, + "lastName": { + "example": "any" + } + } + } + } + } + } + }, + "delete": { + "tags": [ + "Student" + ], + "description": "", + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/api/student/{username}": { + "delete": { + "tags": [ + "Student" + ], + "description": "", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "404": { + "description": "Not Found" + } + } + }, + "get": { + "tags": [ + "Student" + ], + "description": "", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "Created" + }, + "400": { + "description": "Bad Request" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/api/student/{id}/classes": { + "get": { + "tags": [ + "Student" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/api/student/{id}/submissions": { + "get": { + "tags": [ + "Student" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/student/{id}/assignments": { + "get": { + "tags": [ + "Student" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/student/{id}/groups": { + "get": { + "tags": [ + "Student" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/student/{id}/questions": { + "get": { + "tags": [ + "Student" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/group/": { + "get": { + "tags": [ + "Group" + ], + "description": "", + "parameters": [ + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + }, + "post": { + "tags": [ + "Group" + ], + "description": "", + "responses": { + "201": { + "description": "Created" + }, + "400": { + "description": "Bad Request" + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/api/group/{groupid}": { + "get": { + "tags": [ + "Group" + ], + "description": "", + "parameters": [ + { + "name": "groupid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/group/{id}/questions": { + "get": { + "tags": [ + "Group" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/assignment/": { + "get": { + "tags": [ + "Assignment" + ], + "description": "", + "parameters": [ + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + }, + "post": { + "tags": [ + "Assignment" + ], + "description": "", + "responses": { + "201": { + "description": "Created" + }, + "400": { + "description": "Bad Request" + }, + "500": { + "description": "Internal Server Error" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "description": { + "example": "any" + }, + "language": { + "example": "any" + }, + "learningPath": { + "example": "any" + }, + "title": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/assignment/{id}": { + "get": { + "tags": [ + "Assignment" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/api/assignment/{id}/submissions": { + "get": { + "tags": [ + "Assignment" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/assignment/{id}/questions": { + "get": { + "tags": [ + "Assignment" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/assignment/{assignmentid}/groups/": { + "get": { + "tags": [ + "Assignment" + ], + "description": "", + "parameters": [ + { + "name": "assignmentid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + }, + "post": { + "tags": [ + "Assignment" + ], + "description": "", + "parameters": [ + { + "name": "assignmentid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "Created" + }, + "400": { + "description": "Bad Request" + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/api/assignment/{assignmentid}/groups/{groupid}": { + "get": { + "tags": [ + "Assignment" + ], + "description": "", + "parameters": [ + { + "name": "assignmentid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "groupid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/assignment/{assignmentid}/groups/{id}/questions": { + "get": { + "tags": [ + "Assignment" + ], + "description": "", + "parameters": [ + { + "name": "assignmentid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/submission/": { + "get": { + "tags": [ + "Submission" + ], + "description": "", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/submission/{id}": { + "post": { + "tags": [ + "Submission" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + } + } + }, + "get": { + "tags": [ + "Submission" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "language", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "version", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "404": { + "description": "Not Found" + } + } + }, + "delete": { + "tags": [ + "Submission" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "language", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "version", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/api/class/": { + "get": { + "tags": [ + "Class" + ], + "description": "", + "parameters": [ + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + }, + "post": { + "tags": [ + "Class" + ], + "description": "", + "responses": { + "201": { + "description": "Created" + }, + "400": { + "description": "Bad Request" + }, + "500": { + "description": "Internal Server Error" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "displayName": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/class/{id}": { + "get": { + "tags": [ + "Class" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/api/class/{id}/teacher-invitations": { + "get": { + "tags": [ + "Class" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/class/{id}/students": { + "get": { + "tags": [ + "Class" + ], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/class/{classid}/assignments/": { + "get": { + "tags": [ + "Class" + ], + "description": "", + "parameters": [ + { + "name": "classid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + }, + "post": { + "tags": [ + "Class" + ], + "description": "", + "parameters": [ + { + "name": "classid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "Created" + }, + "400": { + "description": "Bad Request" + }, + "500": { + "description": "Internal Server Error" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "description": { + "example": "any" + }, + "language": { + "example": "any" + }, + "learningPath": { + "example": "any" + }, + "title": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/class/{classid}/assignments/{id}": { + "get": { + "tags": [ + "Class" + ], + "description": "", + "parameters": [ + { + "name": "classid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/api/class/{classid}/assignments/{id}/submissions": { + "get": { + "tags": [ + "Class" + ], + "description": "", + "parameters": [ + { + "name": "classid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/class/{classid}/assignments/{id}/questions": { + "get": { + "tags": [ + "Class" + ], + "description": "", + "parameters": [ + { + "name": "classid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/class/{classid}/assignments/{assignmentid}/groups/": { + "get": { + "tags": [ + "Class" + ], + "description": "", + "parameters": [ + { + "name": "classid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "assignmentid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + }, + "post": { + "tags": [ + "Class" + ], + "description": "", + "parameters": [ + { + "name": "classid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "assignmentid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "Created" + }, + "400": { + "description": "Bad Request" + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/api/class/{classid}/assignments/{assignmentid}/groups/{groupid}": { + "get": { + "tags": [ + "Class" + ], + "description": "", + "parameters": [ + { + "name": "classid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "assignmentid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "groupid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/class/{classid}/assignments/{assignmentid}/groups/{id}/questions": { + "get": { + "tags": [ + "Class" + ], + "description": "", + "parameters": [ + { + "name": "classid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "assignmentid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/question/": { + "get": { + "tags": [ + "Question" + ], + "description": "", + "parameters": [ + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + } + } + }, + "post": { + "tags": [ + "Question" + ], + "description": "", + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "learningObjectIdentifier": { + "example": "any" + }, + "author": { + "example": "any" + }, + "content": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/question/{seq}": { + "delete": { + "tags": [ + "Question" + ], + "description": "", + "parameters": [ + { + "name": "seq", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + }, + "get": { + "tags": [ + "Question" + ], + "description": "", + "parameters": [ + { + "name": "seq", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/api/question/answers/{seq}": { + "get": { + "tags": [ + "Question" + ], + "description": "", + "parameters": [ + { + "name": "seq", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/api/auth/config": { + "get": { + "tags": [ + "Auth" + ], + "description": "", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/auth/testAuthenticatedOnly": { + "get": { + "tags": [ + "Auth" + ], + "description": "", + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "student": [] + }, + { + "teacher": [] + } + ] + } + }, + "/api/auth/testStudentsOnly": { + "get": { + "tags": [ + "Auth" + ], + "description": "", + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "student": [] + } + ] + } + }, + "/api/auth/testTeachersOnly": { + "get": { + "tags": [ + "Auth" + ], + "description": "", + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "teacher": [] + } + ] + } + }, + "/api/theme/": { + "get": { + "tags": [ + "Theme" + ], + "description": "", + "parameters": [ + { + "name": "language", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/theme/{theme}": { + "get": { + "tags": [ + "Theme" + ], + "description": "", + "parameters": [ + { + "name": "theme", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/api/learningPath/": { + "get": { + "tags": [ + "Learning Path" + ], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "theme", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "search", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "language", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "forStudent", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "forGroup", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "assignmentNo", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "classId", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/learningObject/": { + "get": { + "tags": [ + "Learning Object" + ], + "description": "", + "parameters": [ + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/learningObject/{hruid}": { + "get": { + "tags": [ + "Learning Object" + ], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/learningObject/{hruid}/html": { + "get": { + "tags": [ + "Learning Object" + ], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/learningObject/{hruid}/html/{attachmentName}": { + "get": { + "tags": [ + "Learning Object" + ], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "attachmentName", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "default": { + "description": "" + } + } + } + }, + "/api/learningObject/{hruid}/submissions/": { + "get": { + "tags": [ + "Learning Object" + ], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/learningObject/{hruid}/submissions/{id}": { + "post": { + "tags": [ + "Learning Object" + ], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + } + } + }, + "get": { + "tags": [ + "Learning Object" + ], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "language", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "version", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "404": { + "description": "Not Found" + } + } + }, + "delete": { + "tags": [ + "Learning Object" + ], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "language", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "version", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/api/learningObject/{hruid}/{version}/questions/": { + "get": { + "tags": [ + "Learning Object" + ], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "version", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + } + } + }, + "post": { + "tags": [ + "Learning Object" + ], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "version", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "learningObjectIdentifier": { + "example": "any" + }, + "author": { + "example": "any" + }, + "content": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/learningObject/{hruid}/{version}/questions/{seq}": { + "delete": { + "tags": [ + "Learning Object" + ], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "version", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "seq", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + }, + "get": { + "tags": [ + "Learning Object" + ], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "version", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "seq", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/api/learningObject/{hruid}/{version}/questions/answers/{seq}": { + "get": { + "tags": [ + "Learning Object" + ], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "version", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "seq", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + } + } + } + } + }, + "components": { + "securitySchemes": { + "student": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://sel2-1.ugent.be/idp/realms/student/protocol/openid-connect/auth", + "scopes": { + "openid": "openid", + "profile": "profile", + "email": "email" + } + } + } + }, + "teacher": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://sel2-1.ugent.be/idp/realms/teacher/protocol/openid-connect/auth", + "scopes": { + "openid": "openid", + "profile": "profile", + "email": "email" + } + } + } + } + } + } +} \ No newline at end of file From 5010ee3b0bfd6ef7bf26d6e1ae2fc5379eecc678 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 13 Mar 2025 23:34:40 +0100 Subject: [PATCH 101/106] fix(backend): Import errors --- backend/src/routes/router.ts | 12 ++++++------ backend/src/services/class.ts | 2 +- backend/src/swagger.ts | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/src/routes/router.ts b/backend/src/routes/router.ts index ef24000e..639857a7 100644 --- a/backend/src/routes/router.ts +++ b/backend/src/routes/router.ts @@ -1,10 +1,10 @@ import { Response, Router } from 'express'; -import studentRouter from './student.js'; -import groupRouter from './group.js'; -import assignmentRouter from './assignment.js'; -import submissionRouter from './submission.js'; -import classRouter from './class.js'; -import questionRouter from './question.js'; +import studentRouter from './students.js'; +import groupRouter from './groups.js'; +import assignmentRouter from './assignments.js'; +import submissionRouter from './submissions.js'; +import classRouter from './classes.js'; +import questionRouter from './questions.js'; import authRouter from './auth.js'; import themeRoutes from './themes.js'; import learningPathRoutes from './learning-paths.js'; diff --git a/backend/src/services/class.ts b/backend/src/services/class.ts index 117bffec..9f6e1efe 100644 --- a/backend/src/services/class.ts +++ b/backend/src/services/class.ts @@ -3,7 +3,7 @@ import { Class } from '../entities/classes/class.entity.js'; import { ClassDTO, mapToClassDTO } from '../interfaces/class.js'; import { mapToStudentDTO, StudentDTO } from '../interfaces/student.js'; import { mapToTeacherInvitationDTO, mapToTeacherInvitationDTOIds, TeacherInvitationDTO } from '../interfaces/teacher-invitation.js'; -import { getLogger } from '../logging/initalize'; +import { getLogger } from '../logging/initalize.js'; const logger = getLogger(); diff --git a/backend/src/swagger.ts b/backend/src/swagger.ts index 97b08496..5845030b 100644 --- a/backend/src/swagger.ts +++ b/backend/src/swagger.ts @@ -1,6 +1,6 @@ import { RequestHandler } from 'express'; import swaggerUi from 'swagger-ui-express'; -import swaggerDocument from '../../docs/api/swagger.json'; +import swaggerDocument from '../../docs/api/swagger.json' with { type: 'json' }; const swaggerMiddleware: RequestHandler = swaggerUi.setup(swaggerDocument); From 6b66b2e366b7c5f2ff75b08dd3537dd623c48285 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 13 Mar 2025 23:35:25 +0100 Subject: [PATCH 102/106] fix(backend): Use /api i.p.v. get --- backend/src/app.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/app.ts b/backend/src/app.ts index eb715a1c..0c5e8892 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -21,9 +21,9 @@ app.use(authenticateUser); // Add response time logging app.use(responseTime(responseTimeLogger)); -// Swagger -app.get('/api', apiRouter); +app.use('/api', apiRouter); +// Swagger app.use('/api-docs', swaggerUi.serve, swaggerMiddleware); async function startServer() { From e9afb9461048643f030d7422b32d7ef408170522 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 13 Mar 2025 23:35:57 +0100 Subject: [PATCH 103/106] ci: Fix Swagger build --- backend/Dockerfile | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 2d50437c..bd7db2ff 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -14,15 +14,10 @@ RUN npm install --silent # Root tsconfig.json COPY tsconfig.json ./ -WORKDIR /app/docs - -COPY docs ./ - -RUN npm run swagger - WORKDIR /app/backend COPY backend ./ +COPY docs /app/docs RUN npm run build @@ -34,8 +29,8 @@ COPY package-lock.json backend/package.json ./ RUN npm install --silent --only=production +COPY ./docs /docs COPY --from=build-stage /app/backend/dist ./dist/ -COPY --from=build-stage /app/docs/api ./docs/swagger EXPOSE 3000 From c735f1e36453b52edd4f4ec376de0960eb57d5ca Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Thu, 13 Mar 2025 23:37:17 +0100 Subject: [PATCH 104/106] chore: Incrementeer pkg versie --- backend/package.json | 2 +- frontend/package.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/package.json b/backend/package.json index 9d7e886a..4e3b890d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "dwengo-1-backend", - "version": "0.0.1", + "version": "0.1.1", "description": "Backend for Dwengo-1", "private": true, "type": "module", diff --git a/frontend/package.json b/frontend/package.json index 6fb13db7..ac1efc4a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "dwengo-1-frontend", - "version": "0.0.1", + "version": "0.1.1", "description": "Frontend for Dwengo-1", "private": true, "type": "module", diff --git a/package.json b/package.json index 09703f20..9a69bdb6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dwengo-1-monorepo", - "version": "0.0.1", + "version": "0.1.1", "description": "Monorepo for Dwengo-1", "private": true, "type": "module", From 79cc175c9ad9e0fa84ea6f9efde82480aeeb73ca Mon Sep 17 00:00:00 2001 From: Joyelle Ndagijimana Date: Sat, 15 Mar 2025 20:09:16 +0100 Subject: [PATCH 105/106] test: testen bug opgelost --- .../tests/service/learning-objects.test.ts | 117 ------------------ backend/tests/service/learning-paths.test.ts | 90 -------------- 2 files changed, 207 deletions(-) delete mode 100644 backend/tests/service/learning-objects.test.ts delete mode 100644 backend/tests/service/learning-paths.test.ts diff --git a/backend/tests/service/learning-objects.test.ts b/backend/tests/service/learning-objects.test.ts deleted file mode 100644 index 130c237e..00000000 --- a/backend/tests/service/learning-objects.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { LearningObjectMetadata, LearningPath } from '../../src/interfaces/learningPath'; -import { fetchWithLogging } from '../../src/util/apiHelper'; -import { getLearningObjectById, getLearningObjectsFromPath } from '../../src/services/learningObjects'; -import { fetchLearningPaths } from '../../src/services/learningPaths'; - -// Mock API functions -vi.mock('../../src/util/apiHelper', () => ({ - fetchWithLogging: vi.fn(), -})); - -vi.mock('../../src/services/learningPaths', () => ({ - fetchLearningPaths: vi.fn(), -})); - -describe('getLearningObjectById', () => { - const hruid = 'test-object'; - const language = 'en'; - const mockMetadata: LearningObjectMetadata = { - hruid, - _id: '123', - uuid: 'uuid-123', - version: 1, - title: 'Test Object', - language, - difficulty: 5, - estimated_time: 120, - available: true, - teacher_exclusive: false, - educational_goals: [{ source: 'source', id: 'id' }], - keywords: ['robotics'], - description: 'A test object', - target_ages: [10, 12], - content_type: 'markdown', - content_location: '', - }; - - it('✅ Should return a filtered learning object when API provides data', async () => { - vi.mocked(fetchWithLogging).mockResolvedValueOnce(mockMetadata); - - const result = await getLearningObjectById(hruid, language); - - expect(result).toEqual({ - key: hruid, - _id: '123', - uuid: 'uuid-123', - version: 1, - title: 'Test Object', - htmlUrl: expect.stringContaining('/learningObject/getRaw?hruid=test-object&language=en'), - language, - difficulty: 5, - estimatedTime: 120, - available: true, - teacherExclusive: false, - educationalGoals: [{ source: 'source', id: 'id' }], - keywords: ['robotics'], - description: 'A test object', - targetAges: [10, 12], - contentType: 'markdown', - contentLocation: '', - }); - }); - - it('⚠️ Should return null if API returns no metadata', async () => { - vi.mocked(fetchWithLogging).mockResolvedValueOnce(null); - const result = await getLearningObjectById(hruid, language); - expect(result).toBeNull(); - }); -}); - -describe('getLearningObjectsFromPath', () => { - const hruid = 'test-path'; - const language = 'en'; - - it('✅ Should not give error or warning', async () => { - const mockPathResponse: LearningPath[] = [ - { - _id: 'path-1', - hruid, - language, - title: 'Test Path', - description: '', - num_nodes: 1, - num_nodes_left: 0, - nodes: [], - keywords: '', - target_ages: [], - min_age: 10, - max_age: 12, - __order: 1, - }, - ]; - - vi.mocked(fetchLearningPaths).mockResolvedValueOnce({ - success: true, - source: 'Test Source', - data: mockPathResponse, - }); - - const result = await getLearningObjectsFromPath(hruid, language); - expect(result).toEqual([]); - }); - - it('⚠️ Should give a warning', async () => { - vi.mocked(fetchLearningPaths).mockResolvedValueOnce({ success: false, source: 'Test Source', data: [] }); - - const result = await getLearningObjectsFromPath(hruid, language); - expect(result).toEqual([]); - }); - - it('❌ Should give an error', async () => { - vi.mocked(fetchLearningPaths).mockRejectedValueOnce(new Error('API Error')); - - const result = await getLearningObjectsFromPath(hruid, language); - expect(result).toEqual([]); - }); -}); diff --git a/backend/tests/service/learning-paths.test.ts b/backend/tests/service/learning-paths.test.ts deleted file mode 100644 index c002dbac..00000000 --- a/backend/tests/service/learning-paths.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { fetchLearningPaths, searchLearningPaths } from '../../src/services/learningPaths'; -import { fetchWithLogging } from '../../src/util/apiHelper'; -import { LearningPathResponse } from '../../src/interfaces/learningPath'; - -// Mock the fetchWithLogging module using vi -vi.mock('../../src/util/apiHelper', () => ({ - fetchWithLogging: vi.fn(), -})); - -describe('fetchLearningPaths', () => { - // Mock data and response - const mockHruids = ['pn_werking', 'art1']; - const language = 'en'; - const source = 'Test Source'; - const mockResponse = [{ title: 'Test Path', hruids: mockHruids }]; - - it('✅ Should return a successful response when HRUIDs are provided', async () => { - // Mock the function to return mockResponse - vi.mocked(fetchWithLogging).mockResolvedValue(mockResponse); - - const result: LearningPathResponse = await fetchLearningPaths(mockHruids, language, source); - - expect(result.success).toBe(true); - expect(result.data).toEqual(mockResponse); - expect(result.source).toBe(source); - }); - - it('⚠️ Should return an error when no HRUIDs are provided', async () => { - vi.mocked(fetchWithLogging).mockResolvedValue(mockResponse); - - const result: LearningPathResponse = await fetchLearningPaths([], language, source); - - expect(result.success).toBe(false); - expect(result.data).toBeNull(); - expect(result.message).toBe(`No HRUIDs provided for ${source}.`); - }); - - it('⚠️ Should return a failure response when no learning paths are found', async () => { - // Mock fetchWithLogging to return an empty array - vi.mocked(fetchWithLogging).mockResolvedValue([]); - - const result: LearningPathResponse = await fetchLearningPaths(mockHruids, language, source); - - expect(result.success).toBe(false); - expect(result.data).toEqual([]); - expect(result.message).toBe(`No learning paths found for ${source}.`); - }); -}); - -describe('searchLearningPaths', () => { - const query = - 'https://dwengo.org/backend/api/learningPath/getPathsFromIdList?pathIdList=%7B%22hruids%22:%5B%22pn_werking%22,%22un_artificiele_intelligentie%22%5D%7D&language=nl'; - const language = 'nl'; - - it('✅ Should return search results when API responds with data', async () => { - const mockResults = [ - { - _id: '67b4488c9dadb305c4104618', - language: 'nl', - hruid: 'pn_werking', - title: 'Werken met notebooks', - description: 'Een korte inleiding tot Python notebooks. Hoe ga je gemakkelijk en efficiënt met de notebooks aan de slag?', - num_nodes: 0, - num_nodes_left: 0, - nodes: [], - keywords: 'Python KIKS Wiskunde STEM AI', - target_ages: [14, 15, 16, 17, 18], - min_age: 14, - max_age: 18, - __order: 0, - }, - ]; - - // Mock fetchWithLogging to return search results - vi.mocked(fetchWithLogging).mockResolvedValue(mockResults); - - const result = await searchLearningPaths(query, language); - - expect(result).toEqual(mockResults); - }); - - it('⚠️ Should return an empty array when API returns no results', async () => { - vi.mocked(fetchWithLogging).mockResolvedValue([]); - - const result = await searchLearningPaths(query, language); - - expect(result).toEqual([]); - }); -}); From 6c3f90bf6be7c57bb348ac03d212da1b7cac20c1 Mon Sep 17 00:00:00 2001 From: Lint Action Date: Sat, 15 Mar 2025 19:21:34 +0000 Subject: [PATCH 106/106] style: fix linting issues met Prettier --- docs/api/swagger.json | 4056 ++++++++++++++++++++--------------------- 1 file changed, 1962 insertions(+), 2094 deletions(-) diff --git a/docs/api/swagger.json b/docs/api/swagger.json index 8d21d094..911839d0 100644 --- a/docs/api/swagger.json +++ b/docs/api/swagger.json @@ -1,2096 +1,1964 @@ { - "openapi": "3.1.0", - "info": { - "version": "0.1.0", - "title": "Dwengo-1 Backend API", - "description": "Dwengo-1 Backend API using Express, based on VZW Dwengo", - "license": { - "name": "MIT", - "url": "https://github.com/SELab-2/Dwengo-1/blob/336496ab6352ee3f8bf47490c90b5cf81526cef6/LICENSE" + "openapi": "3.1.0", + "info": { + "version": "0.1.0", + "title": "Dwengo-1 Backend API", + "description": "Dwengo-1 Backend API using Express, based on VZW Dwengo", + "license": { + "name": "MIT", + "url": "https://github.com/SELab-2/Dwengo-1/blob/336496ab6352ee3f8bf47490c90b5cf81526cef6/LICENSE" + } + }, + "servers": [ + { + "url": "http://localhost:3000/", + "description": "Development server" + }, + { + "url": "https://sel2-1.ugent.be/", + "description": "Production server" + } + ], + "paths": { + "/api/": { + "get": { + "description": "", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/student/": { + "get": { + "tags": ["Student"], + "description": "", + "parameters": [ + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "Created" + }, + "404": { + "description": "Not Found" + } + } + }, + "post": { + "tags": ["Student"], + "description": "", + "responses": { + "201": { + "description": "Created" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "username": { + "example": "any" + }, + "firstName": { + "example": "any" + }, + "lastName": { + "example": "any" + } + } + } + } + } + } + }, + "delete": { + "tags": ["Student"], + "description": "", + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/api/student/{username}": { + "delete": { + "tags": ["Student"], + "description": "", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "404": { + "description": "Not Found" + } + } + }, + "get": { + "tags": ["Student"], + "description": "", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "Created" + }, + "400": { + "description": "Bad Request" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/api/student/{id}/classes": { + "get": { + "tags": ["Student"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/api/student/{id}/submissions": { + "get": { + "tags": ["Student"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/student/{id}/assignments": { + "get": { + "tags": ["Student"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/student/{id}/groups": { + "get": { + "tags": ["Student"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/student/{id}/questions": { + "get": { + "tags": ["Student"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/group/": { + "get": { + "tags": ["Group"], + "description": "", + "parameters": [ + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + }, + "post": { + "tags": ["Group"], + "description": "", + "responses": { + "201": { + "description": "Created" + }, + "400": { + "description": "Bad Request" + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/api/group/{groupid}": { + "get": { + "tags": ["Group"], + "description": "", + "parameters": [ + { + "name": "groupid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/group/{id}/questions": { + "get": { + "tags": ["Group"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/assignment/": { + "get": { + "tags": ["Assignment"], + "description": "", + "parameters": [ + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + }, + "post": { + "tags": ["Assignment"], + "description": "", + "responses": { + "201": { + "description": "Created" + }, + "400": { + "description": "Bad Request" + }, + "500": { + "description": "Internal Server Error" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "description": { + "example": "any" + }, + "language": { + "example": "any" + }, + "learningPath": { + "example": "any" + }, + "title": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/assignment/{id}": { + "get": { + "tags": ["Assignment"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/api/assignment/{id}/submissions": { + "get": { + "tags": ["Assignment"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/assignment/{id}/questions": { + "get": { + "tags": ["Assignment"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/assignment/{assignmentid}/groups/": { + "get": { + "tags": ["Assignment"], + "description": "", + "parameters": [ + { + "name": "assignmentid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + }, + "post": { + "tags": ["Assignment"], + "description": "", + "parameters": [ + { + "name": "assignmentid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "Created" + }, + "400": { + "description": "Bad Request" + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/api/assignment/{assignmentid}/groups/{groupid}": { + "get": { + "tags": ["Assignment"], + "description": "", + "parameters": [ + { + "name": "assignmentid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "groupid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/assignment/{assignmentid}/groups/{id}/questions": { + "get": { + "tags": ["Assignment"], + "description": "", + "parameters": [ + { + "name": "assignmentid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/submission/": { + "get": { + "tags": ["Submission"], + "description": "", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/submission/{id}": { + "post": { + "tags": ["Submission"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + } + } + }, + "get": { + "tags": ["Submission"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "language", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "version", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "404": { + "description": "Not Found" + } + } + }, + "delete": { + "tags": ["Submission"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "language", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "version", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/api/class/": { + "get": { + "tags": ["Class"], + "description": "", + "parameters": [ + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + }, + "post": { + "tags": ["Class"], + "description": "", + "responses": { + "201": { + "description": "Created" + }, + "400": { + "description": "Bad Request" + }, + "500": { + "description": "Internal Server Error" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "displayName": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/class/{id}": { + "get": { + "tags": ["Class"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/api/class/{id}/teacher-invitations": { + "get": { + "tags": ["Class"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/class/{id}/students": { + "get": { + "tags": ["Class"], + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/class/{classid}/assignments/": { + "get": { + "tags": ["Class"], + "description": "", + "parameters": [ + { + "name": "classid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + }, + "post": { + "tags": ["Class"], + "description": "", + "parameters": [ + { + "name": "classid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "Created" + }, + "400": { + "description": "Bad Request" + }, + "500": { + "description": "Internal Server Error" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "description": { + "example": "any" + }, + "language": { + "example": "any" + }, + "learningPath": { + "example": "any" + }, + "title": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/class/{classid}/assignments/{id}": { + "get": { + "tags": ["Class"], + "description": "", + "parameters": [ + { + "name": "classid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/api/class/{classid}/assignments/{id}/submissions": { + "get": { + "tags": ["Class"], + "description": "", + "parameters": [ + { + "name": "classid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/class/{classid}/assignments/{id}/questions": { + "get": { + "tags": ["Class"], + "description": "", + "parameters": [ + { + "name": "classid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/class/{classid}/assignments/{assignmentid}/groups/": { + "get": { + "tags": ["Class"], + "description": "", + "parameters": [ + { + "name": "classid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "assignmentid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + }, + "post": { + "tags": ["Class"], + "description": "", + "parameters": [ + { + "name": "classid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "assignmentid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "Created" + }, + "400": { + "description": "Bad Request" + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/api/class/{classid}/assignments/{assignmentid}/groups/{groupid}": { + "get": { + "tags": ["Class"], + "description": "", + "parameters": [ + { + "name": "classid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "assignmentid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "groupid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/api/class/{classid}/assignments/{assignmentid}/groups/{id}/questions": { + "get": { + "tags": ["Class"], + "description": "", + "parameters": [ + { + "name": "classid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "assignmentid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/question/": { + "get": { + "tags": ["Question"], + "description": "", + "parameters": [ + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + } + } + }, + "post": { + "tags": ["Question"], + "description": "", + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "learningObjectIdentifier": { + "example": "any" + }, + "author": { + "example": "any" + }, + "content": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/question/{seq}": { + "delete": { + "tags": ["Question"], + "description": "", + "parameters": [ + { + "name": "seq", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + }, + "get": { + "tags": ["Question"], + "description": "", + "parameters": [ + { + "name": "seq", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/api/question/answers/{seq}": { + "get": { + "tags": ["Question"], + "description": "", + "parameters": [ + { + "name": "seq", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/api/auth/config": { + "get": { + "tags": ["Auth"], + "description": "", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/auth/testAuthenticatedOnly": { + "get": { + "tags": ["Auth"], + "description": "", + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "student": [] + }, + { + "teacher": [] + } + ] + } + }, + "/api/auth/testStudentsOnly": { + "get": { + "tags": ["Auth"], + "description": "", + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "student": [] + } + ] + } + }, + "/api/auth/testTeachersOnly": { + "get": { + "tags": ["Auth"], + "description": "", + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "teacher": [] + } + ] + } + }, + "/api/theme/": { + "get": { + "tags": ["Theme"], + "description": "", + "parameters": [ + { + "name": "language", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/theme/{theme}": { + "get": { + "tags": ["Theme"], + "description": "", + "parameters": [ + { + "name": "theme", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/api/learningPath/": { + "get": { + "tags": ["Learning Path"], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "theme", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "search", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "language", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "forStudent", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "forGroup", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "assignmentNo", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "classId", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/learningObject/": { + "get": { + "tags": ["Learning Object"], + "description": "", + "parameters": [ + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/learningObject/{hruid}": { + "get": { + "tags": ["Learning Object"], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/learningObject/{hruid}/html": { + "get": { + "tags": ["Learning Object"], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/learningObject/{hruid}/html/{attachmentName}": { + "get": { + "tags": ["Learning Object"], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "attachmentName", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "default": { + "description": "" + } + } + } + }, + "/api/learningObject/{hruid}/submissions/": { + "get": { + "tags": ["Learning Object"], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/learningObject/{hruid}/submissions/{id}": { + "post": { + "tags": ["Learning Object"], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + } + } + }, + "get": { + "tags": ["Learning Object"], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "language", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "version", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "404": { + "description": "Not Found" + } + } + }, + "delete": { + "tags": ["Learning Object"], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "language", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "version", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/api/learningObject/{hruid}/{version}/questions/": { + "get": { + "tags": ["Learning Object"], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "version", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + } + } + }, + "post": { + "tags": ["Learning Object"], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "version", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "learningObjectIdentifier": { + "example": "any" + }, + "author": { + "example": "any" + }, + "content": { + "example": "any" + } + } + } + } + } + } + } + }, + "/api/learningObject/{hruid}/{version}/questions/{seq}": { + "delete": { + "tags": ["Learning Object"], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "version", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "seq", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + }, + "get": { + "tags": ["Learning Object"], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "version", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "seq", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/api/learningObject/{hruid}/{version}/questions/answers/{seq}": { + "get": { + "tags": ["Learning Object"], + "description": "", + "parameters": [ + { + "name": "hruid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "version", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "seq", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "full", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Not Found" + } + } + } + } + }, + "components": { + "securitySchemes": { + "student": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://sel2-1.ugent.be/idp/realms/student/protocol/openid-connect/auth", + "scopes": { + "openid": "openid", + "profile": "profile", + "email": "email" + } + } + } + }, + "teacher": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://sel2-1.ugent.be/idp/realms/teacher/protocol/openid-connect/auth", + "scopes": { + "openid": "openid", + "profile": "profile", + "email": "email" + } + } + } + } + } } - }, - "servers": [ - { - "url": "http://localhost:3000/", - "description": "Development server" - }, - { - "url": "https://sel2-1.ugent.be/", - "description": "Production server" - } - ], - "paths": { - "/api/": { - "get": { - "description": "", - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/api/student/": { - "get": { - "tags": [ - "Student" - ], - "description": "", - "parameters": [ - { - "name": "full", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "201": { - "description": "Created" - }, - "404": { - "description": "Not Found" - } - } - }, - "post": { - "tags": [ - "Student" - ], - "description": "", - "responses": { - "201": { - "description": "Created" - }, - "400": { - "description": "Bad Request" - } - }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "username": { - "example": "any" - }, - "firstName": { - "example": "any" - }, - "lastName": { - "example": "any" - } - } - } - } - } - } - }, - "delete": { - "tags": [ - "Student" - ], - "description": "", - "responses": { - "200": { - "description": "OK" - }, - "400": { - "description": "Bad Request" - }, - "404": { - "description": "Not Found" - } - } - } - }, - "/api/student/{username}": { - "delete": { - "tags": [ - "Student" - ], - "description": "", - "parameters": [ - { - "name": "username", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "400": { - "description": "Bad Request" - }, - "404": { - "description": "Not Found" - } - } - }, - "get": { - "tags": [ - "Student" - ], - "description": "", - "parameters": [ - { - "name": "username", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "201": { - "description": "Created" - }, - "400": { - "description": "Bad Request" - }, - "404": { - "description": "Not Found" - } - } - } - }, - "/api/student/{id}/classes": { - "get": { - "tags": [ - "Student" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "full", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "500": { - "description": "Internal Server Error" - } - } - } - }, - "/api/student/{id}/submissions": { - "get": { - "tags": [ - "Student" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/api/student/{id}/assignments": { - "get": { - "tags": [ - "Student" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "full", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/api/student/{id}/groups": { - "get": { - "tags": [ - "Student" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "full", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/api/student/{id}/questions": { - "get": { - "tags": [ - "Student" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/api/group/": { - "get": { - "tags": [ - "Group" - ], - "description": "", - "parameters": [ - { - "name": "full", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "400": { - "description": "Bad Request" - } - } - }, - "post": { - "tags": [ - "Group" - ], - "description": "", - "responses": { - "201": { - "description": "Created" - }, - "400": { - "description": "Bad Request" - }, - "500": { - "description": "Internal Server Error" - } - } - } - }, - "/api/group/{groupid}": { - "get": { - "tags": [ - "Group" - ], - "description": "", - "parameters": [ - { - "name": "groupid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "400": { - "description": "Bad Request" - } - } - } - }, - "/api/group/{id}/questions": { - "get": { - "tags": [ - "Group" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/api/assignment/": { - "get": { - "tags": [ - "Assignment" - ], - "description": "", - "parameters": [ - { - "name": "full", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - }, - "post": { - "tags": [ - "Assignment" - ], - "description": "", - "responses": { - "201": { - "description": "Created" - }, - "400": { - "description": "Bad Request" - }, - "500": { - "description": "Internal Server Error" - } - }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "description": { - "example": "any" - }, - "language": { - "example": "any" - }, - "learningPath": { - "example": "any" - }, - "title": { - "example": "any" - } - } - } - } - } - } - } - }, - "/api/assignment/{id}": { - "get": { - "tags": [ - "Assignment" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "400": { - "description": "Bad Request" - }, - "404": { - "description": "Not Found" - } - } - } - }, - "/api/assignment/{id}/submissions": { - "get": { - "tags": [ - "Assignment" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "400": { - "description": "Bad Request" - } - } - } - }, - "/api/assignment/{id}/questions": { - "get": { - "tags": [ - "Assignment" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/api/assignment/{assignmentid}/groups/": { - "get": { - "tags": [ - "Assignment" - ], - "description": "", - "parameters": [ - { - "name": "assignmentid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "full", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "400": { - "description": "Bad Request" - } - } - }, - "post": { - "tags": [ - "Assignment" - ], - "description": "", - "parameters": [ - { - "name": "assignmentid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "201": { - "description": "Created" - }, - "400": { - "description": "Bad Request" - }, - "500": { - "description": "Internal Server Error" - } - } - } - }, - "/api/assignment/{assignmentid}/groups/{groupid}": { - "get": { - "tags": [ - "Assignment" - ], - "description": "", - "parameters": [ - { - "name": "assignmentid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "groupid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "400": { - "description": "Bad Request" - } - } - } - }, - "/api/assignment/{assignmentid}/groups/{id}/questions": { - "get": { - "tags": [ - "Assignment" - ], - "description": "", - "parameters": [ - { - "name": "assignmentid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/api/submission/": { - "get": { - "tags": [ - "Submission" - ], - "description": "", - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/api/submission/{id}": { - "post": { - "tags": [ - "Submission" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "404": { - "description": "Not Found" - } - } - }, - "get": { - "tags": [ - "Submission" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "language", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "version", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "400": { - "description": "Bad Request" - }, - "404": { - "description": "Not Found" - } - } - }, - "delete": { - "tags": [ - "Submission" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "language", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "version", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "404": { - "description": "Not Found" - } - } - } - }, - "/api/class/": { - "get": { - "tags": [ - "Class" - ], - "description": "", - "parameters": [ - { - "name": "full", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - }, - "post": { - "tags": [ - "Class" - ], - "description": "", - "responses": { - "201": { - "description": "Created" - }, - "400": { - "description": "Bad Request" - }, - "500": { - "description": "Internal Server Error" - } - }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "displayName": { - "example": "any" - } - } - } - } - } - } - } - }, - "/api/class/{id}": { - "get": { - "tags": [ - "Class" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "404": { - "description": "Not Found" - }, - "500": { - "description": "Internal Server Error" - } - } - } - }, - "/api/class/{id}/teacher-invitations": { - "get": { - "tags": [ - "Class" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "full", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/api/class/{id}/students": { - "get": { - "tags": [ - "Class" - ], - "description": "", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "full", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/api/class/{classid}/assignments/": { - "get": { - "tags": [ - "Class" - ], - "description": "", - "parameters": [ - { - "name": "classid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "full", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - }, - "post": { - "tags": [ - "Class" - ], - "description": "", - "parameters": [ - { - "name": "classid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "201": { - "description": "Created" - }, - "400": { - "description": "Bad Request" - }, - "500": { - "description": "Internal Server Error" - } - }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "description": { - "example": "any" - }, - "language": { - "example": "any" - }, - "learningPath": { - "example": "any" - }, - "title": { - "example": "any" - } - } - } - } - } - } - } - }, - "/api/class/{classid}/assignments/{id}": { - "get": { - "tags": [ - "Class" - ], - "description": "", - "parameters": [ - { - "name": "classid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "400": { - "description": "Bad Request" - }, - "404": { - "description": "Not Found" - } - } - } - }, - "/api/class/{classid}/assignments/{id}/submissions": { - "get": { - "tags": [ - "Class" - ], - "description": "", - "parameters": [ - { - "name": "classid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "400": { - "description": "Bad Request" - } - } - } - }, - "/api/class/{classid}/assignments/{id}/questions": { - "get": { - "tags": [ - "Class" - ], - "description": "", - "parameters": [ - { - "name": "classid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/api/class/{classid}/assignments/{assignmentid}/groups/": { - "get": { - "tags": [ - "Class" - ], - "description": "", - "parameters": [ - { - "name": "classid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "assignmentid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "full", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "400": { - "description": "Bad Request" - } - } - }, - "post": { - "tags": [ - "Class" - ], - "description": "", - "parameters": [ - { - "name": "classid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "assignmentid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "201": { - "description": "Created" - }, - "400": { - "description": "Bad Request" - }, - "500": { - "description": "Internal Server Error" - } - } - } - }, - "/api/class/{classid}/assignments/{assignmentid}/groups/{groupid}": { - "get": { - "tags": [ - "Class" - ], - "description": "", - "parameters": [ - { - "name": "classid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "assignmentid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "groupid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "400": { - "description": "Bad Request" - } - } - } - }, - "/api/class/{classid}/assignments/{assignmentid}/groups/{id}/questions": { - "get": { - "tags": [ - "Class" - ], - "description": "", - "parameters": [ - { - "name": "classid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "assignmentid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/api/question/": { - "get": { - "tags": [ - "Question" - ], - "description": "", - "parameters": [ - { - "name": "full", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "404": { - "description": "Not Found" - } - } - }, - "post": { - "tags": [ - "Question" - ], - "description": "", - "responses": { - "200": { - "description": "OK" - }, - "400": { - "description": "Bad Request" - } - }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "learningObjectIdentifier": { - "example": "any" - }, - "author": { - "example": "any" - }, - "content": { - "example": "any" - } - } - } - } - } - } - } - }, - "/api/question/{seq}": { - "delete": { - "tags": [ - "Question" - ], - "description": "", - "parameters": [ - { - "name": "seq", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "400": { - "description": "Bad Request" - } - } - }, - "get": { - "tags": [ - "Question" - ], - "description": "", - "parameters": [ - { - "name": "seq", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "404": { - "description": "Not Found" - } - } - } - }, - "/api/question/answers/{seq}": { - "get": { - "tags": [ - "Question" - ], - "description": "", - "parameters": [ - { - "name": "seq", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "full", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "404": { - "description": "Not Found" - } - } - } - }, - "/api/auth/config": { - "get": { - "tags": [ - "Auth" - ], - "description": "", - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/api/auth/testAuthenticatedOnly": { - "get": { - "tags": [ - "Auth" - ], - "description": "", - "responses": { - "200": { - "description": "OK" - } - }, - "security": [ - { - "student": [] - }, - { - "teacher": [] - } - ] - } - }, - "/api/auth/testStudentsOnly": { - "get": { - "tags": [ - "Auth" - ], - "description": "", - "responses": { - "200": { - "description": "OK" - } - }, - "security": [ - { - "student": [] - } - ] - } - }, - "/api/auth/testTeachersOnly": { - "get": { - "tags": [ - "Auth" - ], - "description": "", - "responses": { - "200": { - "description": "OK" - } - }, - "security": [ - { - "teacher": [] - } - ] - } - }, - "/api/theme/": { - "get": { - "tags": [ - "Theme" - ], - "description": "", - "parameters": [ - { - "name": "language", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/api/theme/{theme}": { - "get": { - "tags": [ - "Theme" - ], - "description": "", - "parameters": [ - { - "name": "theme", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "404": { - "description": "Not Found" - } - } - } - }, - "/api/learningPath/": { - "get": { - "tags": [ - "Learning Path" - ], - "description": "", - "parameters": [ - { - "name": "hruid", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "theme", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "search", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "language", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "forStudent", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "forGroup", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "assignmentNo", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "classId", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/api/learningObject/": { - "get": { - "tags": [ - "Learning Object" - ], - "description": "", - "parameters": [ - { - "name": "full", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/api/learningObject/{hruid}": { - "get": { - "tags": [ - "Learning Object" - ], - "description": "", - "parameters": [ - { - "name": "hruid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/api/learningObject/{hruid}/html": { - "get": { - "tags": [ - "Learning Object" - ], - "description": "", - "parameters": [ - { - "name": "hruid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/api/learningObject/{hruid}/html/{attachmentName}": { - "get": { - "tags": [ - "Learning Object" - ], - "description": "", - "parameters": [ - { - "name": "hruid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "attachmentName", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "default": { - "description": "" - } - } - } - }, - "/api/learningObject/{hruid}/submissions/": { - "get": { - "tags": [ - "Learning Object" - ], - "description": "", - "parameters": [ - { - "name": "hruid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, - "/api/learningObject/{hruid}/submissions/{id}": { - "post": { - "tags": [ - "Learning Object" - ], - "description": "", - "parameters": [ - { - "name": "hruid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "404": { - "description": "Not Found" - } - } - }, - "get": { - "tags": [ - "Learning Object" - ], - "description": "", - "parameters": [ - { - "name": "hruid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "language", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "version", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "400": { - "description": "Bad Request" - }, - "404": { - "description": "Not Found" - } - } - }, - "delete": { - "tags": [ - "Learning Object" - ], - "description": "", - "parameters": [ - { - "name": "hruid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "language", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "version", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "404": { - "description": "Not Found" - } - } - } - }, - "/api/learningObject/{hruid}/{version}/questions/": { - "get": { - "tags": [ - "Learning Object" - ], - "description": "", - "parameters": [ - { - "name": "hruid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "version", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "full", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "404": { - "description": "Not Found" - } - } - }, - "post": { - "tags": [ - "Learning Object" - ], - "description": "", - "parameters": [ - { - "name": "hruid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "version", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "400": { - "description": "Bad Request" - } - }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "learningObjectIdentifier": { - "example": "any" - }, - "author": { - "example": "any" - }, - "content": { - "example": "any" - } - } - } - } - } - } - } - }, - "/api/learningObject/{hruid}/{version}/questions/{seq}": { - "delete": { - "tags": [ - "Learning Object" - ], - "description": "", - "parameters": [ - { - "name": "hruid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "version", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "seq", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "400": { - "description": "Bad Request" - } - } - }, - "get": { - "tags": [ - "Learning Object" - ], - "description": "", - "parameters": [ - { - "name": "hruid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "version", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "seq", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "404": { - "description": "Not Found" - } - } - } - }, - "/api/learningObject/{hruid}/{version}/questions/answers/{seq}": { - "get": { - "tags": [ - "Learning Object" - ], - "description": "", - "parameters": [ - { - "name": "hruid", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "version", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "seq", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "full", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "404": { - "description": "Not Found" - } - } - } - } - }, - "components": { - "securitySchemes": { - "student": { - "type": "oauth2", - "flows": { - "implicit": { - "authorizationUrl": "https://sel2-1.ugent.be/idp/realms/student/protocol/openid-connect/auth", - "scopes": { - "openid": "openid", - "profile": "profile", - "email": "email" - } - } - } - }, - "teacher": { - "type": "oauth2", - "flows": { - "implicit": { - "authorizationUrl": "https://sel2-1.ugent.be/idp/realms/teacher/protocol/openid-connect/auth", - "scopes": { - "openid": "openid", - "profile": "profile", - "email": "email" - } - } - } - } - } - } -} \ No newline at end of file +}