From 7a657c9b86794b03cd8aa240067bbdcab48bb240 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Fri, 21 Mar 2025 10:00:49 +0100 Subject: [PATCH 01/35] fix: docker compose up voor development Spint enkel de nodige containers op voor development. Anders gebruik -f compose.stating.yml of compose.production.yml --- compose.prod.yml => compose.production.yml | 26 ++++++++++++++++----- compose.override.yml => compose.staging.yml | 23 +++++++++++++++++- compose.yml | 12 ---------- 3 files changed, 42 insertions(+), 19 deletions(-) rename compose.prod.yml => compose.production.yml (82%) rename compose.override.yml => compose.staging.yml (79%) diff --git a/compose.prod.yml b/compose.production.yml similarity index 82% rename from compose.prod.yml rename to compose.production.yml index 8825796e..e6f140d4 100644 --- a/compose.prod.yml +++ b/compose.production.yml @@ -1,7 +1,8 @@ # -# 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. +# Use this configuration to deploy the project on a server. +# +# This configuration builds the frontend and backend services as Docker images, +# and uses the paths for the services, instead of ports, and enables SSL. # services: web: @@ -35,12 +36,16 @@ services: - 'traefik.http.services.api.loadbalancer.server.port=3000' db: - # Also see compose.yml + extends: + file: ./compose.yml + service: db networks: - dwengo-1 idp: - # Also see compose.yml + extends: + file: ./compose.yml + service: idp # TODO Replace with proper production command command: ['start-dev', '--http-port', '7080', '--https-port', '7443', '--import-realm'] networks: @@ -92,7 +97,15 @@ services: - dwengo-1 logging: - # Also see compose.yml + 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 networks: - dwengo-1 @@ -107,6 +120,7 @@ services: volumes: dwengo_grafana_data: dwengo_letsencrypt: + dwengo_loki_data: networks: dwengo-1: diff --git a/compose.override.yml b/compose.staging.yml similarity index 79% rename from compose.override.yml rename to compose.staging.yml index 5c35441e..f5081e12 100644 --- a/compose.override.yml +++ b/compose.staging.yml @@ -32,8 +32,15 @@ services: - 'traefik.http.routers.api.rule=PathPrefix(`/api`)' - 'traefik.http.services.api.loadbalancer.server.port=3000' + db: + extends: + file: ./compose.yml + service: db + idp: - # Also see compose.yml + extends: + file: ./compose.yml + service: idp labels: - 'traefik.http.routers.idp.rule=PathPrefix(`/idp`)' - 'traefik.http.services.idp.loadbalancer.server.port=7080' @@ -60,6 +67,17 @@ services: volumes: - /var/run/docker.sock:/var/run/docker.sock:ro + 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 + dashboards: image: grafana/grafana:latest ports: @@ -70,3 +88,6 @@ services: volumes: dwengo_grafana_data: + dwengo_loki_data: + dwengo_postgres_data: + diff --git a/compose.yml b/compose.yml index 1276c1af..9435f6f8 100644 --- a/compose.yml +++ b/compose.yml @@ -36,17 +36,5 @@ services: 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: From 827b652b68065aef0b7c5c1f61c9f3cfbfeb453b Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Fri, 21 Mar 2025 10:01:38 +0100 Subject: [PATCH 02/35] docs: Beschrijf docker in installatie --- README.md | 12 +++++++----- backend/README.md | 25 ++++++++++++++++--------- frontend/README.md | 8 ++++++++ 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index dc09bbfc..7bbe7269 100644 --- a/README.md +++ b/README.md @@ -21,14 +21,17 @@ Alternatief kan je één van de volgende methodes gebruiken om de applicatie lok ### Quick start +Om de applicatie lokaal te draaien als kant-en-klare Docker-containers: + 1. Installeer Docker en Docker Compose op je systeem (zie [Docker](https://docs.docker.com/get-docker/) en [Docker Compose](https://docs.docker.com/compose/)). 2. Clone deze repository. 3. In de backend, kopieer `.env.example` (of `.env.development.example`) naar `.env` en pas de variabelen aan waar nodig. -4. Voer `docker compose up` uit in de root van de repository. +4. Voer `docker compose -f compose.staging.yml up --build` uit in de root van de repository. 5. Optioneel: Configureer de applicatie aan de hand van de [configuratiehandleiding](https://github.com/SELab-2/Dwengo-1/wiki/Administrator:-Productie-omgeving#dwengo-1-configuratie). +6. De applicatie is nu beschikbaar op [`http://localhost/`](http://localhost/) en [`http://localhost/api`](http://localhost/api). ```bash docker compose version @@ -38,14 +41,13 @@ cp .env.example .env # Pas .env aan nano .env cd .. -docker compose up -# Configureer de applicatie +docker compose -f compose.staging.yml up --build ``` -### Handmatige installatie +### Handmatige installatie en ontwikkeling Zie de submappen voor de installatie-instructies van de [frontend](./frontend/README.md) -en [backend](./backend/README.md). +en [backend](./backend/README.md) en instructies voor het opzetten van een ontwikkelomgeving. ## Architectuur diff --git a/backend/README.md b/backend/README.md index 442cea82..48b14bbf 100644 --- a/backend/README.md +++ b/backend/README.md @@ -4,23 +4,21 @@ ```shell npm install + +# Start de nodige services voor ontwikkeling +cd ../ # Ga naar de root van de repository +docker compose up -d ``` -Setup the environment variables in a `.env` file in the root of the project. You can use the `.env.example` file as a template. +Zet de omgevingsvariabelen in een `.env` bestand in de root van het project. +Je kan het `.env.example` bestand als template gebruiken. -### Development +### Ontwikkeling ```shell npm run dev ``` -### Production - -```shell -npm run build -npm run start -``` - ### Tests Voer volgend commando uit om de unit tests uit te voeren: @@ -29,6 +27,15 @@ Voer volgend commando uit om de unit tests uit te voeren: npm run test:unit ``` +### Productie + +```shell +npm run build +npm run start +``` + +Zie ook de [installatiehandleiding](https://github.com/SELab-2/Dwengo-1/wiki/Administrator:-Productie-omgeving). + ## Keycloak configuratie Tijdens development is het voldoende om gebruik te maken van de keycloak configuratie die automatisch ingeladen wordt. diff --git a/frontend/README.md b/frontend/README.md index f798f404..8baba8b6 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -19,7 +19,15 @@ See [Vite Configuration Reference](https://vite.dev/config/). ## Project Setup ```sh +# Install dependencies npm install + +# Start necessary services for development +cd ../ # Go to the root of the repository +docker compose up -d +# Start the backend +cd backend +npm run dev # or npm run build && npm run start ``` ### Compile and Hot-Reload for Development From a5fd3c06126c168276e6a1389b12fddbe608829a Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Fri, 21 Mar 2025 11:05:01 +0100 Subject: [PATCH 03/35] docs: Omgevingsvariabelen Documenteer omgevingsvariabelen in .env.example en herstructureer de andere .examples om gebruik te maken van dezelfde structuur. --- backend/.env.development.example | 27 ++++++++---- backend/.env.example | 75 ++++++++++++++++++++++++-------- backend/.env.production.example | 35 +++++++++------ backend/.env.test.example | 14 +++++- 4 files changed, 110 insertions(+), 41 deletions(-) diff --git a/backend/.env.development.example b/backend/.env.development.example index 466e1b7b..d03a6744 100644 --- a/backend/.env.development.example +++ b/backend/.env.development.example @@ -1,15 +1,24 @@ # -# Basic configuration +# Development environment configuration +# +# You probably don't need to change these values, as this configuration takes +# the docker services and their default ports into account. # -DWENGO_PORT=3000 # The port the backend will listen on +### Dwengo ### + +#DWENGO_PORT=3000 +#DWENGO_LEARNING_CONTENT_REPO_API_BASE_URL=https://dwengo.org/backend/api +#DWENGO_FALLBACK_LANGUAGE=nl +#DWENGO_RUN_MODE=dev + DWENGO_DB_HOST=localhost DWENGO_DB_PORT=5431 +#DWENGO_DB_NAME=dwengo DWENGO_DB_USERNAME=postgres DWENGO_DB_PASSWORD=postgres DWENGO_DB_UPDATE=true - -# Auth +#DWENGO_DB_CONTENT_PREFIX=u_ DWENGO_AUTH_STUDENT_URL=http://localhost:7080/realms/student DWENGO_AUTH_STUDENT_CLIENT_ID=dwengo @@ -17,12 +26,12 @@ DWENGO_AUTH_STUDENT_JWKS_ENDPOINT=http://localhost:7080/realms/student/protocol/ DWENGO_AUTH_TEACHER_URL=http://localhost:7080/realms/teacher DWENGO_AUTH_TEACHER_CLIENT_ID=dwengo DWENGO_AUTH_TEACHER_JWKS_ENDPOINT=http://localhost:7080/realms/teacher/protocol/openid-connect/certs +#DWENGO_AUTH_AUDIENCE=account -# 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 +#DWENGO_CORS_ALLOWED_HEADERS=Authorization,Content-Type -# -# Advanced configuration -# +### Advanced configuration ### -# LOKI_HOST=http://localhost:9001 # The address of the Loki instance, used for logging +DWENGO_LOGGING_LEVEL=debug +#DWENGO_LOGGING_LOKI_HOST=http://localhost:3102 diff --git a/backend/.env.example b/backend/.env.example index 68cef35d..8873515c 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,27 +1,68 @@ # # Basic configuration # +# Change the values of the variables below to match your environment! +# Default values are commented out. +# -DWENGO_PORT=3000 # The port the backend will listen on +### Dwengo ### + +# Port the backend will listen on +#DWENGO_PORT=3000 +# The hostname or IP address of the remote learning content API. +#DWENGO_LEARNING_CONTENT_REPO_API_BASE_URL=https://dwengo.org/backend/api +# The default fallback language. +#DWENGO_FALLBACK_LANGUAGE=nl +# Whether running in production mode or not. Possible values are "prod" or "dev". +#DWENGO_RUN_MODE=dev + +# ! Change this! The hostname or IP address of the database +# If running your stack in docker, this should use the docker service name. DWENGO_DB_HOST=domain-or-ip-of-database -DWENGO_DB_PORT=5431 - -# Change this to the actual credentials of the user Dwengo should use in the backend -DWENGO_DB_USERNAME=postgres -DWENGO_DB_PASSWORD=postgres - +# The port of the database. +#DWENGO_DB_PORT=5432 +# The name of the database. +#DWENGO_DB_NAME=dwengo +# ! Change this! The username of the database user. +DWENGO_DB_USERNAME=username +# ! Change this! The password of the database user. +DWENGO_DB_PASSWORD=password +# Whether the database scheme needs to be updated. # Set this to true when the database scheme needs to be updated. In that case, take a backup first. -DWENGO_DB_UPDATE=false +#DWENGO_DB_UPDATE=false +# The prefix used for custom user content. +#DWENGO_DB_CONTENT_PREFIX=u_ -# Data for the identity provider via which the students authenticate. -DWENGO_AUTH_STUDENT_URL=http://localhost:7080/realms/student +# ! Change this! The external URL for student authentication. Should be reachable by the client. +# E.g. https://sel2-1.ugent.be/idp/realms/student +DWENGO_AUTH_STUDENT_URL=http://hostname/idp/realms/student +# ! Change this! The client ID for student authentication. DWENGO_AUTH_STUDENT_CLIENT_ID=dwengo -DWENGO_AUTH_STUDENT_JWKS_ENDPOINT=http://localhost:7080/realms/student/protocol/openid-connect/certs - -# Data for the identity provider via which the teachers authenticate. -DWENGO_AUTH_TEACHER_URL=http://localhost:7080/realms/teacher +# ! Change this! The internal URL for retrieving the JWKS for student authentication. +# Should be reachable by the backend. If running your stack in docker, this should use the docker service name. +# E.g. http://idp:7080/realms/student/protocol/openid-connect/certs +DWENGO_AUTH_STUDENT_JWKS_ENDPOINT=http://hostname/realms/student/protocol/openid-connect/certs +# ! Change this! The external URL for teacher authentication. Should be reachable by the client. +# E.g. https://sel2-1.ugent.be/idp/realms/teacher +DWENGO_AUTH_TEACHER_URL=http://hostname/idp/realms/teacher +# ! Change this! The client ID for teacher authentication. DWENGO_AUTH_TEACHER_CLIENT_ID=dwengo -DWENGO_AUTH_TEACHER_JWKS_ENDPOINT=http://localhost:7080/realms/teacher/protocol/openid-connect/certs +# ! Change this! The internal URL for retrieving the JWKS for teacher authentication. +# Should be reachable by the backend. If running your stack in docker, this should use the docker service name. +# E.g. http://idp:7080/realms/teacher/protocol/openid-connect/certs +DWENGO_AUTH_TEACHER_JWKS_ENDPOINT=http://hostname/realms/teacher/protocol/openid-connect/certs +# The IDP audience +#DWENGO_AUTH_AUDIENCE=account -# The address of the Lokiinstance, used for logging -# LOKI_HOST=http://localhost:3102 +# Allowed origins for CORS requests. Separate multiple origins with a comma. +#DWENGO_CORS_ALLOWED_ORIGINS= +# Allowed headers for CORS requests. Separate multiple headers with a comma. +#DWENGO_CORS_ALLOWED_HEADERS=Authorization,Content-Type + +### Advanced configuration ### + +# The logging level. Possible values are "debug", "info", "warn", "error". +#DWENGO_LOGGING_LEVEL=info +# The address of the Loki instance, a log aggregation system. +# If running your stack in docker, this should use the docker service name. +#DWENGO_LOGGING_LOKI_HOST=http://localhost:3102 diff --git a/backend/.env.production.example b/backend/.env.production.example index 390409d1..4f36cf53 100644 --- a/backend/.env.production.example +++ b/backend/.env.production.example @@ -1,28 +1,37 @@ -DWENGO_PORT=3000 # The port the backend will listen on -DWENGO_DB_HOST=db # Name of the database container -DWENGO_DB_PORT=5431 +# +# Production environment configuration +# +# Change the values of the variables below to match your production environment! +# See .env.example for more information. +# -# Change this to the actual credentials of the user Dwengo should use in the backend +### Dwengo ### + +DWENGO_PORT=3000 +#DWENGO_LEARNING_CONTENT_REPO_API_BASE_URL=https://dwengo.org/backend/api +#DWENGO_FALLBACK_LANGUAGE=nl +DWENGO_RUN_MODE=prod + +DWENGO_DB_HOST=db +DWENGO_DB_PORT=5432 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 +#DWENGO_DB_CONTENT_PREFIX=u_ -# 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 +#DWENGO_AUTH_AUDIENCE=account -# -# Advanced configuration -# +#DWENGO_CORS_ALLOWED_ORIGINS= +#DWENGO_CORS_ALLOWED_HEADERS=Authorization,Content-Type -# Logging and monitoring +### Advanced configuration ### -# LOKI_HOST=http://logging:3102 # The address of the Loki instance, used for logging +DWENGO_LOGGING_LEVEL=info +DWENGO_LOGGING_LOKI_HOST=http://logging:3102 diff --git a/backend/.env.test.example b/backend/.env.test.example index b8a81003..535628cd 100644 --- a/backend/.env.test.example +++ b/backend/.env.test.example @@ -1,3 +1,13 @@ -PORT=3000 -DWENGO_DB_UPDATE=true +# +# Test environment configuration +# +# Should not need to be modified. +# See .env.example for more information. +# + +### Dwengo ### + +DWENGO_PORT=3000 + DWENGO_DB_NAME=":memory:" +DWENGO_DB_UPDATE=true From 9a17762fd4ffcdef4cac8d9120b090c18dd2fad1 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Fri, 21 Mar 2025 11:08:42 +0100 Subject: [PATCH 04/35] docs: Kopieer .env bestanden --- README.md | 3 +-- backend/README.md | 3 +++ frontend/README.md | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7bbe7269..3526b53d 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,7 @@ Om de applicatie lokaal te draaien als kant-en-klare Docker-containers: 1. Installeer Docker en Docker Compose op je systeem (zie [Docker](https://docs.docker.com/get-docker/) en [Docker Compose](https://docs.docker.com/compose/)). 2. Clone deze repository. -3. In de backend, kopieer `.env.example` (of `.env.development.example`) naar `.env` en pas de variabelen aan waar - nodig. +3. In de backend, kopieer `.env.example` naar `.env` en pas de variabelen aan waar nodig. 4. Voer `docker compose -f compose.staging.yml up --build` uit in de root van de repository. 5. Optioneel: Configureer de applicatie aan de hand van de [configuratiehandleiding](https://github.com/SELab-2/Dwengo-1/wiki/Administrator:-Productie-omgeving#dwengo-1-configuratie). diff --git a/backend/README.md b/backend/README.md index 48b14bbf..f5568f5f 100644 --- a/backend/README.md +++ b/backend/README.md @@ -16,6 +16,9 @@ Je kan het `.env.example` bestand als template gebruiken. ### Ontwikkeling ```shell +# Omgevingsvariabelen +cp .env.development.example .env.development.local + npm run dev ``` diff --git a/frontend/README.md b/frontend/README.md index 8baba8b6..d4b3b63d 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -27,6 +27,7 @@ cd ../ # Go to the root of the repository docker compose up -d # Start the backend cd backend +cp .env.development.example .env.development.local npm run dev # or npm run build && npm run start ``` From 4fd03cd61638d80d423b7864824ee56a185e5de3 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Fri, 21 Mar 2025 11:12:19 +0100 Subject: [PATCH 05/35] refactor(backend): Gebruik EnvVars ipv. process.env --- backend/src/config.ts | 5 ----- backend/src/logging/initalize.ts | 22 +++++++++++++--------- backend/src/mikro-orm.config.ts | 3 +-- backend/src/util/envvars.ts | 12 ++++++++++-- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/backend/src/config.ts b/backend/src/config.ts index 69af5d74..b9974a3b 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -1,12 +1,7 @@ import { EnvVars, getEnvVar } from './util/envvars.js'; -import { Language } from './entities/content/language.js'; // API export const DWENGO_API_BASE = getEnvVar(EnvVars.LearningContentRepoApiBaseUrl); export const FALLBACK_LANG = getEnvVar(EnvVars.FallbackLanguage); -// Logging -export const LOG_LEVEL: string = 'development' === process.env.NODE_ENV ? 'debug' : 'info'; -export const LOKI_HOST: string = process.env.LOKI_HOST || 'http://localhost:3102'; - export const FALLBACK_SEQ_NUM = 1; diff --git a/backend/src/logging/initalize.ts b/backend/src/logging/initalize.ts index 1ff761c9..bc202dc2 100644 --- a/backend/src/logging/initalize.ts +++ b/backend/src/logging/initalize.ts @@ -1,7 +1,7 @@ import { createLogger, format, Logger as WinstonLogger, transports } from 'winston'; import LokiTransport from 'winston-loki'; import { LokiLabels } from 'loki-logger-ts'; -import { LOG_LEVEL, LOKI_HOST } from '../config.js'; +import { EnvVars, getEnvVar } from '../util/envvars'; export class Logger extends WinstonLogger { constructor() { @@ -22,10 +22,19 @@ function initializeLogger(): Logger { return logger; } + const logLevel = getEnvVar(EnvVars.LogLevel); + + const consoleTransport = new transports.Console({ + level: getEnvVar(EnvVars.LogLevel), + format: format.combine(format.cli(), format.colorize()), + }); + + const lokiHost = getEnvVar(EnvVars.LokiHost); + const lokiTransport: LokiTransport = new LokiTransport({ - host: LOKI_HOST, + host: lokiHost, labels: Labels, - level: LOG_LEVEL, + level: logLevel, json: true, format: format.combine(format.timestamp(), format.json()), onConnectionError: (err) => { @@ -34,16 +43,11 @@ function initializeLogger(): Logger { }, }); - const consoleTransport = new transports.Console({ - level: LOG_LEVEL, - format: format.combine(format.cli(), format.colorize()), - }); - logger = createLogger({ transports: [lokiTransport, consoleTransport], }); - logger.debug(`Logger initialized with level ${LOG_LEVEL}, Loki host ${LOKI_HOST}`); + logger.debug(`Logger initialized with level ${logLevel} to Loki host ${lokiHost}`); return logger; } diff --git a/backend/src/mikro-orm.config.ts b/backend/src/mikro-orm.config.ts index c9cf6ed9..f2fb2bae 100644 --- a/backend/src/mikro-orm.config.ts +++ b/backend/src/mikro-orm.config.ts @@ -3,7 +3,6 @@ import { PostgreSqlDriver } from '@mikro-orm/postgresql'; import { EnvVars, getEnvVar, getNumericEnvVar } from './util/envvars.js'; import { SqliteDriver } from '@mikro-orm/sqlite'; import { MikroOrmLogger } from './logging/mikroOrmLogger.js'; -import { LOG_LEVEL } from './config.js'; // Import alle entity-bestanden handmatig import { User } from './entities/users/user.entity.js'; @@ -69,7 +68,7 @@ function config(testingMode: boolean = false): Options { // EntitiesTs: entitiesTs, // Logging - debug: LOG_LEVEL === 'debug', + debug: getEnvVar(EnvVars.LogLevel) === 'debug', loggerFactory: (options: LoggerOptions) => new MikroOrmLogger(options), }; } diff --git a/backend/src/util/envvars.ts b/backend/src/util/envvars.ts index 115291af..c37c79d5 100644 --- a/backend/src/util/envvars.ts +++ b/backend/src/util/envvars.ts @@ -4,20 +4,24 @@ const IDP_PREFIX = PREFIX + 'AUTH_'; const STUDENT_IDP_PREFIX = IDP_PREFIX + 'STUDENT_'; const TEACHER_IDP_PREFIX = IDP_PREFIX + 'TEACHER_'; const CORS_PREFIX = PREFIX + 'CORS_'; +const LOGGING_PREFIX = PREFIX + 'LOGGING_'; type EnvVar = { key: string; required?: boolean; defaultValue?: any }; export const EnvVars: { [key: string]: EnvVar } = { Port: { key: PREFIX + 'PORT', defaultValue: 3000 }, + LearningContentRepoApiBaseUrl: { key: PREFIX + 'LEARNING_CONTENT_REPO_API_BASE_URL', defaultValue: 'https://dwengo.org/backend/api' }, + FallbackLanguage: { key: PREFIX + 'FALLBACK_LANGUAGE', defaultValue: 'nl' }, + RunMode: { key: PREFIX + 'RUN_MODE', defaultValue: 'dev' }, + DbHost: { key: DB_PREFIX + 'HOST', required: true }, 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 }, DbUpdate: { key: DB_PREFIX + 'UPDATE', defaultValue: false }, - LearningContentRepoApiBaseUrl: { key: PREFIX + 'LEARNING_CONTENT_REPO_API_BASE_URL', defaultValue: 'https://dwengo.org/backend/api' }, - FallbackLanguage: { key: PREFIX + 'FALLBACK_LANGUAGE', defaultValue: 'nl' }, UserContentPrefix: { key: DB_PREFIX + 'USER_CONTENT_PREFIX', defaultValue: 'u_' }, + 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 }, @@ -25,8 +29,12 @@ export const EnvVars: { [key: string]: EnvVar } = { 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' }, + + LogLevel: { key: LOGGING_PREFIX + 'LEVEL', defaultValue: 'info' }, + LokiHost: { key: LOGGING_PREFIX + 'LOKI_HOST', defaultValue: 'http://localhost:3102' }, } as const; /** From 4c7fe1cb3cbbc4d85ace83f3a051e653b9ed3710 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Fri, 21 Mar 2025 11:12:41 +0100 Subject: [PATCH 06/35] chore(backend): Geen Loki tijdens ontwikkeling --- backend/src/logging/initalize.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/src/logging/initalize.ts b/backend/src/logging/initalize.ts index bc202dc2..a4b9dc4f 100644 --- a/backend/src/logging/initalize.ts +++ b/backend/src/logging/initalize.ts @@ -29,6 +29,12 @@ function initializeLogger(): Logger { format: format.combine(format.cli(), format.colorize()), }); + if (getEnvVar(EnvVars.RunMode) === 'dev') { + return createLogger({ + transports: [consoleTransport], + }); + } + const lokiHost = getEnvVar(EnvVars.LokiHost); const lokiTransport: LokiTransport = new LokiTransport({ From 0f7831120240b1fa6be5d14e253ffb19dab3d3d8 Mon Sep 17 00:00:00 2001 From: Lint Action Date: Fri, 21 Mar 2025 10:23:57 +0000 Subject: [PATCH 07/35] style: fix linting issues met Prettier --- compose.staging.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/compose.staging.yml b/compose.staging.yml index f5081e12..8357e773 100644 --- a/compose.staging.yml +++ b/compose.staging.yml @@ -90,4 +90,3 @@ volumes: dwengo_grafana_data: dwengo_loki_data: dwengo_postgres_data: - From 223539ec17809a5c1e194009483e9507dcc75e97 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Fri, 21 Mar 2025 13:19:47 +0100 Subject: [PATCH 08/35] fix: .js toevoegen aan imports --- backend/src/logging/initalize.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/logging/initalize.ts b/backend/src/logging/initalize.ts index a4b9dc4f..4d14e8ab 100644 --- a/backend/src/logging/initalize.ts +++ b/backend/src/logging/initalize.ts @@ -1,7 +1,7 @@ import { createLogger, format, Logger as WinstonLogger, transports } from 'winston'; import LokiTransport from 'winston-loki'; import { LokiLabels } from 'loki-logger-ts'; -import { EnvVars, getEnvVar } from '../util/envvars'; +import { EnvVars, getEnvVar } from '../util/envvars.js'; export class Logger extends WinstonLogger { constructor() { From 9ce4532a5389a340548c2e9526c910b534c8a8be Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Fri, 21 Mar 2025 13:19:58 +0100 Subject: [PATCH 09/35] docs: .env voor npm run start --- backend/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/README.md b/backend/README.md index f5568f5f..8a78ed14 100644 --- a/backend/README.md +++ b/backend/README.md @@ -33,6 +33,9 @@ npm run test:unit ### Productie ```shell +# Omgevingsvariabelen +cp .env.development.example .env + npm run build npm run start ``` From 32a3bb0dd64d22cd78a8c4d0ebaa23caf09d5dc2 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sat, 22 Mar 2025 11:41:52 +0100 Subject: [PATCH 10/35] fix: backend controllers gehomologeerd #130 --- backend/src/controllers/assignments.ts | 2 +- backend/src/controllers/classes.ts | 2 +- backend/src/controllers/groups.ts | 2 +- backend/src/controllers/learning-objects.ts | 2 +- backend/src/controllers/questions.ts | 4 +- backend/src/controllers/students.ts | 32 ++------ backend/src/controllers/submissions.ts | 12 +-- backend/src/controllers/teachers.ts | 88 ++++++++------------ backend/src/controllers/users.ts | 91 --------------------- backend/src/services/teachers.ts | 34 ++++---- 10 files changed, 75 insertions(+), 194 deletions(-) delete mode 100644 backend/src/controllers/users.ts diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index 03332469..28961ce2 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -37,7 +37,7 @@ export async function createAssignmentHandler(req: Request, re return; } - res.status(201).json({ assignment: assignment }); + res.status(201).json(assignment); } export async function getAssignmentHandler(req: Request, res: Response): Promise { diff --git a/backend/src/controllers/classes.ts b/backend/src/controllers/classes.ts index ca2f5698..a571176b 100644 --- a/backend/src/controllers/classes.ts +++ b/backend/src/controllers/classes.ts @@ -28,7 +28,7 @@ export async function createClassHandler(req: Request, res: Response): Promise { diff --git a/backend/src/controllers/groups.ts b/backend/src/controllers/groups.ts index b7bfd212..a5e7daa0 100644 --- a/backend/src/controllers/groups.ts +++ b/backend/src/controllers/groups.ts @@ -66,7 +66,7 @@ export async function createGroupHandler(req: Request, res: Response): Promise { diff --git a/backend/src/controllers/learning-objects.ts b/backend/src/controllers/learning-objects.ts index 455a4006..e9d0d727 100644 --- a/backend/src/controllers/learning-objects.ts +++ b/backend/src/controllers/learning-objects.ts @@ -40,7 +40,7 @@ export async function getAllLearningObjects(req: Request, res: Response): Promis learningObjects = await learningObjectService.getLearningObjectIdsFromPath(learningPathId); } - res.json(learningObjects); + res.json({ learningObjects: learningObjects }); } export async function getLearningObject(req: Request, res: Response): Promise { diff --git a/backend/src/controllers/questions.ts b/backend/src/controllers/questions.ts index 917b48ae..604954b2 100644 --- a/backend/src/controllers/questions.ts +++ b/backend/src/controllers/questions.ts @@ -48,7 +48,7 @@ export async function getAllQuestionsHandler(req: Request, res: Response): Promi if (!questions) { res.status(404).json({ error: `Questions not found.` }); } else { - res.json(questions); + res.json({ questions: questions }); } } @@ -81,7 +81,7 @@ export async function getQuestionAnswersHandler(req: Request, res: Response): Pr if (!answers) { res.status(404).json({ error: `Questions not found.` }); } else { - res.json(answers); + res.json({ answers: answers }); } } diff --git a/backend/src/controllers/students.ts b/backend/src/controllers/students.ts index 6c253cff..8b039545 100644 --- a/backend/src/controllers/students.ts +++ b/backend/src/controllers/students.ts @@ -9,13 +9,8 @@ import { getStudentGroups, getStudentSubmissions, } from '../services/students.js'; -import { ClassDTO } from '../interfaces/class.js'; -import { getAllAssignments } from '../services/assignments.js'; -import { getUserHandler } from './users.js'; -import { Student } from '../entities/users/student.entity.js'; import { StudentDTO } from '../interfaces/student.js'; import { getStudentRepository } from '../data/repositories.js'; -import { UserDTO } from '../interfaces/user.js'; // TODO: accept arguments (full, ...) // TODO: endpoints @@ -31,7 +26,7 @@ export async function getAllStudentsHandler(req: Request, res: Response): Promis return; } - res.status(201).json(students); + res.json({ students: students }); } export async function getStudentHandler(req: Request, res: Response): Promise { @@ -51,7 +46,7 @@ export async function getStudentHandler(req: Request, res: Response): Promise { - try { - const full = req.query.full === 'true'; - const username = req.params.id; + const full = req.query.full === 'true'; + const username = req.params.id; - const classes = await getStudentClasses(username, full); + const classes = await getStudentClasses(username, full); - res.json({ - classes: classes, - endpoints: { - self: `${req.baseUrl}/${req.params.id}`, - classes: `${req.baseUrl}/${req.params.id}/invitations`, - questions: `${req.baseUrl}/${req.params.id}/assignments`, - students: `${req.baseUrl}/${req.params.id}/students`, - }, - }); - } catch (error) { - console.error('Error fetching learning objects:', error); - res.status(500).json({ error: 'Internal server error' }); - } + res.json({ + classes: classes, + }); } // TODO diff --git a/backend/src/controllers/submissions.ts b/backend/src/controllers/submissions.ts index 1e66dbe9..361daaff 100644 --- a/backend/src/controllers/submissions.ts +++ b/backend/src/controllers/submissions.ts @@ -36,10 +36,11 @@ export async function createSubmissionHandler(req: Request, res: Response) { const submission = await createSubmission(submissionDTO); if (!submission) { - res.status(404).json({ error: 'Submission not added' }); - } else { - res.json(submission); + res.status(400).json({ error: 'Failed to create submission' }); + return; } + + res.json(submission); } export async function deleteSubmissionHandler(req: Request, res: Response) { @@ -53,7 +54,8 @@ export async function deleteSubmissionHandler(req: Request, res: Response) { if (!submission) { res.status(404).json({ error: 'Submission not found' }); - } else { - res.json(submission); + return; } + + res.json(submission); } diff --git a/backend/src/controllers/teachers.ts b/backend/src/controllers/teachers.ts index 52e5e713..f04e7477 100644 --- a/backend/src/controllers/teachers.ts +++ b/backend/src/controllers/teachers.ts @@ -4,17 +4,10 @@ import { deleteTeacher, getAllTeachers, getClassesByTeacher, - getClassIdsByTeacher, - getQuestionIdsByTeacher, getQuestionsByTeacher, - getStudentIdsByTeacher, getStudentsByTeacher, getTeacher, } from '../services/teachers.js'; -import { ClassDTO } from '../interfaces/class.js'; -import { StudentDTO } from '../interfaces/student.js'; -import { QuestionDTO, QuestionId } from '../interfaces/question.js'; -import { Teacher } from '../entities/users/teacher.entity.js'; import { TeacherDTO } from '../interfaces/teacher.js'; import { getTeacherRepository } from '../data/repositories.js'; @@ -30,7 +23,7 @@ export async function getAllTeachersHandler(req: Request, res: Response): Promis return; } - res.status(201).json(teachers); + res.status(201).json({ teachers: teachers }); } export async function getTeacherHandler(req: Request, res: Response): Promise { @@ -45,7 +38,7 @@ export async function getTeacherHandler(req: Request, res: Response): Promise { - try { - const username = req.params.username as string; - const full = req.query.full === 'true'; + const username = req.params.username as string; + const full = req.query.full === 'true'; - if (!username) { - res.status(400).json({ error: 'Missing required field: username' }); - return; - } - - const classes: ClassDTO[] | string[] = full ? await getClassesByTeacher(username) : await getClassIdsByTeacher(username); - - res.status(201).json(classes); - } catch (error) { - console.error('Error fetching classes by teacher:', error); - res.status(500).json({ error: 'Internal server error' }); + if (!username) { + res.status(400).json({ error: 'Missing required field: username' }); + return; } + + const classes = await getClassesByTeacher(username, full); + + res.status(201).json({ classes: classes }); } export async function getTeacherStudentHandler(req: Request, res: Response): Promise { - try { - const username = req.params.username as string; - const full = req.query.full === 'true'; + const username = req.params.username as string; + const full = req.query.full === 'true'; - if (!username) { - res.status(400).json({ error: 'Missing required field: username' }); - return; - } - - const students: StudentDTO[] | string[] = full ? await getStudentsByTeacher(username) : await getStudentIdsByTeacher(username); - - res.status(201).json(students); - } catch (error) { - console.error('Error fetching students by teacher:', error); - res.status(500).json({ error: 'Internal server error' }); + if (!username) { + res.status(400).json({ error: 'Missing required field: username' }); + return; } + + const students = await getStudentsByTeacher(username, full); + + res.json({ students: students }); } export async function getTeacherQuestionHandler(req: Request, res: Response): Promise { - try { - const username = req.params.username as string; - const full = req.query.full === 'true'; + const username = req.params.username as string; + const full = req.query.full === 'true'; - if (!username) { - res.status(400).json({ error: 'Missing required field: username' }); - return; - } - - const questions: QuestionDTO[] | QuestionId[] = full ? await getQuestionsByTeacher(username) : await getQuestionIdsByTeacher(username); - - res.status(201).json(questions); - } catch (error) { - console.error('Error fetching questions by teacher:', error); - res.status(500).json({ error: 'Internal server error' }); + if (!username) { + res.status(400).json({ error: 'Missing required field: username' }); + return; } + + const questions = await getQuestionsByTeacher(username, full); + + res.json({ questions: questions }); } diff --git a/backend/src/controllers/users.ts b/backend/src/controllers/users.ts deleted file mode 100644 index 850c6549..00000000 --- a/backend/src/controllers/users.ts +++ /dev/null @@ -1,91 +0,0 @@ -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'; - - const users: UserDTO[] | string[] = full ? await service.getAllUsers() : await service.getAllUserIds(); - - if (!users) { - res.status(404).json({ error: `Users not found.` }); - return; - } - - res.status(201).json(users); - } catch (error) { - console.error('❌ Error fetching users:', error); - res.status(500).json({ error: 'Internal server error' }); - } -} - -export 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/services/teachers.ts b/backend/src/services/teachers.ts index f4dbedfe..fd80b692 100644 --- a/backend/src/services/teachers.ts +++ b/backend/src/services/teachers.ts @@ -76,27 +76,29 @@ export async function fetchClassesByTeacher(username: string): Promise { - return await fetchClassesByTeacher(username); -} - -export async function getClassIdsByTeacher(username: string): Promise { +export async function getClassesByTeacher(username: string, full: boolean): Promise { const classes = await fetchClassesByTeacher(username); + + if (full) { + return classes; + } + return classes.map((cls) => cls.id); } export async function fetchStudentsByTeacher(username: string) { - const classes = await getClassIdsByTeacher(username); + const classes = await getClassesByTeacher(username, false) as string[]; return (await Promise.all(classes.map(async (id) => getClassStudents(id)))).flat(); } -export async function getStudentsByTeacher(username: string): Promise { - return await fetchStudentsByTeacher(username); -} - -export async function getStudentIdsByTeacher(username: string): Promise { +export async function getStudentsByTeacher(username: string, full: boolean): Promise { const students = await fetchStudentsByTeacher(username); + + if (full) { + return students; + } + return students.map((student) => student.username); } @@ -118,12 +120,12 @@ export async function fetchTeacherQuestions(username: string): Promise { - return await fetchTeacherQuestions(username); -} - -export async function getQuestionIdsByTeacher(username: string): Promise { +export async function getQuestionsByTeacher(username: string, full: boolean): Promise { const questions = await fetchTeacherQuestions(username); + if (full) { + return questions; + } + return questions.map(mapToQuestionId); } From ce0fabc0e2ec49e24c1c7c107d5ae79ff36e013d Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sat, 22 Mar 2025 11:57:25 +0100 Subject: [PATCH 11/35] fix: meervoud en ongebruikte file gefixt #130 --- backend/src/controllers/classes.ts | 2 +- backend/src/services/{class.ts => classes.ts} | 0 backend/src/services/teachers.ts | 3 +- backend/src/services/users.ts | 41 ------------------- 4 files changed, 2 insertions(+), 44 deletions(-) rename backend/src/services/{class.ts => classes.ts} (100%) delete mode 100644 backend/src/services/users.ts diff --git a/backend/src/controllers/classes.ts b/backend/src/controllers/classes.ts index a571176b..d7d1e4cc 100644 --- a/backend/src/controllers/classes.ts +++ b/backend/src/controllers/classes.ts @@ -1,5 +1,5 @@ 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/classes.js'; import { ClassDTO } from '../interfaces/class.js'; export async function getAllClassesHandler(req: Request, res: Response): Promise { diff --git a/backend/src/services/class.ts b/backend/src/services/classes.ts similarity index 100% rename from backend/src/services/class.ts rename to backend/src/services/classes.ts diff --git a/backend/src/services/teachers.ts b/backend/src/services/teachers.ts index fd80b692..f86bc3d7 100644 --- a/backend/src/services/teachers.ts +++ b/backend/src/services/teachers.ts @@ -7,10 +7,9 @@ import { } 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 { getClassStudents } from './classes.js'; import { StudentDTO } from '../interfaces/student.js'; import { mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId } from '../interfaces/question.js'; -import { UserService } from './users.js'; import { mapToUser } from '../interfaces/user.js'; import { mapToTeacher, mapToTeacherDTO, TeacherDTO } from '../interfaces/teacher.js'; diff --git a/backend/src/services/users.ts b/backend/src/services/users.ts deleted file mode 100644 index 65ed5d4c..00000000 --- a/backend/src/services/users.ts +++ /dev/null @@ -1,41 +0,0 @@ -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); - } -} From 826803109617454cbd2b777cd46679554ec6f531 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sat, 22 Mar 2025 12:17:25 +0100 Subject: [PATCH 12/35] fix: foute endpoints in app.ts weggehaald --- backend/src/routes/router.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/backend/src/routes/router.ts b/backend/src/routes/router.ts index 639857a7..204ea3d6 100644 --- a/backend/src/routes/router.ts +++ b/backend/src/routes/router.ts @@ -22,11 +22,7 @@ router.get('/', (_, res: Response) => { }); router.use('/student', studentRouter /* #swagger.tags = ['Student'] */); -router.use('/group', groupRouter /* #swagger.tags = ['Group'] */); -router.use('/assignment', assignmentRouter /* #swagger.tags = ['Assignment'] */); -router.use('/submission', submissionRouter /* #swagger.tags = ['Submission'] */); router.use('/class', classRouter /* #swagger.tags = ['Class'] */); -router.use('/question', questionRouter /* #swagger.tags = ['Question'] */); router.use('/auth', authRouter /* #swagger.tags = ['Auth'] */); router.use('/theme', themeRoutes /* #swagger.tags = ['Theme'] */); router.use('/learningPath', learningPathRoutes /* #swagger.tags = ['Learning Path'] */); From d65bdd4fb4d224f24f8d3414ecfb419c0d3cb11a Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sat, 22 Mar 2025 13:46:29 +0100 Subject: [PATCH 13/35] feat: 'full' query toegevoegd aan alle endpoints waar nodig --- backend/src/controllers/assignments.ts | 4 +++- backend/src/controllers/classes.ts | 27 ++++++++------------------ backend/src/controllers/groups.ts | 4 ++-- backend/src/controllers/students.ts | 7 +++---- backend/src/controllers/teachers.ts | 12 +++++------- backend/src/interfaces/class.ts | 6 ------ backend/src/interfaces/submission.ts | 18 +++++++++++++++++ backend/src/services/assignments.ts | 10 +++++++--- backend/src/services/groups.ts | 15 +++++++++++--- backend/src/services/students.ts | 25 +++++++++++++----------- backend/src/services/teachers.ts | 15 +++++++------- 11 files changed, 80 insertions(+), 63 deletions(-) diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index 28961ce2..bc01c9c2 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -62,13 +62,15 @@ export async function getAssignmentHandler(req: Request, res: export async function getAssignmentsSubmissionsHandler(req: Request, res: Response): Promise { const classid = req.params.classid; const assignmentNumber = +req.params.id; + const full = req.query.full === 'true'; + if (isNaN(assignmentNumber)) { res.status(400).json({ error: 'Assignment id must be a number' }); return; } - const submissions = await getAssignmentsSubmissions(classid, assignmentNumber); + const submissions = await getAssignmentsSubmissions(classid, assignmentNumber, full); res.json({ submissions: submissions, diff --git a/backend/src/controllers/classes.ts b/backend/src/controllers/classes.ts index d7d1e4cc..7526f7c4 100644 --- a/backend/src/controllers/classes.ts +++ b/backend/src/controllers/classes.ts @@ -32,26 +32,15 @@ export async function createClassHandler(req: Request, res: Response): Promise { - try { - const classId = req.params.id; - const cls = await getClass(classId); + const classId = req.params.id; + const cls = await getClass(classId); - if (!cls) { - res.status(404).json({ error: 'Class not found' }); - return; - } - 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' }); + if (!cls) { + res.status(404).json({ error: 'Class not found' }); + return; } + + res.json(cls); } export async function getClassStudentsHandler(req: Request, res: Response): Promise { @@ -67,7 +56,7 @@ export async function getClassStudentsHandler(req: Request, res: Response): Prom 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 full = req.query.full === 'true'; const invitations = await getClassTeacherInvitations(classId, full); diff --git a/backend/src/controllers/groups.ts b/backend/src/controllers/groups.ts index a5e7daa0..c5087d02 100644 --- a/backend/src/controllers/groups.ts +++ b/backend/src/controllers/groups.ts @@ -71,7 +71,7 @@ export async function createGroupHandler(req: Request, 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; @@ -87,7 +87,7 @@ export async function getGroupSubmissionsHandler(req: Request, res: Response): P return; } - const submissions = await getGroupSubmissions(classId, assignmentId, groupId); + const submissions = await getGroupSubmissions(classId, assignmentId, groupId, full); res.json({ submissions: submissions, diff --git a/backend/src/controllers/students.ts b/backend/src/controllers/students.ts index 8b039545..e96deaad 100644 --- a/backend/src/controllers/students.ts +++ b/backend/src/controllers/students.ts @@ -17,9 +17,7 @@ import { getStudentRepository } from '../data/repositories.js'; export async function getAllStudentsHandler(req: Request, res: Response): Promise { const full = req.query.full === 'true'; - const studentRepository = getStudentRepository(); - - const students: StudentDTO[] | string[] = full ? await getAllStudents() : await getAllStudents(); + const students = await getAllStudents(full); if (!students) { res.status(404).json({ error: `Student not found.` }); @@ -121,8 +119,9 @@ export async function getStudentGroupsHandler(req: Request, res: Response): Prom export async function getStudentSubmissionsHandler(req: Request, res: Response): Promise { const username = req.params.id; + const full = req.query.full === 'true'; - const submissions = await getStudentSubmissions(username); + const submissions = await getStudentSubmissions(username, full); res.json({ submissions: submissions, diff --git a/backend/src/controllers/teachers.ts b/backend/src/controllers/teachers.ts index f04e7477..65c1a360 100644 --- a/backend/src/controllers/teachers.ts +++ b/backend/src/controllers/teachers.ts @@ -14,16 +14,14 @@ import { getTeacherRepository } from '../data/repositories.js'; export async function getAllTeachersHandler(req: Request, res: Response): Promise { const full = req.query.full === 'true'; - const teacherRepository = getTeacherRepository(); - - const teachers: TeacherDTO[] | string[] = full ? await getAllTeachers() : await getAllTeachers(); + const teachers = await getAllTeachers(full); if (!teachers) { res.status(404).json({ error: `Teacher not found.` }); return; } - res.status(201).json({ teachers: teachers }); + res.json({ teachers: teachers }); } export async function getTeacherHandler(req: Request, res: Response): Promise { @@ -38,12 +36,12 @@ export async function getTeacherHandler(req: Request, res: Response): Promise { diff --git a/backend/src/interfaces/class.ts b/backend/src/interfaces/class.ts index 371e3cae..ea1d4901 100644 --- a/backend/src/interfaces/class.ts +++ b/backend/src/interfaces/class.ts @@ -9,12 +9,6 @@ export interface ClassDTO { teachers: string[]; students: string[]; joinRequests: string[]; - endpoints?: { - self: string; - invitations: string; - assignments: string; - students: string; - }; } export function mapToClassDTO(cls: Class): ClassDTO { diff --git a/backend/src/interfaces/submission.ts b/backend/src/interfaces/submission.ts index fbaf520d..76e85490 100644 --- a/backend/src/interfaces/submission.ts +++ b/backend/src/interfaces/submission.ts @@ -17,6 +17,14 @@ export interface SubmissionDTO { content: string; } +export interface SubmissionDTOId { + learningObjectHruid: string; + learningObjectLanguage: Language; + learningObjectVersion: number; + + submissionNumber?: number; +} + export function mapToSubmissionDTO(submission: Submission): SubmissionDTO { return { learningObjectHruid: submission.learningObjectHruid, @@ -31,6 +39,16 @@ export function mapToSubmissionDTO(submission: Submission): SubmissionDTO { }; } +export function mapToSubmissionDTOId(submission: Submission): SubmissionDTOId { + return { + learningObjectHruid: submission.learningObjectHruid, + learningObjectLanguage: submission.learningObjectLanguage, + learningObjectVersion: submission.learningObjectVersion, + + submissionNumber: submission.submissionNumber, + } +} + export function mapToSubmission(submissionDTO: SubmissionDTO): Submission { const submission = new Submission(); submission.learningObjectHruid = submissionDTO.learningObjectHruid; diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts index be121810..06ff2efb 100644 --- a/backend/src/services/assignments.ts +++ b/backend/src/services/assignments.ts @@ -1,7 +1,7 @@ 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 { mapToSubmissionDTO, SubmissionDTO } from '../interfaces/submission.js'; +import { mapToSubmissionDTO, mapToSubmissionDTOId, SubmissionDTO, SubmissionDTOId } from '../interfaces/submission.js'; export async function getAllAssignments(classid: string, full: boolean): Promise { const classRepository = getClassRepository(); @@ -60,7 +60,7 @@ export async function getAssignment(classid: string, id: number): Promise { +export async function getAssignmentsSubmissions(classid: string, assignmentNumber: number, full: boolean): Promise { const classRepository = getClassRepository(); const cls = await classRepository.findById(classid); @@ -81,5 +81,9 @@ export async function getAssignmentsSubmissions(classid: string, assignmentNumbe const submissionRepository = getSubmissionRepository(); const submissions = (await Promise.all(groups.map((group) => submissionRepository.findAllSubmissionsForGroup(group)))).flat(); - return submissions.map(mapToSubmissionDTO); + if (full) { + return submissions.map(mapToSubmissionDTO); + } + + return submissions.map(mapToSubmissionDTOId); } diff --git a/backend/src/services/groups.ts b/backend/src/services/groups.ts index 91091703..45845954 100644 --- a/backend/src/services/groups.ts +++ b/backend/src/services/groups.ts @@ -8,7 +8,7 @@ import { } from '../data/repositories.js'; import { Group } from '../entities/assignments/group.entity.js'; import { GroupDTO, mapToGroupDTO, mapToGroupDTOId } from '../interfaces/group.js'; -import { mapToSubmissionDTO, SubmissionDTO } from '../interfaces/submission.js'; +import { mapToSubmissionDTO, mapToSubmissionDTOId, SubmissionDTO, SubmissionDTOId } from '../interfaces/submission.js'; export async function getGroup(classId: string, assignmentNumber: number, groupNumber: number, full: boolean): Promise { const classRepository = getClassRepository(); @@ -103,7 +103,12 @@ export async function getAllGroups(classId: string, assignmentNumber: number, fu 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, + full: boolean +): Promise { const classRepository = getClassRepository(); const cls = await classRepository.findById(classId); @@ -128,5 +133,9 @@ export async function getGroupSubmissions(classId: string, assignmentNumber: num const submissionRepository = getSubmissionRepository(); const submissions = await submissionRepository.findAllSubmissionsForGroup(group); - return submissions.map(mapToSubmissionDTO); + if (full) { + return submissions.map(mapToSubmissionDTO); + } + + return submissions.map(mapToSubmissionDTOId); } diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts index 5099a18d..7a224f8f 100644 --- a/backend/src/services/students.ts +++ b/backend/src/services/students.ts @@ -5,19 +5,18 @@ 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 { mapToSubmissionDTO, mapToSubmissionDTOId, SubmissionDTO, SubmissionDTOId } from '../interfaces/submission.js'; import { getAllAssignments } from './assignments.js'; -import { UserService } from './users.js'; -export async function getAllStudents(): Promise { +export async function getAllStudents(full: boolean): Promise { const studentRepository = getStudentRepository(); - const users = await studentRepository.findAll(); - return users.map(mapToStudentDTO); -} + const students = await studentRepository.findAll(); -export async function getAllStudentIds(): Promise { - const users = await getAllStudents(); - return users.map((user) => user.username); + if (full) { + return students.map(mapToStudentDTO); + } + + return students.map(student => student.username); } export async function getStudent(username: string): Promise { @@ -111,7 +110,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, full: boolean): Promise { const studentRepository = getStudentRepository(); const student = await studentRepository.findByUsername(username); @@ -122,5 +121,9 @@ export async function getStudentSubmissions(username: string): Promise { +export async function getAllTeachers(full: boolean): Promise { const teacherRepository = getTeacherRepository(); - const users = await teacherRepository.findAll(); - return users.map(mapToTeacherDTO); -} + const teachers = await teacherRepository.findAll(); -export async function getAllTeacherIds(): Promise { - const users = await getAllTeachers(); - return users.map((user) => user.username); + if (full) { + return teachers.map(mapToTeacherDTO); + } + + return teachers.map(teacher => teacher.username); } export async function getTeacher(username: string): Promise { From fe1a6b7eea3fc0e14599de8d344f7faab75f1689 Mon Sep 17 00:00:00 2001 From: Lint Action Date: Sat, 22 Mar 2025 12:52:47 +0000 Subject: [PATCH 14/35] style: fix linting issues met Prettier --- backend/src/controllers/assignments.ts | 1 - backend/src/controllers/submissions.ts | 2 +- backend/src/controllers/teachers.ts | 2 +- backend/src/interfaces/submission.ts | 2 +- backend/src/services/assignments.ts | 6 +++++- backend/src/services/groups.ts | 6 +++--- backend/src/services/students.ts | 2 +- backend/src/services/teachers.ts | 4 ++-- 8 files changed, 14 insertions(+), 11 deletions(-) diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index bc01c9c2..16dbb310 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -64,7 +64,6 @@ export async function getAssignmentsSubmissionsHandler(req: Request { +export async function getAssignmentsSubmissions( + classid: string, + assignmentNumber: number, + full: boolean +): Promise { const classRepository = getClassRepository(); const cls = await classRepository.findById(classid); diff --git a/backend/src/services/groups.ts b/backend/src/services/groups.ts index 45845954..d4ced41a 100644 --- a/backend/src/services/groups.ts +++ b/backend/src/services/groups.ts @@ -104,9 +104,9 @@ export async function getAllGroups(classId: string, assignmentNumber: number, fu } export async function getGroupSubmissions( - classId: string, - assignmentNumber: number, - groupNumber: number, + classId: string, + assignmentNumber: number, + groupNumber: number, full: boolean ): Promise { const classRepository = getClassRepository(); diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts index 7a224f8f..4775c8a4 100644 --- a/backend/src/services/students.ts +++ b/backend/src/services/students.ts @@ -16,7 +16,7 @@ export async function getAllStudents(full: boolean): Promise student.username); + return students.map((student) => student.username); } export async function getStudent(username: string): Promise { diff --git a/backend/src/services/teachers.ts b/backend/src/services/teachers.ts index 314563b9..464a809f 100644 --- a/backend/src/services/teachers.ts +++ b/backend/src/services/teachers.ts @@ -22,7 +22,7 @@ export async function getAllTeachers(full: boolean): Promise teacher.username); + return teachers.map((teacher) => teacher.username); } export async function getTeacher(username: string): Promise { @@ -87,7 +87,7 @@ export async function getClassesByTeacher(username: string, full: boolean): Prom } export async function fetchStudentsByTeacher(username: string) { - const classes = await getClassesByTeacher(username, false) as string[]; + const classes = (await getClassesByTeacher(username, false)) as string[]; return (await Promise.all(classes.map(async (id) => getClassStudents(id)))).flat(); } From 59e8f2fcf231081cec01c3a61bf112f68f80575a Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sun, 23 Mar 2025 19:02:09 +0100 Subject: [PATCH 15/35] fix: submissionDTO bevat nu learningObjectIdentifier ipv elk veld apart --- backend/src/interfaces/submission.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/backend/src/interfaces/submission.ts b/backend/src/interfaces/submission.ts index de47e81d..d4e4eb72 100644 --- a/backend/src/interfaces/submission.ts +++ b/backend/src/interfaces/submission.ts @@ -4,11 +4,10 @@ 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 { LearningObjectIdentifier } from './learning-content.js'; export interface SubmissionDTO { - learningObjectHruid: string; - learningObjectLanguage: Language; - learningObjectVersion: number; + learningObjectIdentifier: LearningObjectIdentifier; submissionNumber?: number; submitter: StudentDTO; @@ -27,9 +26,11 @@ export interface SubmissionDTOId { export function mapToSubmissionDTO(submission: Submission): SubmissionDTO { return { - learningObjectHruid: submission.learningObjectHruid, - learningObjectLanguage: submission.learningObjectLanguage, - learningObjectVersion: submission.learningObjectVersion, + learningObjectIdentifier: { + hruid: submission.learningObjectHruid, + language: submission.learningObjectLanguage, + version: submission.learningObjectVersion, + }, submissionNumber: submission.submissionNumber, submitter: mapToStudentDTO(submission.submitter), @@ -51,9 +52,9 @@ export function mapToSubmissionDTOId(submission: Submission): SubmissionDTOId { export function mapToSubmission(submissionDTO: SubmissionDTO): Submission { const submission = new Submission(); - submission.learningObjectHruid = submissionDTO.learningObjectHruid; - submission.learningObjectLanguage = submissionDTO.learningObjectLanguage; - submission.learningObjectVersion = submissionDTO.learningObjectVersion; + submission.learningObjectHruid = submissionDTO.learningObjectIdentifier.hruid; + submission.learningObjectLanguage = submissionDTO.learningObjectIdentifier.language; + submission.learningObjectVersion = submissionDTO.learningObjectIdentifier.version!; // Submission.submissionNumber = submissionDTO.submissionNumber; submission.submitter = mapToStudent(submissionDTO.submitter); // Submission.submissionTime = submissionDTO.time; From 1664642940fb80e83d78ec87fcfa2657380a9b08 Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sun, 23 Mar 2025 19:49:41 +0100 Subject: [PATCH 16/35] fix: juiste responsecodes en messages toegevoegd --- backend/src/controllers/groups.ts | 5 +++++ backend/src/controllers/questions.ts | 6 +++--- backend/src/controllers/students.ts | 8 +++++++ backend/src/controllers/teachers.ts | 15 +++++++++++++ backend/src/services/assignments.ts | 4 ++-- backend/src/services/classes.ts | 4 ++-- backend/src/services/questions.ts | 2 +- backend/src/services/submissions.ts | 2 +- backend/src/services/teachers.ts | 32 +++++++++++++++++++++------- 9 files changed, 61 insertions(+), 17 deletions(-) diff --git a/backend/src/controllers/groups.ts b/backend/src/controllers/groups.ts index c5087d02..38d5d5d0 100644 --- a/backend/src/controllers/groups.ts +++ b/backend/src/controllers/groups.ts @@ -28,6 +28,11 @@ export async function getGroupHandler(req: Request, res: Response): const group = await getGroup(classId, assignmentId, groupId, full); + if (!group) { + res.status(404).json({ error: 'Group not found' }); + return; + } + res.json(group); } diff --git a/backend/src/controllers/questions.ts b/backend/src/controllers/questions.ts index 604954b2..00a51329 100644 --- a/backend/src/controllers/questions.ts +++ b/backend/src/controllers/questions.ts @@ -76,10 +76,10 @@ export async function getQuestionAnswersHandler(req: Request, res: Response): Pr return; } - const answers = getAnswersByQuestion(questionId, full); + const answers = await 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: answers }); } @@ -96,7 +96,7 @@ 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 create question' }); } else { res.json(question); } diff --git a/backend/src/controllers/students.ts b/backend/src/controllers/students.ts index e96deaad..adc88ca2 100644 --- a/backend/src/controllers/students.ts +++ b/backend/src/controllers/students.ts @@ -58,6 +58,14 @@ export async function createStudentHandler(req: Request, res: Response) { } const newUser = await createStudent(userData); + + if (!newUser) { + res.status(500).json({ + error: 'Something went wrong while creating student' + }); + return; + } + res.status(201).json(newUser); } diff --git a/backend/src/controllers/teachers.ts b/backend/src/controllers/teachers.ts index 559e0d3d..726b76c0 100644 --- a/backend/src/controllers/teachers.ts +++ b/backend/src/controllers/teachers.ts @@ -94,6 +94,11 @@ export async function getTeacherClassHandler(req: Request, res: Response): Promi const classes = await getClassesByTeacher(username, full); + if (!classes) { + res.status(404).json({ error: 'Teacher not found' }); + return; + } + res.json({ classes: classes }); } @@ -108,6 +113,11 @@ export async function getTeacherStudentHandler(req: Request, res: Response): Pro const students = await getStudentsByTeacher(username, full); + if (!students) { + res.status(404).json({ error: 'Teacher not found' }); + return; + } + res.json({ students: students }); } @@ -122,5 +132,10 @@ export async function getTeacherQuestionHandler(req: Request, res: Response): Pr const questions = await getQuestionsByTeacher(username, full); + if (!questions) { + res.status(404).json({ error: 'Teacher not found' }); + return; + } + res.json({ questions: questions }); } diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts index 688f9a79..075f01d3 100644 --- a/backend/src/services/assignments.ts +++ b/backend/src/services/assignments.ts @@ -21,7 +21,7 @@ export async function getAllAssignments(classid: string, full: boolean): Promise 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); @@ -36,7 +36,7 @@ export async function createAssignment(classid: string, assignmentData: Assignme const newAssignment = assignmentRepository.create(assignment); await assignmentRepository.save(newAssignment); - return newAssignment; + return mapToAssignmentDTO(newAssignment); } catch (e) { return null; } diff --git a/backend/src/services/classes.ts b/backend/src/services/classes.ts index 9f6e1efe..8d69b1e3 100644 --- a/backend/src/services/classes.ts +++ b/backend/src/services/classes.ts @@ -21,7 +21,7 @@ export async function getAllClasses(full: boolean): Promise cls.classId!); } -export async function createClass(classData: ClassDTO): Promise { +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); @@ -42,7 +42,7 @@ export async function createClass(classData: ClassDTO): Promise { }); await classRepository.save(newClass); - return newClass; + return mapToClassDTO(newClass); } catch (e) { logger.error(e); return null; diff --git a/backend/src/services/questions.ts b/backend/src/services/questions.ts index ee003bcd..0e52440f 100644 --- a/backend/src/services/questions.ts +++ b/backend/src/services/questions.ts @@ -103,5 +103,5 @@ export async function deleteQuestion(questionId: QuestionId) { return null; } - return question; + return mapToQuestionDTO(question); } diff --git a/backend/src/services/submissions.ts b/backend/src/services/submissions.ts index a8fa96c7..0e1ad9ac 100644 --- a/backend/src/services/submissions.ts +++ b/backend/src/services/submissions.ts @@ -32,7 +32,7 @@ export async function createSubmission(submissionDTO: SubmissionDTO) { return null; } - return submission; + return mapToSubmissionDTO(submission); } export async function deleteSubmission(learningObjectHruid: string, language: Language, version: number, submissionNumber: number) { diff --git a/backend/src/services/teachers.ts b/backend/src/services/teachers.ts index 464a809f..77cea501 100644 --- a/backend/src/services/teachers.ts +++ b/backend/src/services/teachers.ts @@ -64,11 +64,11 @@ export async function deleteTeacher(username: string): Promise { +export async function fetchClassesByTeacher(username: string): Promise { const teacherRepository = getTeacherRepository(); const teacher = await teacherRepository.findByUsername(username); if (!teacher) { - return []; + return null; } const classRepository = getClassRepository(); @@ -76,9 +76,13 @@ export async function fetchClassesByTeacher(username: string): Promise { +export async function getClassesByTeacher(username: string, full: boolean): Promise { const classes = await fetchClassesByTeacher(username); + if (!classes) { + return null; + } + if (full) { return classes; } @@ -86,15 +90,23 @@ export async function getClassesByTeacher(username: string, full: boolean): Prom return classes.map((cls) => cls.id); } -export async function fetchStudentsByTeacher(username: string) { +export async function fetchStudentsByTeacher(username: string): Promise { const classes = (await getClassesByTeacher(username, false)) as string[]; + if (!classes) { + return null; + } + return (await Promise.all(classes.map(async (id) => getClassStudents(id)))).flat(); } -export async function getStudentsByTeacher(username: string, full: boolean): Promise { +export async function getStudentsByTeacher(username: string, full: boolean): Promise { const students = await fetchStudentsByTeacher(username); + if (!students) { + return null; + } + if (full) { return students; } @@ -102,11 +114,11 @@ export async function getStudentsByTeacher(username: string, full: boolean): Pro return students.map((student) => student.username); } -export async function fetchTeacherQuestions(username: string): Promise { +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.`); + return null; } // Find all learning objects that this teacher manages @@ -120,9 +132,13 @@ export async function fetchTeacherQuestions(username: string): Promise { +export async function getQuestionsByTeacher(username: string, full: boolean): Promise { const questions = await fetchTeacherQuestions(username); + if (!questions) { + return null; + } + if (full) { return questions; } From 08d8dd924c5b44b0c478aa5a51f904a1b80771cb Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sun, 23 Mar 2025 19:51:13 +0100 Subject: [PATCH 17/35] fix: teacher route toegevoegd in app.ts --- backend/src/routes/router.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/routes/router.ts b/backend/src/routes/router.ts index 204ea3d6..15aa6724 100644 --- a/backend/src/routes/router.ts +++ b/backend/src/routes/router.ts @@ -1,5 +1,6 @@ import { Response, Router } from 'express'; import studentRouter from './students.js'; +import teacherRouter from './teachers.js'; import groupRouter from './groups.js'; import assignmentRouter from './assignments.js'; import submissionRouter from './submissions.js'; @@ -22,6 +23,7 @@ router.get('/', (_, res: Response) => { }); router.use('/student', studentRouter /* #swagger.tags = ['Student'] */); +router.use('/teacher', teacherRouter /* #swagger.tags = ['Teacher'] */); router.use('/class', classRouter /* #swagger.tags = ['Class'] */); router.use('/auth', authRouter /* #swagger.tags = ['Auth'] */); router.use('/theme', themeRoutes /* #swagger.tags = ['Theme'] */); From c219612d995915ddf668b830234691f9dde659ac Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Tue, 25 Mar 2025 10:08:36 +0100 Subject: [PATCH 18/35] refactor(backend): _i18n -> i18n --- backend/{_i18n => i18n}/de.yml | 0 backend/{_i18n => i18n}/en.yml | 0 backend/{_i18n => i18n}/fr.yml | 0 backend/{_i18n => i18n}/nl.yml | 0 backend/src/util/translation-helper.ts | 4 ++-- 5 files changed, 2 insertions(+), 2 deletions(-) rename backend/{_i18n => i18n}/de.yml (100%) rename backend/{_i18n => i18n}/en.yml (100%) rename backend/{_i18n => i18n}/fr.yml (100%) rename backend/{_i18n => i18n}/nl.yml (100%) diff --git a/backend/_i18n/de.yml b/backend/i18n/de.yml similarity index 100% rename from backend/_i18n/de.yml rename to backend/i18n/de.yml diff --git a/backend/_i18n/en.yml b/backend/i18n/en.yml similarity index 100% rename from backend/_i18n/en.yml rename to backend/i18n/en.yml diff --git a/backend/_i18n/fr.yml b/backend/i18n/fr.yml similarity index 100% rename from backend/_i18n/fr.yml rename to backend/i18n/fr.yml diff --git a/backend/_i18n/nl.yml b/backend/i18n/nl.yml similarity index 100% rename from backend/_i18n/nl.yml rename to backend/i18n/nl.yml diff --git a/backend/src/util/translation-helper.ts b/backend/src/util/translation-helper.ts index d0a83b02..55b354c6 100644 --- a/backend/src/util/translation-helper.ts +++ b/backend/src/util/translation-helper.ts @@ -8,12 +8,12 @@ const logger: Logger = getLogger(); export function loadTranslations(language: string): T { try { - const filePath = path.join(process.cwd(), '_i18n', `${language}.yml`); + const filePath = path.join(process.cwd(), 'i18n', `${language}.yml`); const yamlFile = fs.readFileSync(filePath, 'utf8'); return yaml.load(yamlFile) as T; } catch (error) { logger.warn(`Cannot load translation for ${language}, fallen back to dutch`, 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 3c58f798fb9ffd0aa88d903c02aa5deab2065429 Mon Sep 17 00:00:00 2001 From: Tibo De Peuter Date: Tue, 25 Mar 2025 10:08:44 +0100 Subject: [PATCH 19/35] fix(backend): i18n in Docker build --- backend/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/Dockerfile b/backend/Dockerfile index bd7db2ff..7c63c4b8 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -30,6 +30,7 @@ COPY package-lock.json backend/package.json ./ RUN npm install --silent --only=production COPY ./docs /docs +COPY ./backend/i18n /app/i18n COPY --from=build-stage /app/backend/dist ./dist/ EXPOSE 3000 From ba38356b30b5f9780c7df434b424793de6b129f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Mar 2025 14:53:15 +0000 Subject: [PATCH 20/35] chore(deps-dev): bump vite from 6.1.1 to 6.1.2 Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.1.1 to 6.1.2. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v6.1.2/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v6.1.2/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- frontend/package.json | 2 +- package-lock.json | 1753 +++++++++++++++++++++++++++++------------ 2 files changed, 1266 insertions(+), 489 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index e8133004..e6ce1426 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -42,7 +42,7 @@ "jsdom": "^26.0.0", "npm-run-all2": "^7.0.2", "typescript": "~5.7.3", - "vite": "^6.1.0", + "vite": "^6.1.2", "vite-plugin-vue-devtools": "^7.7.2", "vitest": "^3.0.5", "vue-tsc": "^2.2.2" diff --git a/package-lock.json b/package-lock.json index 2ee0a6ea..c54f2f5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -118,7 +118,7 @@ "jsdom": "^26.0.0", "npm-run-all2": "^7.0.2", "typescript": "~5.7.3", - "vite": "^6.1.0", + "vite": "^6.1.2", "vite-plugin-vue-devtools": "^7.7.2", "vitest": "^3.0.5", "vue-tsc": "^2.2.2" @@ -621,6 +621,16 @@ "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@colors/colors": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", @@ -641,7 +651,9 @@ } }, "node_modules/@csstools/color-helpers": { - "version": "5.0.1", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", + "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", "funding": [ { "type": "github", @@ -658,7 +670,9 @@ } }, "node_modules/@csstools/css-calc": { - "version": "2.1.1", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.2.tgz", + "integrity": "sha512-TklMyb3uBB28b5uQdxjReG4L80NxAqgrECqLZFQbyLekwwlcDDS8r3f07DKqeo8C4926Br0gf/ZDe17Zv4wIuw==", "funding": [ { "type": "github", @@ -679,7 +693,9 @@ } }, "node_modules/@csstools/css-color-parser": { - "version": "3.0.7", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.8.tgz", + "integrity": "sha512-pdwotQjCCnRPuNi06jFuP68cykU1f3ZWExLe/8MQ1LOs8Xq+fTkYgd+2V8mWUWMrOn9iS2HftPVaMZDaXzGbhQ==", "funding": [ { "type": "github", @@ -692,8 +708,8 @@ ], "license": "MIT", "dependencies": { - "@csstools/color-helpers": "^5.0.1", - "@csstools/css-calc": "^2.1.1" + "@csstools/color-helpers": "^5.0.2", + "@csstools/css-calc": "^2.1.2" }, "engines": { "node": ">=18" @@ -750,278 +766,6 @@ "kuler": "^2.0.0" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", - "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", - "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", - "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", - "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", - "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", - "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", - "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", - "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", - "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", - "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", - "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", - "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", - "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", - "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", - "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", - "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/linux-x64": { "version": "0.25.0", "cpu": [ @@ -1033,142 +777,7 @@ "os": [ "linux" ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", - "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", - "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", - "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", - "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", - "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", - "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", - "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", - "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "peer": true, "engines": { "node": ">=18" } @@ -1449,6 +1058,16 @@ "node": ">=12" } }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@jercle/yargonaut": { "version": "1.1.5", "dev": true, @@ -2033,6 +1652,216 @@ "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" + ], + "peer": true + }, + "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" + ], + "peer": true + }, + "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" + ], + "peer": true + }, + "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" + ], + "peer": true + }, + "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" + ], + "peer": true + }, + "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" + ], + "peer": true + }, + "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" + ], + "peer": true + }, + "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" + ], + "peer": true + }, + "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" + ], + "peer": true + }, + "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" + ], + "peer": true + }, + "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" + ], + "peer": true + }, + "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" + ], + "peer": true + }, + "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" + ], + "peer": true + }, + "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" + ], + "peer": true + }, "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.34.8", "cpu": [ @@ -2043,7 +1872,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-x64-musl": { "version": "4.34.8", @@ -2055,49 +1885,8 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.36.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.36.0.tgz", - "integrity": "sha512-qbqt4N7tokFwwSVlWDsjfoHgviS3n/vZ8LK0h1uLG9TYIRuUTJC88E1xb3LM2iqZ/WTqNQjYrtmtGmrmmawB6A==", - "cpu": [ - "arm64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.36.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.36.0.tgz", - "integrity": "sha512-t+RY0JuRamIocMuQcfwYSOkmdX9dtkr1PbhKW42AMvaDQa+jOdpUYysroTF/nuPpAaQMWp7ye+ndlmmthieJrQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.36.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.36.0.tgz", - "integrity": "sha512-aRXd7tRZkWLqGbChgcMMDEHjOKudo1kChb1Jt1IfR8cY/KIpgNviLeJy5FUb9IpSuQj8dU2fAYNMPW/hLKOSTw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "peer": true }, "node_modules/@scarf/scarf": { "version": "1.4.0", @@ -2665,12 +2454,14 @@ } }, "node_modules/@vitest/expect": { - "version": "3.0.6", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.8.tgz", + "integrity": "sha512-Xu6TTIavTvSSS6LZaA3EebWFr6tsoXPetOWNMOlc7LO88QVVBwq2oQWBoDiLCN6YTvNYsGSjqOO8CAdjom5DCQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.0.6", - "@vitest/utils": "3.0.6", + "@vitest/spy": "3.0.8", + "@vitest/utils": "3.0.8", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" }, @@ -2679,11 +2470,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "3.0.6", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.8.tgz", + "integrity": "sha512-n3LjS7fcW1BCoF+zWZxG7/5XvuYH+lsFg+BDwwAz0arIwHQJFUEsKBQ0BLU49fCxuM/2HSeBPHQD8WjgrxMfow==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.0.6", + "@vitest/spy": "3.0.8", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, @@ -2705,6 +2498,8 @@ }, "node_modules/@vitest/mocker/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": { @@ -2712,7 +2507,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.0.6", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.9.tgz", + "integrity": "sha512-OW9F8t2J3AwFEwENg3yMyKWweF7oRJlMyHOMIhO5F3n0+cgQAJZBjNgrF8dLwFTEXl5jUqBLXd9QyyKv8zEcmA==", "dev": true, "license": "MIT", "dependencies": { @@ -2723,11 +2520,13 @@ } }, "node_modules/@vitest/runner": { - "version": "3.0.6", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.8.tgz", + "integrity": "sha512-c7UUw6gEcOzI8fih+uaAXS5DwjlBaCJUo7KJ4VvJcjL95+DSR1kova2hFuRt3w41KZEFcOEiq098KkyrjXeM5w==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.0.6", + "@vitest/utils": "3.0.8", "pathe": "^2.0.3" }, "funding": { @@ -2735,11 +2534,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "3.0.6", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.8.tgz", + "integrity": "sha512-x8IlMGSEMugakInj44nUrLSILh/zy1f2/BgH0UeHpNyOocG18M9CWVIFBaXPt8TrqVZWmcPjwfG/ht5tnpba8A==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.0.6", + "@vitest/pretty-format": "3.0.8", "magic-string": "^0.30.17", "pathe": "^2.0.3" }, @@ -2747,8 +2548,23 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@vitest/snapshot/node_modules/@vitest/pretty-format": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.8.tgz", + "integrity": "sha512-BNqwbEyitFhzYMYHUVbIvepOyeQOSFA/NeJMIP9enMntkkxLgOcgABH6fjyXG85ipTgvero6noreavGIqfJcIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@vitest/spy": { - "version": "3.0.6", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.8.tgz", + "integrity": "sha512-MR+PzJa+22vFKYb934CejhR4BeRpMSoxkvNoDit68GQxRLSf11aT6CTj3XaqUU9rxgWJFnqicN/wxw6yBRkI1Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2759,11 +2575,13 @@ } }, "node_modules/@vitest/utils": { - "version": "3.0.6", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.8.tgz", + "integrity": "sha512-nkBC3aEhfX2PdtQI/QwAWp8qZWwzASsU4Npbcd5RdMPBSSLCpkZp52P3xku3s3uA0HIEhGvEcF8rNkBsz9dQ4Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.0.6", + "@vitest/pretty-format": "3.0.8", "loupe": "^3.1.3", "tinyrainbow": "^2.0.0" }, @@ -2771,6 +2589,19 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@vitest/utils/node_modules/@vitest/pretty-format": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.8.tgz", + "integrity": "sha512-BNqwbEyitFhzYMYHUVbIvepOyeQOSFA/NeJMIP9enMntkkxLgOcgABH6fjyXG85ipTgvero6noreavGIqfJcIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@volar/language-core": { "version": "2.4.11", "dev": true, @@ -3234,6 +3065,8 @@ }, "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": { @@ -3459,6 +3292,8 @@ }, "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": { @@ -3613,6 +3448,8 @@ }, "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": { @@ -3643,6 +3480,8 @@ }, "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": { @@ -4018,6 +3857,8 @@ }, "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": { @@ -4321,6 +4162,8 @@ }, "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" }, @@ -4386,6 +4229,438 @@ "@esbuild/win32-x64": "0.25.0" } }, + "node_modules/esbuild/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/escalade": { "version": "3.2.0", "license": "MIT", @@ -6446,6 +6721,8 @@ }, "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" }, @@ -7471,6 +7748,8 @@ }, "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": { @@ -7971,6 +8250,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -8184,6 +8464,51 @@ "fsevents": "~2.3.2" } }, + "node_modules/rollup/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" + ], + "peer": true + }, + "node_modules/rollup/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" + ], + "peer": true + }, + "node_modules/rollup/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" + ], + "peer": true + }, "node_modules/router": { "version": "2.1.0", "license": "MIT", @@ -9109,6 +9434,8 @@ }, "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": { @@ -9494,7 +9821,9 @@ } }, "node_modules/vite": { - "version": "6.1.1", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.1.2.tgz", + "integrity": "sha512-EiXfDyO/uNKhYOSlZ6+9qBz4H46A8Lr07pyjmb88KTbJ+xkXvnqtxvgtg2VxPU6Kfj8Ep0un9JLqdrCWLqIanw==", "dev": true, "license": "MIT", "dependencies": { @@ -9575,7 +9904,9 @@ } }, "node_modules/vite-node": { - "version": "3.0.6", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.8.tgz", + "integrity": "sha512-6PhR4H9VGlcwXZ+KWCdMqbtG649xCPZqfI9j2PsK1FcXgEzro5bGHcVKFCTqPLaNKZES8Evqv4LwvZARsq5qlg==", "dev": true, "license": "MIT", "dependencies": { @@ -9664,6 +9995,294 @@ "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" + ], + "peer": true, + "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" + ], + "peer": true, + "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" + ], + "peer": true, + "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" + ], + "peer": true, + "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" + ], + "peer": true, + "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" + ], + "peer": true, + "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" + ], + "peer": true, + "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" + ], + "peer": true, + "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" + ], + "peer": true, + "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" + ], + "peer": true, + "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" + ], + "peer": true, + "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" + ], + "peer": true, + "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" + ], + "peer": true, + "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" + ], + "peer": true, + "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" + ], + "peer": true, + "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" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, "node_modules/vite/node_modules/@esbuild/linux-x64": { "version": "0.24.2", "cpu": [ @@ -9675,6 +10294,151 @@ "os": [ "linux" ], + "peer": true, + "engines": { + "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" + ], + "peer": true, + "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" + ], + "peer": true, + "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" + ], + "peer": true, + "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" + ], + "peer": true, + "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" + ], + "peer": true, + "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" + ], + "peer": true, + "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" + ], + "peer": true, + "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" + ], + "peer": true, "engines": { "node": ">=18" } @@ -9718,18 +10482,36 @@ "@esbuild/win32-x64": "0.24.2" } }, + "node_modules/vite/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/vitest": { - "version": "3.0.6", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.8.tgz", + "integrity": "sha512-dfqAsNqRGUc8hB9OVR2P0w8PZPEckti2+5rdZip0WIz9WW0MnImJ8XiR61QhqLa92EQzKP2uPkzenKOAHyEIbA==", "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", + "@vitest/expect": "3.0.8", + "@vitest/mocker": "3.0.8", + "@vitest/pretty-format": "^3.0.8", + "@vitest/runner": "3.0.8", + "@vitest/snapshot": "3.0.8", + "@vitest/spy": "3.0.8", + "@vitest/utils": "3.0.8", "chai": "^5.2.0", "debug": "^4.4.0", "expect-type": "^1.1.0", @@ -9741,7 +10523,7 @@ "tinypool": "^1.0.2", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0", - "vite-node": "3.0.6", + "vite-node": "3.0.8", "why-is-node-running": "^2.3.0" }, "bin": { @@ -9757,8 +10539,8 @@ "@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", + "@vitest/browser": "3.0.8", + "@vitest/ui": "3.0.8", "happy-dom": "*", "jsdom": "*" }, @@ -10281,11 +11063,6 @@ "node": ">=10" } }, - "node_modules/yallist": { - "version": "3.1.1", - "dev": true, - "license": "ISC" - }, "node_modules/yargs": { "version": "17.7.2", "dev": true, From e3559d54a3832d5829a753217fd5e9c055e782d9 Mon Sep 17 00:00:00 2001 From: Lint Action Date: Tue, 25 Mar 2025 14:53:49 +0000 Subject: [PATCH 21/35] style: fix linting issues met ESLint --- frontend/src/queries/themes.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/frontend/src/queries/themes.ts b/frontend/src/queries/themes.ts index 9892c141..a4317c09 100644 --- a/frontend/src/queries/themes.ts +++ b/frontend/src/queries/themes.ts @@ -4,22 +4,18 @@ import {type MaybeRefOrGetter, toValue} from "vue"; const themeController = getThemeController(); -export const useThemeQuery = (language: MaybeRefOrGetter) => { - return useQuery({ +export const useThemeQuery = (language: MaybeRefOrGetter) => useQuery({ queryKey: ['themes', language], queryFn: () => { const lang = toValue(language); return themeController.getAll(lang); }, - enabled: () => !!toValue(language), + enabled: () => Boolean(toValue(language)), }); -}; -export const useThemeHruidsQuery = (themeKey: string | null) => { - return useQuery({ +export const useThemeHruidsQuery = (themeKey: string | null) => useQuery({ queryKey: ['theme-hruids', themeKey], queryFn: () => themeController.getHruidsByKey(themeKey!), - enabled: !!themeKey, + enabled: Boolean(themeKey), }); -}; From 468338d2a4c6ac8040838a0d026a5d4799ec2735 Mon Sep 17 00:00:00 2001 From: Lint Action Date: Tue, 25 Mar 2025 14:53:54 +0000 Subject: [PATCH 22/35] style: fix linting issues met Prettier --- frontend/src/components/BrowseThemes.vue | 69 +++++----- frontend/src/components/MenuBar.vue | 2 +- frontend/src/components/ThemeCard.vue | 77 +++++------ frontend/src/controllers/base-controller.ts | 2 +- frontend/src/controllers/controllers.ts | 2 +- frontend/src/controllers/themes.ts | 2 +- frontend/src/main.ts | 2 +- frontend/src/queries/themes.ts | 17 +-- frontend/src/utils/constants.ts | 73 +++++++---- frontend/src/views/SingleTheme.vue | 10 +- frontend/src/views/homepage/UserHomePage.vue | 128 +++++++++---------- 11 files changed, 209 insertions(+), 175 deletions(-) diff --git a/frontend/src/components/BrowseThemes.vue b/frontend/src/components/BrowseThemes.vue index eeea2c81..97ff8352 100644 --- a/frontend/src/components/BrowseThemes.vue +++ b/frontend/src/components/BrowseThemes.vue @@ -1,47 +1,56 @@ - diff --git a/frontend/src/controllers/base-controller.ts b/frontend/src/controllers/base-controller.ts index adc0c8c0..c6024e52 100644 --- a/frontend/src/controllers/base-controller.ts +++ b/frontend/src/controllers/base-controller.ts @@ -1,4 +1,4 @@ -import {apiConfig} from "@/config.ts"; +import { apiConfig } from "@/config.ts"; export class BaseController { protected baseUrl: string; diff --git a/frontend/src/controllers/controllers.ts b/frontend/src/controllers/controllers.ts index b3bbeb61..ad526276 100644 --- a/frontend/src/controllers/controllers.ts +++ b/frontend/src/controllers/controllers.ts @@ -1,4 +1,4 @@ -import {ThemeController} from "@/controllers/themes.ts"; +import { ThemeController } from "@/controllers/themes.ts"; export function controllerGetter(Factory: new () => T): () => T { let instance: T | undefined; diff --git a/frontend/src/controllers/themes.ts b/frontend/src/controllers/themes.ts index 447c9248..bc76985e 100644 --- a/frontend/src/controllers/themes.ts +++ b/frontend/src/controllers/themes.ts @@ -1,4 +1,4 @@ -import {BaseController} from "@/controllers/base-controller.ts"; +import { BaseController } from "@/controllers/base-controller.ts"; export class ThemeController extends BaseController { constructor() { diff --git a/frontend/src/main.ts b/frontend/src/main.ts index fc531957..5945a2ab 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -10,7 +10,7 @@ import i18n from "./i18n/i18n.ts"; // Components import App from "./App.vue"; import router from "./router"; -import { VueQueryPlugin, QueryClient } from '@tanstack/vue-query'; +import { VueQueryPlugin, QueryClient } from "@tanstack/vue-query"; const app = createApp(App); diff --git a/frontend/src/queries/themes.ts b/frontend/src/queries/themes.ts index a4317c09..4568b7b4 100644 --- a/frontend/src/queries/themes.ts +++ b/frontend/src/queries/themes.ts @@ -1,11 +1,12 @@ -import { useQuery } from '@tanstack/vue-query'; -import { getThemeController } from '@/controllers/controllers'; -import {type MaybeRefOrGetter, toValue} from "vue"; +import { useQuery } from "@tanstack/vue-query"; +import { getThemeController } from "@/controllers/controllers"; +import { type MaybeRefOrGetter, toValue } from "vue"; const themeController = getThemeController(); -export const useThemeQuery = (language: MaybeRefOrGetter) => useQuery({ - queryKey: ['themes', language], +export const useThemeQuery = (language: MaybeRefOrGetter) => + useQuery({ + queryKey: ["themes", language], queryFn: () => { const lang = toValue(language); return themeController.getAll(lang); @@ -13,9 +14,9 @@ export const useThemeQuery = (language: MaybeRefOrGetter) => useQuery({ enabled: () => Boolean(toValue(language)), }); -export const useThemeHruidsQuery = (themeKey: string | null) => useQuery({ - queryKey: ['theme-hruids', themeKey], +export const useThemeHruidsQuery = (themeKey: string | null) => + useQuery({ + queryKey: ["theme-hruids", themeKey], queryFn: () => themeController.getHruidsByKey(themeKey!), enabled: Boolean(themeKey), }); - diff --git a/frontend/src/services/auth/auth-service.ts b/frontend/src/services/auth/auth-service.ts index a779c8a0..9fc0c434 100644 --- a/frontend/src/services/auth/auth-service.ts +++ b/frontend/src/services/auth/auth-service.ts @@ -5,7 +5,7 @@ import { computed, reactive } from "vue"; import type { AuthState, Role, UserManagersForRoles } from "@/services/auth/auth.d.ts"; import { User, UserManager } from "oidc-client-ts"; -import {AUTH_CONFIG_ENDPOINT, loadAuthConfig} from "@/services/auth/auth-config-loader.ts"; +import { AUTH_CONFIG_ENDPOINT, 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"; diff --git a/frontend/src/utils/constants.ts b/frontend/src/utils/constants.ts index e046d9c3..b56437d7 100644 --- a/frontend/src/utils/constants.ts +++ b/frontend/src/utils/constants.ts @@ -1,37 +1,64 @@ export const THEMES_KEYS = [ - "kiks", "art", "socialrobot", "agriculture", "wegostem", - "computational_thinking", "math_with_python", "python_programming", - "stem", "care", "chatbot", "physical_computing", "algorithms", "basics_ai" + "kiks", + "art", + "socialrobot", + "agriculture", + "wegostem", + "computational_thinking", + "math_with_python", + "python_programming", + "stem", + "care", + "chatbot", + "physical_computing", + "algorithms", + "basics_ai", ]; export const THEMESITEMS: Record = { - "all": THEMES_KEYS, - "culture": ["art", "wegostem", "chatbot"], + all: THEMES_KEYS, + culture: ["art", "wegostem", "chatbot"], "electricity-and-mechanics": ["socialrobot", "wegostem", "stem", "physical_computing"], "nature-and-climate": ["kiks", "agriculture"], - "agriculture": ["agriculture"], - "society": ["kiks", "socialrobot", "care", "chatbot"], - "math": ["kiks", "math_with_python", "python_programming", "stem", "care", "basics_ai"], - "technology": ["socialrobot", "wegostem", "computational_thinking", "stem", "physical_computing", "basics_ai"], - "algorithms": ["math_with_python", "python_programming", "stem", "algorithms", "basics_ai"], + agriculture: ["agriculture"], + society: ["kiks", "socialrobot", "care", "chatbot"], + math: ["kiks", "math_with_python", "python_programming", "stem", "care", "basics_ai"], + technology: ["socialrobot", "wegostem", "computational_thinking", "stem", "physical_computing", "basics_ai"], + algorithms: ["math_with_python", "python_programming", "stem", "algorithms", "basics_ai"], }; -export const AGEITEMS = [ - "all", "primary-school", "lower-secondary", "upper-secondary", "high-school", "older" -]; +export const AGEITEMS = ["all", "primary-school", "lower-secondary", "upper-secondary", "high-school", "older"]; export const AGE_TO_THEMES: Record = { - "all": THEMES_KEYS, + all: THEMES_KEYS, "primary-school": ["wegostem", "computational_thinking", "physical_computing"], "lower-secondary": ["socialrobot", "art", "wegostem", "computational_thinking", "physical_computing"], - "upper-secondary": ["kiks", "art", "socialrobot", "agriculture", - "computational_thinking", "math_with_python", "python_programming", - "stem", "care", "chatbot", "algorithms", "basics_ai"], - "high-school": [ - "kiks", "art", "agriculture", "computational_thinking", "math_with_python", "python_programming", - "stem", "care", "chatbot", "algorithms", "basics_ai" + "upper-secondary": [ + "kiks", + "art", + "socialrobot", + "agriculture", + "computational_thinking", + "math_with_python", + "python_programming", + "stem", + "care", + "chatbot", + "algorithms", + "basics_ai", ], - "older": [ - "kiks", "computational_thinking", "algorithms", "basics_ai" - ] + "high-school": [ + "kiks", + "art", + "agriculture", + "computational_thinking", + "math_with_python", + "python_programming", + "stem", + "care", + "chatbot", + "algorithms", + "basics_ai", + ], + older: ["kiks", "computational_thinking", "algorithms", "basics_ai"], }; diff --git a/frontend/src/views/HomePage.vue b/frontend/src/views/HomePage.vue index 0899902d..436205e6 100644 --- a/frontend/src/views/HomePage.vue +++ b/frontend/src/views/HomePage.vue @@ -28,7 +28,7 @@ alt="Dwengo logo" style="align-self: center" /> -

{{ t("homeTitle") }}

+

{{ t("homeTitle") }}

{{ t("homeIntroduction1") }}

diff --git a/frontend/src/views/SingleTheme.vue b/frontend/src/views/SingleTheme.vue index 73336fa3..1a35a59f 100644 --- a/frontend/src/views/SingleTheme.vue +++ b/frontend/src/views/SingleTheme.vue @@ -1,11 +1,7 @@ - + - + diff --git a/frontend/src/views/homepage/UserHomePage.vue b/frontend/src/views/homepage/UserHomePage.vue index b6cdea17..c4f933a5 100644 --- a/frontend/src/views/homepage/UserHomePage.vue +++ b/frontend/src/views/homepage/UserHomePage.vue @@ -1,13 +1,13 @@ From d8e5fed2fe4671508b0e5c6f0c0200cc2bd11b00 Mon Sep 17 00:00:00 2001 From: Gerald Schmittinger <165218235+geraldschmittinger@users.noreply.github.com> Date: Wed, 26 Mar 2025 11:30:17 +0100 Subject: [PATCH 31/35] fix(frontend): Redirect na login Na de login moet de gebruiker naar `/user` i.p.v. `/` geredirect worden --- frontend/src/views/CallbackPage.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/views/CallbackPage.vue b/frontend/src/views/CallbackPage.vue index 306dfe10..7f4d4958 100644 --- a/frontend/src/views/CallbackPage.vue +++ b/frontend/src/views/CallbackPage.vue @@ -8,7 +8,7 @@ onMounted(async () => { try { await auth.handleLoginCallback(); - await router.replace("/"); // Redirect to home (or dashboard) + await router.replace("/user"); // Redirect to theme page } catch (error) { console.error("OIDC callback error:", error); } From b3d17f4cb836b809ba83e7d33abea24914c15782 Mon Sep 17 00:00:00 2001 From: laurejablonski Date: Wed, 26 Mar 2025 18:46:13 +0100 Subject: [PATCH 32/35] fix: overbodige file verwijderd --- frontend/src/components/MenuBar.old.vue | 367 ------------------------ 1 file changed, 367 deletions(-) delete mode 100644 frontend/src/components/MenuBar.old.vue diff --git a/frontend/src/components/MenuBar.old.vue b/frontend/src/components/MenuBar.old.vue deleted file mode 100644 index 4945aec2..00000000 --- a/frontend/src/components/MenuBar.old.vue +++ /dev/null @@ -1,367 +0,0 @@ - - - - - From 00fb333ae8df5bdaa3ff623985d443aa2b643729 Mon Sep 17 00:00:00 2001 From: laurejablonski Date: Wed, 26 Mar 2025 18:47:08 +0100 Subject: [PATCH 33/35] style: linting --- frontend/src/components/MenuBar.vue | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/MenuBar.vue b/frontend/src/components/MenuBar.vue index df1102ca..50c2eabe 100644 --- a/frontend/src/components/MenuBar.vue +++ b/frontend/src/components/MenuBar.vue @@ -31,12 +31,12 @@ localStorage.setItem("user-lang", langCode); }; - // contains functionality to let the collapsed menu appear and disappear - // when the screen size varies + // Contains functionality to let the collapsed menu appear and disappear + // When the screen size varies const drawer = ref(false); - // when the user wants to logout, a popup is shown to verify this - // if verified, the user should be logged out + // When the user wants to logout, a popup is shown to verify this + // If verified, the user should be logged out const performLogout = () => { auth.logout(); }; From b9e71de2818bc31b62f1a200dd4e057e4304710a Mon Sep 17 00:00:00 2001 From: Adriaan Jacquet Date: Sat, 29 Mar 2025 20:15:38 +0100 Subject: [PATCH 34/35] fix: deel van linting problemen gefixt --- backend/src/config.ts | 1 - backend/src/controllers/students.ts | 1 - backend/src/controllers/teachers.ts | 1 - backend/src/data/repositories.ts | 2 -- backend/src/data/users/student-repository.ts | 1 - backend/src/data/users/teacher-repository.ts | 1 - .../src/entities/assignments/assignment.entity.ts | 2 +- backend/src/entities/assignments/group.entity.ts | 2 +- .../entities/classes/class-join-request.entity.ts | 14 +++++++------- backend/src/interfaces/assignment.ts | 4 +--- backend/src/interfaces/question.ts | 2 -- backend/src/interfaces/submission.ts | 2 -- backend/src/routes/router.ts | 4 ---- backend/src/routes/students.ts | 1 - backend/src/services/assignments.ts | 2 +- backend/src/services/classes.ts | 7 ++----- backend/src/services/groups.ts | 3 +-- backend/src/services/learning-objects.ts | 11 ++++++++--- 18 files changed, 22 insertions(+), 39 deletions(-) diff --git a/backend/src/config.ts b/backend/src/config.ts index 69af5d74..7e1b3b6a 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -1,5 +1,4 @@ import { EnvVars, getEnvVar } from './util/envvars.js'; -import { Language } from './entities/content/language.js'; // API export const DWENGO_API_BASE = getEnvVar(EnvVars.LearningContentRepoApiBaseUrl); diff --git a/backend/src/controllers/students.ts b/backend/src/controllers/students.ts index adc88ca2..5190c1d6 100644 --- a/backend/src/controllers/students.ts +++ b/backend/src/controllers/students.ts @@ -10,7 +10,6 @@ import { getStudentSubmissions, } from '../services/students.js'; import { StudentDTO } from '../interfaces/student.js'; -import { getStudentRepository } from '../data/repositories.js'; // TODO: accept arguments (full, ...) // TODO: endpoints diff --git a/backend/src/controllers/teachers.ts b/backend/src/controllers/teachers.ts index 726b76c0..7376abed 100644 --- a/backend/src/controllers/teachers.ts +++ b/backend/src/controllers/teachers.ts @@ -9,7 +9,6 @@ import { getTeacher, } from '../services/teachers.js'; import { TeacherDTO } from '../interfaces/teacher.js'; -import { getTeacherRepository } from '../data/repositories.js'; export async function getAllTeachersHandler(req: Request, res: Response): Promise { const full = req.query.full === 'true'; diff --git a/backend/src/data/repositories.ts b/backend/src/data/repositories.ts index 02385109..cdeb50c1 100644 --- a/backend/src/data/repositories.ts +++ b/backend/src/data/repositories.ts @@ -2,8 +2,6 @@ import { AnyEntity, EntityManager, EntityName, EntityRepository } from '@mikro-o import { forkEntityManager } from '../orm.js'; import { StudentRepository } from './users/student-repository.js'; import { Student } from '../entities/users/student.entity.js'; -import { User } from '../entities/users/user.entity.js'; -import { UserRepository } from './users/user-repository.js'; import { Teacher } from '../entities/users/teacher.entity.js'; import { TeacherRepository } from './users/teacher-repository.js'; import { Class } from '../entities/classes/class.entity.js'; diff --git a/backend/src/data/users/student-repository.ts b/backend/src/data/users/student-repository.ts index 0792678d..a13fbb22 100644 --- a/backend/src/data/users/student-repository.ts +++ b/backend/src/data/users/student-repository.ts @@ -1,5 +1,4 @@ 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'; diff --git a/backend/src/data/users/teacher-repository.ts b/backend/src/data/users/teacher-repository.ts index 2b2bee75..825b4d18 100644 --- a/backend/src/data/users/teacher-repository.ts +++ b/backend/src/data/users/teacher-repository.ts @@ -1,6 +1,5 @@ import { Teacher } from '../../entities/users/teacher.entity.js'; import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; -import { UserRepository } from './user-repository.js'; export class TeacherRepository extends DwengoEntityRepository { public findByUsername(username: string): Promise { diff --git a/backend/src/entities/assignments/assignment.entity.ts b/backend/src/entities/assignments/assignment.entity.ts index 692e2112..daa71ed6 100644 --- a/backend/src/entities/assignments/assignment.entity.ts +++ b/backend/src/entities/assignments/assignment.entity.ts @@ -1,4 +1,4 @@ -import { Collection, Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; +import { Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; import { Class } from '../classes/class.entity.js'; import { Group } from './group.entity.js'; import { Language } from '../content/language.js'; diff --git a/backend/src/entities/assignments/group.entity.ts b/backend/src/entities/assignments/group.entity.ts index 213e0f38..cfe21f7f 100644 --- a/backend/src/entities/assignments/group.entity.ts +++ b/backend/src/entities/assignments/group.entity.ts @@ -1,4 +1,4 @@ -import { Collection, Entity, ManyToMany, ManyToOne, PrimaryKey } from '@mikro-orm/core'; +import { Entity, ManyToMany, ManyToOne, PrimaryKey } from '@mikro-orm/core'; import { Assignment } from './assignment.entity.js'; import { Student } from '../users/student.entity.js'; import { GroupRepository } from '../../data/assignments/group-repository.js'; diff --git a/backend/src/entities/classes/class-join-request.entity.ts b/backend/src/entities/classes/class-join-request.entity.ts index bdef1f52..64a597bb 100644 --- a/backend/src/entities/classes/class-join-request.entity.ts +++ b/backend/src/entities/classes/class-join-request.entity.ts @@ -3,6 +3,12 @@ import { Student } from '../users/student.entity.js'; import { Class } from './class.entity.js'; import { ClassJoinRequestRepository } from '../../data/classes/class-join-request-repository.js'; +export enum ClassJoinRequestStatus { + Open = 'open', + Accepted = 'accepted', + Declined = 'declined', +} + @Entity({ repository: () => ClassJoinRequestRepository, }) @@ -21,10 +27,4 @@ export class ClassJoinRequest { @Enum(() => ClassJoinRequestStatus) status!: ClassJoinRequestStatus; -} - -export enum ClassJoinRequestStatus { - Open = 'open', - Accepted = 'accepted', - Declined = 'declined', -} +} \ No newline at end of file diff --git a/backend/src/interfaces/assignment.ts b/backend/src/interfaces/assignment.ts index 8f6120b6..eefa8c96 100644 --- a/backend/src/interfaces/assignment.ts +++ b/backend/src/interfaces/assignment.ts @@ -2,7 +2,7 @@ import { FALLBACK_LANG } from '../config.js'; import { Assignment } from '../entities/assignments/assignment.entity.js'; import { Class } from '../entities/classes/class.entity.js'; import { languageMap } from '../entities/content/language.js'; -import { GroupDTO, mapToGroupDTO } from './group.js'; +import { GroupDTO } from './group.js'; export interface AssignmentDTO { id: number; @@ -46,7 +46,5 @@ export function mapToAssignment(assignmentData: AssignmentDTO, cls: Class): Assi assignment.learningPathLanguage = languageMap[assignmentData.language] || FALLBACK_LANG; assignment.within = cls; - console.log(assignment); - return assignment; } diff --git a/backend/src/interfaces/question.ts b/backend/src/interfaces/question.ts index 8cca42f6..0da87eb7 100644 --- a/backend/src/interfaces/question.ts +++ b/backend/src/interfaces/question.ts @@ -1,8 +1,6 @@ import { Question } from '../entities/questions/question.entity.js'; -import { UserDTO } from './user.js'; import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; import { mapToStudentDTO, StudentDTO } from './student.js'; -import { TeacherDTO } from './teacher.js'; export interface QuestionDTO { learningObjectIdentifier: LearningObjectIdentifier; diff --git a/backend/src/interfaces/submission.ts b/backend/src/interfaces/submission.ts index d4e4eb72..98cc4f22 100644 --- a/backend/src/interfaces/submission.ts +++ b/backend/src/interfaces/submission.ts @@ -2,8 +2,6 @@ import { Submission } from '../entities/assignments/submission.entity.js'; import { Language } from '../entities/content/language.js'; import { GroupDTO, mapToGroupDTO } from './group.js'; import { mapToStudent, mapToStudentDTO, StudentDTO } from './student.js'; -import { mapToUser } from './user'; -import { Student } from '../entities/users/student.entity'; import { LearningObjectIdentifier } from './learning-content.js'; export interface SubmissionDTO { diff --git a/backend/src/routes/router.ts b/backend/src/routes/router.ts index 15aa6724..99d4312c 100644 --- a/backend/src/routes/router.ts +++ b/backend/src/routes/router.ts @@ -1,11 +1,7 @@ import { Response, Router } from 'express'; import studentRouter from './students.js'; import teacherRouter from './teachers.js'; -import groupRouter from './groups.js'; -import assignmentRouter from './assignments.js'; -import submissionRouter from './submissions.js'; 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/routes/students.ts b/backend/src/routes/students.ts index 7ed7a666..6efbab39 100644 --- a/backend/src/routes/students.ts +++ b/backend/src/routes/students.ts @@ -9,7 +9,6 @@ import { getStudentHandler, getStudentSubmissionsHandler, } from '../controllers/students.js'; -import { getStudentGroups } from '../services/students.js'; const router = express.Router(); // Root endpoint used to search objects diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts index 075f01d3..a21a96fa 100644 --- a/backend/src/services/assignments.ts +++ b/backend/src/services/assignments.ts @@ -1,5 +1,4 @@ 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 { mapToSubmissionDTO, mapToSubmissionDTOId, SubmissionDTO, SubmissionDTOId } from '../interfaces/submission.js'; @@ -38,6 +37,7 @@ export async function createAssignment(classid: string, assignmentData: Assignme return mapToAssignmentDTO(newAssignment); } catch (e) { + console.error(e); return null; } } diff --git a/backend/src/services/classes.ts b/backend/src/services/classes.ts index 8d69b1e3..5b1e3cfc 100644 --- a/backend/src/services/classes.ts +++ b/backend/src/services/classes.ts @@ -1,5 +1,4 @@ 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'; @@ -24,13 +23,11 @@ export async function getAllClasses(full: boolean): Promise { const teacherRepository = getTeacherRepository(); const teacherUsernames = classData.teachers || []; - const teachers = (await Promise.all(teacherUsernames.map((id) => teacherRepository.findByUsername(id)))).filter((teacher) => teacher != null); + const teachers = (await Promise.all(teacherUsernames.map((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 students = (await Promise.all(studentUsernames.map((id) => studentRepository.findByUsername(id)))).filter((student) => student !== null); const classRepository = getClassRepository(); diff --git a/backend/src/services/groups.ts b/backend/src/services/groups.ts index d4ced41a..4a1cbbf0 100644 --- a/backend/src/services/groups.ts +++ b/backend/src/services/groups.ts @@ -1,4 +1,3 @@ -import { GroupRepository } from '../data/assignments/group-repository.js'; import { getAssignmentRepository, getClassRepository, @@ -43,7 +42,7 @@ export async function createGroup(groupData: GroupDTO, classid: string, assignme const studentRepository = getStudentRepository(); const memberUsernames = (groupData.members as string[]) || []; // TODO check if groupdata.members is a list - const members = (await Promise.all([...memberUsernames].map((id) => studentRepository.findByUsername(id)))).filter((student) => student != null); + const members = (await Promise.all([...memberUsernames].map((id) => studentRepository.findByUsername(id)))).filter((student) => student !== null); console.log(members); diff --git a/backend/src/services/learning-objects.ts b/backend/src/services/learning-objects.ts index fb579471..85141b1d 100644 --- a/backend/src/services/learning-objects.ts +++ b/backend/src/services/learning-objects.ts @@ -45,6 +45,13 @@ export async function getLearningObjectById(hruid: string, language: string): Pr return filterData(metadata, htmlUrl); } +/** + * Generic function to fetch learning paths + */ +function fetchLearningPaths(arg0: string[], language: string, arg2: string): LearningPathResponse | PromiseLike { + throw new Error('Function not implemented.'); +} + /** * Generic function to fetch learning objects (full data or just HRUIDs) */ @@ -85,6 +92,4 @@ export async function getLearningObjectsFromPath(hruid: string, language: string export async function getLearningObjectIdsFromPath(hruid: string, language: string): Promise { return (await fetchLearningObjects(hruid, false, language)) as string[]; } -function fetchLearningPaths(arg0: string[], language: string, arg2: string): LearningPathResponse | PromiseLike { - throw new Error('Function not implemented.'); -} + From e7518998e0ac5d37c73e8cb2bc844f4c1a96b086 Mon Sep 17 00:00:00 2001 From: Lint Action Date: Sun, 30 Mar 2025 08:14:03 +0000 Subject: [PATCH 35/35] style: fix linting issues met Prettier --- backend/src/controllers/students.ts | 2 +- backend/src/entities/classes/class-join-request.entity.ts | 2 +- backend/src/services/learning-objects.ts | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/backend/src/controllers/students.ts b/backend/src/controllers/students.ts index 5190c1d6..8ce5b11a 100644 --- a/backend/src/controllers/students.ts +++ b/backend/src/controllers/students.ts @@ -60,7 +60,7 @@ export async function createStudentHandler(req: Request, res: Response) { if (!newUser) { res.status(500).json({ - error: 'Something went wrong while creating student' + error: 'Something went wrong while creating student', }); return; } diff --git a/backend/src/entities/classes/class-join-request.entity.ts b/backend/src/entities/classes/class-join-request.entity.ts index 64a597bb..fdf13aa9 100644 --- a/backend/src/entities/classes/class-join-request.entity.ts +++ b/backend/src/entities/classes/class-join-request.entity.ts @@ -27,4 +27,4 @@ export class ClassJoinRequest { @Enum(() => ClassJoinRequestStatus) status!: ClassJoinRequestStatus; -} \ No newline at end of file +} diff --git a/backend/src/services/learning-objects.ts b/backend/src/services/learning-objects.ts index 85141b1d..faa77cb4 100644 --- a/backend/src/services/learning-objects.ts +++ b/backend/src/services/learning-objects.ts @@ -92,4 +92,3 @@ export async function getLearningObjectsFromPath(hruid: string, language: string export async function getLearningObjectIdsFromPath(hruid: string, language: string): Promise { return (await fetchLearningObjects(hruid, false, language)) as string[]; } -