diff --git a/.github/workflows/validate-pr-title.yml b/.github/workflows/validate-pr-title.yml new file mode 100644 index 000000000000..37e280ee2017 --- /dev/null +++ b/.github/workflows/validate-pr-title.yml @@ -0,0 +1,14 @@ +name: Validate PR Title + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review, edited] + +jobs: + validate-pr-title: + runs-on: ubuntu-latest + timeout-minutes: 1 + steps: + - uses: Slashgear/action-check-pr-title@v4.3.0 + with: + regexp: '^`(Programming exercises|Integrated code lifecycle|Quiz exercises|Modeling exercises|Text exercises|File upload exercises|Exam mode|Grading|Assessment|Communication|Notifications|Team exercises|Lectures|Integrated markdown editor|Plagiarism checks|Learning analytics|Adaptive learning|Learning path|Tutorial groups|Iris|Scalability|Usability|Performance|Infrastructure|Mobile apps|Development|General)`:\s[A-Z].*$' \ No newline at end of file diff --git a/build.gradle b/build.gradle index 29da90bf3674..163b2517748f 100644 --- a/build.gradle +++ b/build.gradle @@ -246,14 +246,15 @@ dependencies { implementation "org.gitlab4j:gitlab4j-api:6.0.0-rc.5" implementation "de.jplag:jplag:${jplag_version}" - implementation "de.jplag:java:${jplag_version}" - implementation "de.jplag:kotlin:${jplag_version}" + implementation "de.jplag:c:${jplag_version}" - implementation "de.jplag:swift:${jplag_version}" implementation "de.jplag:java:${jplag_version}" + implementation "de.jplag:javascript:${jplag_version}" + implementation "de.jplag:kotlin:${jplag_version}" implementation "de.jplag:python-3:${jplag_version}" + implementation "de.jplag:rlang:${jplag_version}" implementation "de.jplag:rust:${jplag_version}" - implementation "de.jplag:javascript:${jplag_version}" + implementation "de.jplag:swift:${jplag_version}" implementation "de.jplag:text:${jplag_version}" // those are transitive dependencies of JPlag Text --> Stanford NLP @@ -534,7 +535,7 @@ dependencies { testImplementation "org.awaitility:awaitility:4.2.2" testImplementation "org.apache.maven.shared:maven-invoker:3.3.0" testImplementation "org.gradle:gradle-tooling-api:8.10.2" - testImplementation "org.apache.maven.surefire:surefire-report-parser:3.5.0" + testImplementation "org.apache.maven.surefire:surefire-report-parser:3.5.1" testImplementation "com.opencsv:opencsv:5.9" testImplementation("io.zonky.test:embedded-database-spring-test:2.5.1") { exclude group: "org.testcontainers", module: "mariadb" diff --git a/docs/dev/guidelines/database.rst b/docs/dev/guidelines/database.rst index 65365549f10a..ea4e436b53d4 100644 --- a/docs/dev/guidelines/database.rst +++ b/docs/dev/guidelines/database.rst @@ -295,7 +295,7 @@ Best Practices // IrisSubSettings.java @Column(name = "allowed_models") @Convert(converter = IrisModelListConverter.class) - private TreeSet allowedModels = new TreeSet<>(); + private TreeSet allowedVariants = new TreeSet<>(); * **Ordered Collection with duplicates**: When you want to order the collection of (potentially duplicated) objects of the relationship, then always use a ``List``. It is important to note here that there is no inherent order in a database table. One could argue that you can use the ``id`` field for the ordering, but there are edge cases where this can lead to problems. Therefore, for an ordered collection with duplicates, **always** annotate it with ``@OrderColumn``. An order column indicates to Hibernate that we want to order our collection based on a specific column of our data table. By default, the column name it expects is *tablenameS\_order*. For ordered collections, we also recommend that you annotate them with ``cascade = CascadeType.ALL`` and ``orphanRemoval = true``. E.g.: diff --git a/docs/user/exercises/programming-exercise-features.inc b/docs/user/exercises/programming-exercise-features.inc index 7bccf1596315..660e2bd4bf02 100644 --- a/docs/user/exercises/programming-exercise-features.inc +++ b/docs/user/exercises/programming-exercise-features.inc @@ -37,6 +37,8 @@ Instructors can still use those templates to generate programming exercises and +----------------------+----------+---------+ | JavaScript | yes | yes | +----------------------+----------+---------+ + | R | yes | yes | + +----------------------+----------+---------+ - Not all ``templates`` support the same feature set and supported features can also change depending on the continuous integration system setup. Depending on the feature set, some options might not be available during the creation of the programming exercise. @@ -71,6 +73,8 @@ Instructors can still use those templates to generate programming exercises and +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+----------------------------+------------------------+ | JavaScript | no | no | yes | no | n/a | no | no | L: yes, J: no | +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+----------------------------+------------------------+ + | R | no | no | yes | no | n/a | no | no | L: yes, J: no | + +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+----------------------------+------------------------+ - *Sequential Test Runs*: ``Artemis`` can generate a build plan which first executes structural and then behavioral tests. This feature can help students to better concentrate on the immediate challenge at hand. - *Static Code Analysis*: ``Artemis`` can generate a build plan which additionally executes static code analysis tools. diff --git a/gradle.properties b/gradle.properties index 46526dddaf56..07ee79d07d25 100644 --- a/gradle.properties +++ b/gradle.properties @@ -25,17 +25,17 @@ jplag_version=5.1.0 # NOTE: we do not need to use the latest version 9.x here as long as Stanford CoreNLP does not reference it lucene_version=8.11.4 slf4j_version=2.0.16 -sentry_version=7.14.0 +sentry_version=7.15.0 liquibase_version=4.29.2 docker_java_version=3.4.0 -logback_version=1.5.8 +logback_version=1.5.10 java_parser_version=3.26.2 -byte_buddy_version=1.15.3 +byte_buddy_version=1.15.4 # testing # make sure both versions are compatible junit_version=5.11.0 -junit_platform_version=1.11.1 +junit_platform_version=1.11.2 mockito_version=5.14.1 diff --git a/jest.config.js b/jest.config.js index 3dab49d4b7e0..77fd322375d8 100644 --- a/jest.config.js +++ b/jest.config.js @@ -102,10 +102,10 @@ module.exports = { coverageThreshold: { global: { // TODO: in the future, the following values should increase to at least 90% - statements: 87.37, - branches: 73.68, - functions: 81.93, - lines: 87.42, + statements: 87.44, + branches: 73.74, + functions: 82.10, + lines: 87.49, }, }, coverageReporters: ['clover', 'json', 'lcov', 'text-summary'], diff --git a/package-lock.json b/package-lock.json index 4b7bc1437ba6..91ee03d72aaf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,18 +10,18 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { - "@angular/animations": "18.2.7", - "@angular/cdk": "18.2.7", - "@angular/common": "18.2.7", - "@angular/compiler": "18.2.7", - "@angular/core": "18.2.7", - "@angular/forms": "18.2.7", - "@angular/localize": "18.2.7", - "@angular/material": "18.2.7", - "@angular/platform-browser": "18.2.7", - "@angular/platform-browser-dynamic": "18.2.7", - "@angular/router": "18.2.7", - "@angular/service-worker": "18.2.7", + "@angular/animations": "18.2.8", + "@angular/cdk": "18.2.8", + "@angular/common": "18.2.8", + "@angular/compiler": "18.2.8", + "@angular/core": "18.2.8", + "@angular/forms": "18.2.8", + "@angular/localize": "18.2.8", + "@angular/material": "18.2.8", + "@angular/platform-browser": "18.2.8", + "@angular/platform-browser-dynamic": "18.2.8", + "@angular/router": "18.2.8", + "@angular/service-worker": "18.2.8", "@ctrl/ngx-emoji-mart": "9.2.0", "@danielmoncada/angular-datetime-picker": "18.1.0", "@fingerprintjs/fingerprintjs": "4.5.0", @@ -33,7 +33,7 @@ "@ng-bootstrap/ng-bootstrap": "17.0.1", "@ngx-translate/core": "15.0.0", "@ngx-translate/http-loader": "8.0.0", - "@sentry/angular": "8.33.1", + "@sentry/angular": "8.34.0", "@siemens/ngx-datatable": "22.4.1", "@swimlane/ngx-charts": "20.5.0", "@swimlane/ngx-graph": "8.4.0", @@ -59,8 +59,8 @@ "ngx-infinite-scroll": "18.0.0", "ngx-webstorage": "18.0.0", "papaparse": "5.4.1", - "pdfjs-dist": "4.6.82", - "posthog-js": "1.166.1", + "pdfjs-dist": "4.7.76", + "posthog-js": "1.167.0", "rxjs": "7.8.1", "showdown": "2.1.0", "showdown-highlight": "3.1.0", @@ -78,29 +78,29 @@ }, "devDependencies": { "@angular-builders/jest": "18.0.0", - "@angular-devkit/build-angular": "18.2.7", + "@angular-devkit/build-angular": "18.2.8", "@angular-eslint/builder": "18.3.1", "@angular-eslint/eslint-plugin": "18.3.1", "@angular-eslint/eslint-plugin-template": "18.3.1", "@angular-eslint/schematics": "18.3.1", "@angular-eslint/template-parser": "18.3.1", - "@angular/cli": "18.2.7", - "@angular/compiler-cli": "18.2.7", - "@angular/language-service": "18.2.7", - "@sentry/types": "8.33.1", + "@angular/cli": "18.2.8", + "@angular/compiler-cli": "18.2.8", + "@angular/language-service": "18.2.8", + "@sentry/types": "8.34.0", "@types/crypto-js": "4.2.2", "@types/d3-shape": "3.1.6", "@types/dompurify": "3.0.5", "@types/jest": "29.5.13", "@types/lodash-es": "4.17.12", - "@types/node": "22.7.4", + "@types/node": "22.7.5", "@types/papaparse": "5.3.14", "@types/showdown": "2.0.6", "@types/smoothscroll-polyfill": "0.3.4", "@types/sockjs-client": "1.5.4", "@types/uuid": "10.0.0", - "@typescript-eslint/eslint-plugin": "8.8.0", - "@typescript-eslint/parser": "8.8.0", + "@typescript-eslint/eslint-plugin": "8.8.1", + "@typescript-eslint/parser": "8.8.1", "eslint": "9.12.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-deprecation": "3.0.0", @@ -121,7 +121,7 @@ "ngxtension": "4.0.0", "prettier": "3.3.3", "rimraf": "6.0.1", - "sass": "1.79.4", + "sass": "1.79.5", "ts-jest": "29.2.5", "typescript": "5.5.4", "weak-napi": "2.0.2" @@ -212,13 +212,13 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.1802.7", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.7.tgz", - "integrity": "sha512-kpcgXnepEXcoxDTbqbGj7Hg1WJLWj1HLR3/FKmC5TbpBf1xiLxiqfkQNwz3BbE/W9JWMLdrXr3GI9O3O2gWPLg==", + "version": "0.1802.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.8.tgz", + "integrity": "sha512-/rtFQEKgS7LlB9oHr4NCBSdKnvP5kr8L5Hbd3Vl8hZOYK9QWjxKPEXnryA2d5+PCE98bBzZswCNXqELZCPTgIQ==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.7", + "@angular-devkit/core": "18.2.8", "rxjs": "7.8.1" }, "engines": { @@ -228,17 +228,17 @@ } }, "node_modules/@angular-devkit/build-angular": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.2.7.tgz", - "integrity": "sha512-u8PriYdgddK7k+OS/pOFPD1v4Iu5bztUJZXZVcGeXBZFFdnGFFzKmQw9mfcyGvTMJp2ABgBuuJT0YqYgNfAhzw==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.2.8.tgz", + "integrity": "sha512-qK/iLk7A8vQp1CyiJV4DpwfLjPKoiOlTtFqoO5vD8Tyxmc+R06FQp6GJTsZ7JtrTLYSiH+QAWiY6NgF/Rj/hHg==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1802.7", - "@angular-devkit/build-webpack": "0.1802.7", - "@angular-devkit/core": "18.2.7", - "@angular/build": "18.2.7", + "@angular-devkit/architect": "0.1802.8", + "@angular-devkit/build-webpack": "0.1802.8", + "@angular-devkit/core": "18.2.8", + "@angular/build": "18.2.8", "@babel/core": "7.25.2", "@babel/generator": "7.25.0", "@babel/helper-annotate-as-pure": "7.24.7", @@ -249,7 +249,7 @@ "@babel/preset-env": "7.25.3", "@babel/runtime": "7.25.0", "@discoveryjs/json-ext": "0.6.1", - "@ngtools/webpack": "18.2.7", + "@ngtools/webpack": "18.2.8", "@vitejs/plugin-basic-ssl": "1.1.0", "ansi-colors": "4.1.3", "autoprefixer": "10.4.20", @@ -382,13 +382,13 @@ "license": "0BSD" }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1802.7", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1802.7.tgz", - "integrity": "sha512-VrtbrhZ+dht3f0GjtfRLRGRN4XHN/W+/bA9DqckdxVS6SydsrCWNHonvEPmOs4jJmGIGXIu6tUBMcWleTao2sg==", + "version": "0.1802.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1802.8.tgz", + "integrity": "sha512-uPpopkXkO66SSdjtVr7xCyQCPs/x6KUC76xkDc4j0b8EEHifTbi/fNpbkcZ6wBmoAfjKLWXfKvtkh0TqKK5Hkw==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.1802.7", + "@angular-devkit/architect": "0.1802.8", "rxjs": "7.8.1" }, "engines": { @@ -402,9 +402,9 @@ } }, "node_modules/@angular-devkit/core": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.7.tgz", - "integrity": "sha512-1ZTi4A6tEC2bkJ/puCIdIPYhesnlCVOMSDJL/lZAd0hC6X22T4pwu0AEvue7mcP5NbXpQDiBaXOZ3MmCA8PwOA==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.8.tgz", + "integrity": "sha512-4o2T6wsmXGE/v53+F8L7kGoN2+qzt03C9rtjLVQpOljzpJVttQ8bhvfWxyYLWwcl04RWqRa+82fpIZtBkOlZJw==", "dev": true, "license": "MIT", "dependencies": { @@ -430,13 +430,13 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.7.tgz", - "integrity": "sha512-j7198lpkOXMG+Gyfln/5aDgBZV7m4pWMzHFhkO3+w3cbCNUN1TVZW0SyJcF+CYaxANzTbuumfvpsYc/fTeAGLw==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.8.tgz", + "integrity": "sha512-i/h2Oji5FhJMC7wDSnIl5XUe/qym+C1ZwScaATJwDyRLCUIynZkj5rLgdG/uK6l+H0PgvxigkF+akWpokkwW6w==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.7", + "@angular-devkit/core": "18.2.8", "jsonc-parser": "3.3.1", "magic-string": "0.30.11", "ora": "5.4.1", @@ -549,9 +549,9 @@ } }, "node_modules/@angular/animations": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-18.2.7.tgz", - "integrity": "sha512-5B7qD1K+kKOf9lgJT4VNMft3IK2BnRHjN1S6l38ywzQ/nxpmCG7f+qKAAU6CpCywhNUBeXW0hVXTMuMNPVOcQQ==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-18.2.8.tgz", + "integrity": "sha512-dMSn2hg70siv3lhP+vqhMbgc923xw6XBUvnpCPEzhZqFHvPXfh/LubmsD5RtqHmjWebXtgVcgS+zg3Gq3jB2lg==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -560,18 +560,18 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/core": "18.2.7" + "@angular/core": "18.2.8" } }, "node_modules/@angular/build": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-18.2.7.tgz", - "integrity": "sha512-oq6JsVxLP9/w9F2IjKroJwPB9CdlMblu2Xhfq/qQZRSUuM8Ppt1svr2FBTo1HrLIbosqukkVcSSdmKYDneo+cg==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-18.2.8.tgz", + "integrity": "sha512-ufuA4vHJSrL9SQW7bKV61DOoN1mm0t0ILTHaxSoCG3YF70cZJOX7+HNp3cK2uoldRMwbTOKSvCWBw54KKDRd5Q==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1802.7", + "@angular-devkit/architect": "0.1802.8", "@babel/core": "7.25.2", "@babel/helper-annotate-as-pure": "7.24.7", "@babel/helper-split-export-declaration": "7.24.7", @@ -651,9 +651,9 @@ } }, "node_modules/@angular/cdk": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.7.tgz", - "integrity": "sha512-Dfl37WBLeEUURQrDeuMcOgX2bkQJ+BGMOlr1qsFXzUWHH+qgYW2YwO1rbna/rjxyeFzc2Sy569dYRzNPqMewzg==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.8.tgz", + "integrity": "sha512-J8A2FkwTBzLleAEWz6EgW73dEoeq87GREBPjTv8+2JV09LX+V3hnbgNk6zWq5k4OXtQNg9WrWP9QyRbUyA597g==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -668,18 +668,18 @@ } }, "node_modules/@angular/cli": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.7.tgz", - "integrity": "sha512-KoWgSvhRsU05A2m6B7jw1kdpyoS+Ce5GGLW6xcnX7VF2AckW54vYd/8ZkgpzQrKfvIpVblYd4KJGizKoaLZ5jA==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.8.tgz", + "integrity": "sha512-GKXG7F7z5rxwZ8/bnW/Bp8/zsfE/BpHmIP/icLfUIOwv2kaY5OD2tfQssWXPEuqZzYq2AYz+wjVSbWjxGoja8A==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.1802.7", - "@angular-devkit/core": "18.2.7", - "@angular-devkit/schematics": "18.2.7", + "@angular-devkit/architect": "0.1802.8", + "@angular-devkit/core": "18.2.8", + "@angular-devkit/schematics": "18.2.8", "@inquirer/prompts": "5.3.8", "@listr2/prompt-adapter-inquirer": "2.0.15", - "@schematics/angular": "18.2.7", + "@schematics/angular": "18.2.8", "@yarnpkg/lockfile": "1.1.0", "ini": "4.1.3", "jsonc-parser": "3.3.1", @@ -702,9 +702,9 @@ } }, "node_modules/@angular/common": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-18.2.7.tgz", - "integrity": "sha512-5vDBmBR2JcIxHVEDunKXNU+T+OvTGiHZTSo35GFOHJxKFgX5g6+0tJBZunK04oBZGbJQUmp3pg2kMvuKKjZnkQ==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-18.2.8.tgz", + "integrity": "sha512-TYsKtE5nVaIScWSLGSO34Skc+s3hB/BujSddnfQHoNFvPT/WR0dfmdlpVCTeLj+f50htFoMhW11tW99PbK+whQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -713,14 +713,14 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/core": "18.2.7", + "@angular/core": "18.2.8", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-18.2.7.tgz", - "integrity": "sha512-XemlYyRGnu/HrICtXwTPmGtyOrI8BhbGg/HMiJ7sVx40AeEIX0uyDgnu9Gc5OjmtDqZZ8Qftg1sQAxaCVjLb1w==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-18.2.8.tgz", + "integrity": "sha512-JRedHNfK1CCPVyeGQB5w3WBYqMA6X8Q240CkvjlGfn0pVXihf9DWk3nkSQJVgYxpvpHfxdgjaYZ5IpMzlkmkhw==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -729,7 +729,7 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/core": "18.2.7" + "@angular/core": "18.2.8" }, "peerDependenciesMeta": { "@angular/core": { @@ -738,14 +738,14 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-18.2.7.tgz", - "integrity": "sha512-U7cveObj+rrXH5EC8egAhATCeAAcOceEQDTVIOWmBa0qMR4hOMjtI2XUS2QRuI1Q+fQZ2hVEOW95WVLvEMsANA==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-18.2.8.tgz", + "integrity": "sha512-OksDE4LWQUCcIvMjtZF7eiDCdIMrcMMpC1+Q0PIYi7KmnqXFGs4/Y0NdJvtn/LrQznzz5WaKM3ZDVNZTRX4wmw==", "license": "MIT", "dependencies": { "@babel/core": "7.25.2", "@jridgewell/sourcemap-codec": "^1.4.14", - "chokidar": "^3.0.0", + "chokidar": "^4.0.0", "convert-source-map": "^1.5.1", "reflect-metadata": "^0.2.0", "semver": "^7.0.0", @@ -761,14 +761,42 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/compiler": "18.2.7", + "@angular/compiler": "18.2.8", "typescript": ">=5.4 <5.6" } }, + "node_modules/@angular/compiler-cli/node_modules/chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@angular/compiler-cli/node_modules/readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "license": "MIT", + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular/core": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-18.2.7.tgz", - "integrity": "sha512-hLOxgxLiyWm9iVHBsUsJfx1hDsXWZnfJBlr+N7cev53f0CDoPfbshqq6KV+JFqXFDguzR9dKHm1ewT1jK3e6Tw==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-18.2.8.tgz", + "integrity": "sha512-NwIuX/Iby1jT6Iv1/s6S3wOFf8xfuQR3MPGvKhGgNtjXLbHG+TXceK9+QPZC0s9/Z8JR/hz+li34B79GrIKgUg==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -782,9 +810,9 @@ } }, "node_modules/@angular/forms": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-18.2.7.tgz", - "integrity": "sha512-WO3c9/OA7ekBnDBgmvi5TlHshOt5S4NREIP+/VVyuRgg28BwUWyO/Nqh19nguE1UNNRt6OMLkT6NSV2ewhcXUg==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-18.2.8.tgz", + "integrity": "sha512-JCLki7KC6D5vF6dE6yGlBmW33khIgpHs8N9SzuiJtkQqNDTIQA8cPsGV6qpLpxflxASynQOX5lDkWYdQyfm77Q==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -793,16 +821,16 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "18.2.7", - "@angular/core": "18.2.7", - "@angular/platform-browser": "18.2.7", + "@angular/common": "18.2.8", + "@angular/core": "18.2.8", + "@angular/platform-browser": "18.2.8", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/language-service": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-18.2.7.tgz", - "integrity": "sha512-gFsme3y5uC/dQGBBX05VnmT2KAEAZ6gsNk8m1b226LYvh8Oc+JQ4sXv7THGq1x5VnrTzRcCIELbkNHCiFdvL1Q==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-18.2.8.tgz", + "integrity": "sha512-IueQ57CPP0Dt0z2n8B1A6JTwTq6m/AJVObZzrkSfXlzY1rY2qRuTJmAbZpTJ3iAxVzNYoaGh+NFHmJL8fRiXKQ==", "dev": true, "license": "MIT", "engines": { @@ -810,9 +838,9 @@ } }, "node_modules/@angular/localize": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-18.2.7.tgz", - "integrity": "sha512-qYozomhO+1BlvtoMEEgKhaKz8thoztqNZEYPq9RmfkTB5uW7Q8h6rr1Sc2YAzJ6+ZA0McwabdJSX1TDxWyZx0Q==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-18.2.8.tgz", + "integrity": "sha512-1T7aXEdgVyeYnHOfQUuIDO8Lsamg1ZLrJrA5zUv61asPJp6HCcMjXy9vDQ1XvHm5+CdDjKk/rczlN4lSMZ0QRw==", "license": "MIT", "dependencies": { "@babel/core": "7.25.2", @@ -829,21 +857,21 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/compiler": "18.2.7", - "@angular/compiler-cli": "18.2.7" + "@angular/compiler": "18.2.8", + "@angular/compiler-cli": "18.2.8" } }, "node_modules/@angular/material": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-18.2.7.tgz", - "integrity": "sha512-mgPj2TCIrsngmu3iNnoaPc6su7uPv+NPCv9HaiKhTx4QGae8EW+RvUxEZJvh4Qaym1fJTi3hjnVeWvQDLQt4CA==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-18.2.8.tgz", + "integrity": "sha512-wQGMVsfQ9lQfih2VsWAvV4z3S3uBxrxc61owlE+K0T1BxH9u/jo3A/rnRitIdvR/L4NnYlfhCnmrW9K+Pl+WCg==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/animations": "^18.0.0 || ^19.0.0", - "@angular/cdk": "18.2.7", + "@angular/cdk": "18.2.8", "@angular/common": "^18.0.0 || ^19.0.0", "@angular/core": "^18.0.0 || ^19.0.0", "@angular/forms": "^18.0.0 || ^19.0.0", @@ -852,9 +880,9 @@ } }, "node_modules/@angular/platform-browser": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.7.tgz", - "integrity": "sha512-xgj2DH/isFrMZ73dJJm89NRnWBI3AHtugQrZbIapkKBdEt/C1o4SR2W2cV4mPb9o+ELnWurfrxFt9o/q2vnVLw==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.8.tgz", + "integrity": "sha512-EPai4ZPqSq3ilLJUC85kPi9wo5j5suQovwtgRyjM/75D9Qy4TV19g8hkVM5Co/zrltO8a2G6vDscCNI5BeGw2A==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -863,9 +891,9 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/animations": "18.2.7", - "@angular/common": "18.2.7", - "@angular/core": "18.2.7" + "@angular/animations": "18.2.8", + "@angular/common": "18.2.8", + "@angular/core": "18.2.8" }, "peerDependenciesMeta": { "@angular/animations": { @@ -874,9 +902,9 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-18.2.7.tgz", - "integrity": "sha512-BDldzUKjnUjo0NW5gHjBY6CeJP1bWVfF1h/T3idyYG+F4Lxlb3aykRgLWXg4srNLY1KqE7XOYUmgc5cV613bgw==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-18.2.8.tgz", + "integrity": "sha512-poZoapDqyN/rxGKQ3C6esdPiPLMkSpP2v12hoEa12KHgfPk7T1e+a+NMyJjV8HeOY3WyvL7tGRhW0NPTajTkhw==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -885,16 +913,16 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "18.2.7", - "@angular/compiler": "18.2.7", - "@angular/core": "18.2.7", - "@angular/platform-browser": "18.2.7" + "@angular/common": "18.2.8", + "@angular/compiler": "18.2.8", + "@angular/core": "18.2.8", + "@angular/platform-browser": "18.2.8" } }, "node_modules/@angular/router": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-18.2.7.tgz", - "integrity": "sha512-TXE8Aw63hDp3PEaNu4B1DMNvlS0uCzs36o/OSCCmewmLnzyJygkgi4jeEj20FsWPAQOUj5g5tnCYgxz1IRrCUg==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-18.2.8.tgz", + "integrity": "sha512-L+olYgxIiBq+tbfayVI0cv1yOuymsw33msnGC2l/vpc9sSVfqGzESFnB4yMVU3vHtE9v6v2Y6O+iV44/b79W/g==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -903,16 +931,16 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "18.2.7", - "@angular/core": "18.2.7", - "@angular/platform-browser": "18.2.7", + "@angular/common": "18.2.8", + "@angular/core": "18.2.8", + "@angular/platform-browser": "18.2.8", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/service-worker": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-18.2.7.tgz", - "integrity": "sha512-1t8PUWmZi32i/SG/r12vz+cfn0l3xVEa0FY7GXaZK7hlfDL34js1HZXHkvGUuRZRw/4L1jl7AwPoxwGeWr2ldg==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-18.2.8.tgz", + "integrity": "sha512-LQktgS2Hn845ASWNyjde18V+CHkkPeCzORfh0ChYKiOmXYFtj/myEik5o/QI/G13Kaymy+vcuwQKiUuZjZiD1w==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -924,8 +952,8 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "18.2.7", - "@angular/core": "18.2.7" + "@angular/common": "18.2.8", + "@angular/core": "18.2.8" } }, "node_modules/@babel/code-frame": { @@ -942,9 +970,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.7.tgz", - "integrity": "sha512-9ickoLz+hcXCeh7jrcin+/SLWm+GkxE2kTvoYyp38p4WkdFXfQJxDFGWp/YHjiKLPx06z2A7W8XKuqbReXDzsw==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.8.tgz", + "integrity": "sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1353,12 +1381,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.7.tgz", - "integrity": "sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.8.tgz", + "integrity": "sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==", "license": "MIT", "dependencies": { - "@babel/types": "^7.25.7" + "@babel/types": "^7.25.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -1864,15 +1892,14 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.25.7.tgz", - "integrity": "sha512-rvUUtoVlkDWtDWxGAiiQj0aNktTPn3eFynBcMC2IhsXweehwgdI9ODe+XjWw515kEmv22sSOTp/rxIRuTiB7zg==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.25.8.tgz", + "integrity": "sha512-e82gl3TCorath6YLf9xUwFehVvjvfqFhdOo4+0iVIVju+6XOi5XHkqB3P2AXnSwoeTX0HBoXq5gJFtvotJzFnQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/plugin-syntax-class-static-block": "^7.14.5" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1999,14 +2026,13 @@ } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.7.tgz", - "integrity": "sha512-UvcLuual4h7/GfylKm2IAA3aph9rwvAM2XBA0uPKU3lca+Maai4jBjjEVUS568ld6kJcgbouuumCBhMd/Yz17w==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.8.tgz", + "integrity": "sha512-gznWY+mr4ZQL/EWPcbBQUP3BXS5FwZp8RUOw06BaRn8tQLzN4XLIxXejpHN9Qo8x8jjBmAAKp6FoS51AgkSA/A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2033,14 +2059,13 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.7.tgz", - "integrity": "sha512-h3MDAP5l34NQkkNulsTNyjdaR+OiB0Im67VU//sFupouP8Q6m9Spy7l66DcaAQxtmCqGdanPByLsnwFttxKISQ==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.8.tgz", + "integrity": "sha512-sPtYrduWINTQTW7FtOy99VCTWp4H23UX7vYcut7S4CIMEXU+54zKX9uCoGkLsWXteyaMXzVHgzWbLfQ1w4GZgw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2085,14 +2110,13 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.7.tgz", - "integrity": "sha512-Ot43PrL9TEAiCe8C/2erAjXMeVSnE/BLEx6eyrKLNFCCw5jvhTHKyHxdI1pA0kz5njZRYAnMO2KObGqOCRDYSA==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.8.tgz", + "integrity": "sha512-4OMNv7eHTmJ2YXs3tvxAfa/I43di+VcF+M4Wt66c88EAED1RoGaf1D64cL5FkRpNL+Vx9Hds84lksWvd/wMIdA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/plugin-syntax-json-strings": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2118,14 +2142,13 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.7.tgz", - "integrity": "sha512-iImzbA55BjiovLyG2bggWS+V+OLkaBorNvc/yJoeeDQGztknRnDdYfp2d/UPmunZYEnZi6Lg8QcTmNMHOB0lGA==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.8.tgz", + "integrity": "sha512-f5W0AhSbbI+yY6VakT04jmxdxz+WsID0neG7+kQZbCOjuyJNdL5Nn4WIBm4hRpKnUcO9lP0eipUhFN12JpoH8g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2255,14 +2278,13 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.7.tgz", - "integrity": "sha512-FbuJ63/4LEL32mIxrxwYaqjJxpbzxPVQj5a+Ebrc8JICV6YX8nE53jY+K0RZT3um56GoNWgkS2BQ/uLGTjtwfw==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.8.tgz", + "integrity": "sha512-Z7WJJWdQc8yCWgAmjI3hyC+5PXIubH9yRKzkl9ZEG647O9szl9zvmKLzpbItlijBnVhTUf1cpyWBsZ3+2wjWPQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2272,14 +2294,13 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.7.tgz", - "integrity": "sha512-8CbutzSSh4hmD+jJHIA8vdTNk15kAzOnFLVVgBSMGr28rt85ouT01/rezMecks9pkU939wDInImwCKv4ahU4IA==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.8.tgz", + "integrity": "sha512-rm9a5iEFPS4iMIy+/A/PiS0QN0UyjPIeVvbU5EMZFKJZHt8vQnasbpo3T3EFcxzCeYO0BHfc4RqooCZc51J86Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2289,15 +2310,14 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.7.tgz", - "integrity": "sha512-1JdVKPhD7Y5PvgfFy0Mv2brdrolzpzSoUq2pr6xsR+m+3viGGeHEokFKsCgOkbeFOQxfB1Vt2F0cPJLRpFI4Zg==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.8.tgz", + "integrity": "sha512-LkUu0O2hnUKHKE7/zYOIjByMa4VRaV2CD/cdGz0AxU9we+VA3kDDggKEzI0Oz1IroG+6gUP6UmWEHBMWZU316g==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.25.7", "@babel/helper-plugin-utils": "^7.25.7", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-transform-parameters": "^7.25.7" }, "engines": { @@ -2325,14 +2345,13 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.7.tgz", - "integrity": "sha512-m9obYBA39mDPN7lJzD5WkGGb0GO54PPLXsbcnj1Hyeu8mSRz7Gb4b1A6zxNX32ZuUySDK4G6it8SDFWD1nCnqg==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.8.tgz", + "integrity": "sha512-EbQYweoMAHOn7iJ9GgZo14ghhb9tTjgOc88xFgYngifx7Z9u580cENCV159M4xDh3q/irbhSjZVpuhpC2gKBbg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2342,15 +2361,14 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.7.tgz", - "integrity": "sha512-h39agClImgPWg4H8mYVAbD1qP9vClFbEjqoJmt87Zen8pjqK8FTPUwrOXAvqu5soytwxrLMd2fx2KSCp2CHcNg==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.8.tgz", + "integrity": "sha512-q05Bk7gXOxpTHoQ8RSzGSh/LHVB9JEIkKnk3myAWwZHnYiTGYtbdrYkIsS8Xyh4ltKf7GNUSgzs/6P2bJtBAQg==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2393,16 +2411,15 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.7.tgz", - "integrity": "sha512-LzA5ESzBy7tqj00Yjey9yWfs3FKy4EmJyKOSWld144OxkTji81WWnUT8nkLUn+imN/zHL8ZQlOu/MTUAhHaX3g==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.8.tgz", + "integrity": "sha512-8Uh966svuB4V8RHHg0QJOB32QK287NBksJOByoKmHMp1TAobNniNalIkI2i5IPj5+S9NYCG4VIjbEuiSN8r+ow==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.7", "@babel/helper-create-class-features-plugin": "^7.25.7", - "@babel/helper-plugin-utils": "^7.25.7", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2827,9 +2844,9 @@ } }, "node_modules/@babel/types": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.7.tgz", - "integrity": "sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.8.tgz", + "integrity": "sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.25.7", @@ -2908,9 +2925,9 @@ } }, "node_modules/@emnapi/core": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.2.0.tgz", - "integrity": "sha512-E7Vgw78I93we4ZWdYCb4DGAwRROGkMIXk7/y87UmANR+J6qsWusmC3gLt0H+O0KOt5e6O38U8oJamgbudrES/w==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.3.1.tgz", + "integrity": "sha512-pVGjBIt1Y6gg3EJN8jTcfpP/+uuRksIo055oE/OBkDNcjZqVbfkWCksG1Jp4yZnj3iKWyWX8fdG/j6UDYPbFog==", "dev": true, "license": "MIT", "dependencies": { @@ -2919,9 +2936,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz", - "integrity": "sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", "dev": true, "license": "MIT", "dependencies": { @@ -3793,9 +3810,9 @@ } }, "node_modules/@inquirer/figures": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.6.tgz", - "integrity": "sha512-yfZzps3Cso2UbM7WlxKwZQh2Hs6plrbjs1QnzQDZhK2DgyCo6D8AaHps9olkNcUFlcYERMqU3uJSp1gmy3s/qQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.7.tgz", + "integrity": "sha512-m+Trk77mp54Zma6xLkLuY+mvanPxlE4A7yNKs2HBiyZ4UkVs28Mv5c/pgWrHeInx+USHeX/WEPzjrWrcJiQgjw==", "dev": true, "license": "MIT", "engines": { @@ -4990,9 +5007,9 @@ } }, "node_modules/@jsonjoy.com/util": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.3.0.tgz", - "integrity": "sha512-Cebt4Vk7k1xHy87kHY7KSPLT77A7Ev7IfOblyLZhtYEhrdQ6fX4EoLq3xOQ3O/DRMEh2ok5nyC180E+ABS8Wmw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.5.0.tgz", + "integrity": "sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -5359,9 +5376,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.7.tgz", - "integrity": "sha512-BmnFxss6zGobGyq9Mi7736golbK8RLgF+zYCQZ+4/OfMMA1jKVoELnyJqNyAx+DQn3m1qKVBjtGEL7pTNpPzOw==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.8.tgz", + "integrity": "sha512-sq0kI8gEen4QlM6X8XqOYy7j4B8iLCYNo+iKxatV36ts4AXH0MuVkP56+oMaoH5oZNoSqd0RlfnotEHfvJAr8A==", "dev": true, "license": "MIT", "engines": { @@ -5691,23 +5708,23 @@ } }, "node_modules/@nrwl/devkit": { - "version": "19.8.3", - "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-19.8.3.tgz", - "integrity": "sha512-67vZJRMCEA543A0uz8dPTZ5lX4wsAlgsr24KJafsUxBC2WCf9z4BqcLj0jVWfmRdKJmu2UwaxtD2UB1bekt3sg==", + "version": "19.8.4", + "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-19.8.4.tgz", + "integrity": "sha512-OoIqDjj2mWzLs3aSF6w5OiC2xywYi/jBxHc7t7Lyi56Vc4dQq8vJMELa9WtG6qH0k05fF7N+jAoKlfvLgbbEFA==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "19.8.3" + "@nx/devkit": "19.8.4" } }, "node_modules/@nrwl/tao": { - "version": "19.8.3", - "resolved": "https://registry.npmjs.org/@nrwl/tao/-/tao-19.8.3.tgz", - "integrity": "sha512-byjBtOXx+xGjMu1wKopJSJbrR3gKqTsCEgp1+YSZ45+iFKxFdXLJrGsyhVqBovCKVBM+5/KtGuEkZoUPlP8JWg==", + "version": "19.8.4", + "resolved": "https://registry.npmjs.org/@nrwl/tao/-/tao-19.8.4.tgz", + "integrity": "sha512-03/+QZ4/6HmKbEmvzCutLI1XIclBspNYtiVHmGPRWuwhnZViqYfnyl8J7RWVdFEoKKA5fhJqpg7e28aGuoMBvQ==", "dev": true, "license": "MIT", "dependencies": { - "nx": "19.8.3", + "nx": "19.8.4", "tslib": "^2.3.0" }, "bin": { @@ -5715,13 +5732,13 @@ } }, "node_modules/@nx/devkit": { - "version": "19.8.3", - "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-19.8.3.tgz", - "integrity": "sha512-uX50CAM11tzhwswf0ftN0QfzW2FM3M4Mf/pD/nRRnmsTkcPTdMXVu4LHuLVTp4CMsaO+cOQlqgHXujHYfOIctg==", + "version": "19.8.4", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-19.8.4.tgz", + "integrity": "sha512-FPFT8gVDFRSEmU0n7nRkT4Rnqy7OMznfPXLfDZtVuzEi5Cl6ftG3UBUvCgJcJFCYJVAZAUuv6vRSRarHd51XFQ==", "dev": true, "license": "MIT", "dependencies": { - "@nrwl/devkit": "19.8.3", + "@nrwl/devkit": "19.8.4", "ejs": "^3.1.7", "enquirer": "~2.3.6", "ignore": "^5.0.4", @@ -5762,9 +5779,9 @@ } }, "node_modules/@nx/nx-darwin-arm64": { - "version": "19.8.3", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-19.8.3.tgz", - "integrity": "sha512-ORHFFWMZcvFi0xcpCaXccXVEhFwAevSHOIKfW359+12H9w7VW2O42B+2NcVMK1mrDTOjlXTd+0AmAu7P4NzWFA==", + "version": "19.8.4", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-19.8.4.tgz", + "integrity": "sha512-mbSGt63hYcVCSQ54kpHl0lFqr5CsbkGJ4L3liWE30Da7vXZJwUBr9f+b9DnQ64IZzlu6vAhNcaiYQXa9lAk0yQ==", "cpu": [ "arm64" ], @@ -5779,9 +5796,9 @@ } }, "node_modules/@nx/nx-darwin-x64": { - "version": "19.8.3", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-19.8.3.tgz", - "integrity": "sha512-Ji9DPA0tuzygMcypD/FHRDQSPipcRqMNmSaNKxVpcCbozVTWHvqXFk0rloDIUnxnE0+zvE9LN71H2sS4ZHdTQA==", + "version": "19.8.4", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-19.8.4.tgz", + "integrity": "sha512-lTcXUCXNvqHdLmrNCOyDF+u6pDx209Ew7nSR47sQPvkycIHYi0gvgk0yndFn1Swah0lP4OxWg7rzAfmOlZd6ew==", "cpu": [ "x64" ], @@ -5796,9 +5813,9 @@ } }, "node_modules/@nx/nx-freebsd-x64": { - "version": "19.8.3", - "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-19.8.3.tgz", - "integrity": "sha512-Ys+PqtBZCS+QBNs7he3fnxVhMWz/lSSaBVUlVHoQcV1Y4clEpP2TWNQSsbaVnnpcB7pdmKN5ymWdaCaAQuqCMw==", + "version": "19.8.4", + "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-19.8.4.tgz", + "integrity": "sha512-4BUplOxPZeUwlUNfzHHMmebNVgDFW/jNX6TWRS+jINwOHnpWLkLFAXu27G80/S3OaniVCzEQklXO9b+1UsdgXw==", "cpu": [ "x64" ], @@ -5813,9 +5830,9 @@ } }, "node_modules/@nx/nx-linux-arm-gnueabihf": { - "version": "19.8.3", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-19.8.3.tgz", - "integrity": "sha512-hGOlML60ELXkgkqLHB/w/sXbTbXFhOQGSXC72CjaP5G0u1gj8eTQKJ7WEsqPAFMk5SLFFxqM7eid0LmAYYuZWQ==", + "version": "19.8.4", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-19.8.4.tgz", + "integrity": "sha512-Wahul8oz9huEm/Jv3wud5IGWdZxkGG4tdJm9i5TV5wxfUMAWbKU9v2nzZZins452UYESWvwvDkiuBPZqSto3qw==", "cpu": [ "arm" ], @@ -5830,9 +5847,9 @@ } }, "node_modules/@nx/nx-linux-arm64-gnu": { - "version": "19.8.3", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-19.8.3.tgz", - "integrity": "sha512-K/5iVbLbhsx28YtZHvveJgF41rbr2kMdabooZeFqy6VReN7U/zGJMjpV1FzDlf3TNr9jyjPDZgVQRS+qXau2qA==", + "version": "19.8.4", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-19.8.4.tgz", + "integrity": "sha512-L0RVCZkNAtZDplLT7uJV7M9cXxq2Fxw+8ex3eb9XSp7eyLeFO21T0R6vTouJ42E/PEvGApCAcyGqtnyPNMZFfw==", "cpu": [ "arm64" ], @@ -5847,9 +5864,9 @@ } }, "node_modules/@nx/nx-linux-arm64-musl": { - "version": "19.8.3", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-19.8.3.tgz", - "integrity": "sha512-zqzWjFniZDXiI/3MYxbJ0yIenUKr56apLy70oABTBHx++dsUA3/DxLMNypMA82a8KQtsbePWUi3Pgtr+JIMNXw==", + "version": "19.8.4", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-19.8.4.tgz", + "integrity": "sha512-0q8r8I8WCsY3xowDI2j109SCUSkFns/BJ40aCfRh9hhrtaIIc5qXUw2YFTjxUZNcRJXx9j9+hTe9jBkUSIGvCw==", "cpu": [ "arm64" ], @@ -5864,9 +5881,9 @@ } }, "node_modules/@nx/nx-linux-x64-gnu": { - "version": "19.8.3", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-19.8.3.tgz", - "integrity": "sha512-W1RRCqsQvpur4BxP5g5cQwjZB6jhxYLSSXi3QQDaU5ITkaV5Pdj/L7D/G6YgRB8lzKZrXc57aLJ5UKY/Z+di7w==", + "version": "19.8.4", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-19.8.4.tgz", + "integrity": "sha512-XcRBNe0ws7KB0PMcUlpQqzzjjxMP8VdqirBz7CfB2XQ8xKmP3370p0cDvqs/4oKDHK4PCkmvVFX60tzakutylA==", "cpu": [ "x64" ], @@ -5881,9 +5898,9 @@ } }, "node_modules/@nx/nx-linux-x64-musl": { - "version": "19.8.3", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-19.8.3.tgz", - "integrity": "sha512-waTo0zBBGnmU7fS87IpOnVGx7EHa0umzSMlGG0LUoU6swOeNODezsBn1Vbvaw1o7sStWBzdEBlxLxHOQXRAidg==", + "version": "19.8.4", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-19.8.4.tgz", + "integrity": "sha512-JB4tAuZBCF0yqSnKF3pHXa0b7LA3ebi3Bw08QmMr//ON4aU+eXURGBuj9XvULD2prY+gpBrvf+MsG1XJAHL6Zg==", "cpu": [ "x64" ], @@ -5898,9 +5915,9 @@ } }, "node_modules/@nx/nx-win32-arm64-msvc": { - "version": "19.8.3", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-19.8.3.tgz", - "integrity": "sha512-lio7ulblEMs1otMtVIrdfdMTBqKRZEHim57AcMHSVnwmtl2ENP6TR3YIgyigjfLlkPanNU7i0QQ4h6Nk2I/FRw==", + "version": "19.8.4", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-19.8.4.tgz", + "integrity": "sha512-WvQag/pN9ofRWRDvOZxj3jvJoTetlvV1uyirnDrhupRgi+Fj67OlGGt2zVUHaXFGEa1MfCEG6Vhk6152m4KyaQ==", "cpu": [ "arm64" ], @@ -5915,9 +5932,9 @@ } }, "node_modules/@nx/nx-win32-x64-msvc": { - "version": "19.8.3", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-19.8.3.tgz", - "integrity": "sha512-RU11iXJzdrw5CmogT2AwsjxK7g8vWf6Oy23NlrvsQFODtavjqAWoD5qpUY/H16s9lVDwrpzCbGbAXph0lbgLKA==", + "version": "19.8.4", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-19.8.4.tgz", + "integrity": "sha512-//JntLrN3L7WL/WgP3D0FE34caYTPcG/GIMBguC9w7YDyTlEikLgLbobjdCPz+2f9OWGvIZbJgGmtHNjnETM/g==", "cpu": [ "x64" ], @@ -5931,6 +5948,312 @@ "node": ">= 10" } }, + "node_modules/@parcel/watcher": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.4.1.tgz", + "integrity": "sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.4.1", + "@parcel/watcher-darwin-arm64": "2.4.1", + "@parcel/watcher-darwin-x64": "2.4.1", + "@parcel/watcher-freebsd-x64": "2.4.1", + "@parcel/watcher-linux-arm-glibc": "2.4.1", + "@parcel/watcher-linux-arm64-glibc": "2.4.1", + "@parcel/watcher-linux-arm64-musl": "2.4.1", + "@parcel/watcher-linux-x64-glibc": "2.4.1", + "@parcel/watcher-linux-x64-musl": "2.4.1", + "@parcel/watcher-win32-arm64": "2.4.1", + "@parcel/watcher-win32-ia32": "2.4.1", + "@parcel/watcher-win32-x64": "2.4.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.4.1.tgz", + "integrity": "sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.4.1.tgz", + "integrity": "sha512-ln41eihm5YXIY043vBrrHfn94SIBlqOWmoROhsMVTSXGh0QahKGy77tfEywQ7v3NywyxBBkGIfrWRHm0hsKtzA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.4.1.tgz", + "integrity": "sha512-yrw81BRLjjtHyDu7J61oPuSoeYWR3lDElcPGJyOvIXmor6DEo7/G2u1o7I38cwlcoBHQFULqF6nesIX3tsEXMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.4.1.tgz", + "integrity": "sha512-TJa3Pex/gX3CWIx/Co8k+ykNdDCLx+TuZj3f3h7eOjgpdKM+Mnix37RYsYU4LHhiYJz3DK5nFCCra81p6g050w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.4.1.tgz", + "integrity": "sha512-4rVYDlsMEYfa537BRXxJ5UF4ddNwnr2/1O4MHM5PjI9cvV2qymvhwZSFgXqbS8YoTk5i/JR0L0JDs69BUn45YA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.4.1.tgz", + "integrity": "sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.4.1.tgz", + "integrity": "sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.4.1.tgz", + "integrity": "sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.4.1.tgz", + "integrity": "sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.4.1.tgz", + "integrity": "sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.4.1.tgz", + "integrity": "sha512-maNRit5QQV2kgHFSYwftmPBxiuK5u4DXjbXx7q6eKjq5dsLXZ4FJiVvlcw35QXzk0KrUecJmuVFbj4uV9oYrcw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.4.1.tgz", + "integrity": "sha512-+DvS92F9ezicfswqrvIRM2njcYJbd5mb9CUgtrHCHmvn7pPPa+nMDRu1o1bYYz/l5IB2NVGNJWiH7h1E58IF2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/@parcel/watcher/node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -6247,14 +6570,14 @@ ] }, "node_modules/@schematics/angular": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.2.7.tgz", - "integrity": "sha512-WOBzO11qstznHbC9tZXQf6/8+PqmaRI6QYcdTspqXNh9q9nNglvi43Xn4tSIpEhW8aSHea9hgWZV8sG+i/4W9Q==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.2.8.tgz", + "integrity": "sha512-62Sr7/j/dlhZorxH4GzQgpJy0s162BVts0Q7knZuEacP4VL+IWOUE1NS9OFkh/cbomoyXBdoewkZ5Zd1dVX78w==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.7", - "@angular-devkit/schematics": "18.2.7", + "@angular-devkit/core": "18.2.8", + "@angular-devkit/schematics": "18.2.8", "jsonc-parser": "3.3.1" }, "engines": { @@ -6264,73 +6587,73 @@ } }, "node_modules/@sentry-internal/browser-utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.33.1.tgz", - "integrity": "sha512-TW6/r+Gl5jiXv54iK1xZ3mlVgTS/jaBp4vcQ0xGMdgiQ3WchEPcFSeYovL+YHT3tSud0GZqVtDQCz+5i76puqA==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.34.0.tgz", + "integrity": "sha512-4AcYOzPzD1tL5eSRQ/GpKv5enquZf4dMVUez99/Bh3va8qiJrNP55AcM7UzZ7WZLTqKygIYruJTU5Zu2SpEAPQ==", "license": "MIT", "dependencies": { - "@sentry/core": "8.33.1", - "@sentry/types": "8.33.1", - "@sentry/utils": "8.33.1" + "@sentry/core": "8.34.0", + "@sentry/types": "8.34.0", + "@sentry/utils": "8.34.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/feedback": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.33.1.tgz", - "integrity": "sha512-qauMRTm3qDaLqZ3ibI03cj4gLF40y0ij65nj+cns6iWxGCtPrO8tjvXFWuQsE7Aye9dGMnBgmv7uN+NTUtC3RA==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.34.0.tgz", + "integrity": "sha512-aYSM2KPUs0FLPxxbJCFSwCYG70VMzlT04xepD1Y/tTlPPOja/02tSv2tyOdZbv8Uw7xslZs3/8Lhj74oYcTBxw==", "license": "MIT", "dependencies": { - "@sentry/core": "8.33.1", - "@sentry/types": "8.33.1", - "@sentry/utils": "8.33.1" + "@sentry/core": "8.34.0", + "@sentry/types": "8.34.0", + "@sentry/utils": "8.34.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/replay": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.33.1.tgz", - "integrity": "sha512-fm4coIOjmanU29NOVN9MyaP4fUCOYytbtFqVSKRFNZQ/xAgNeySiBIbUd6IjujMmnOk9bY0WEUMcdm3Uotjdog==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.34.0.tgz", + "integrity": "sha512-EoMh9NYljNewZK1quY23YILgtNdGgrkzJ9TPsj6jXUG0LZ0Q7N7eFWd0xOEDBvFxrmI3cSXF1i4d1sBb+eyKRw==", "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "8.33.1", - "@sentry/core": "8.33.1", - "@sentry/types": "8.33.1", - "@sentry/utils": "8.33.1" + "@sentry-internal/browser-utils": "8.34.0", + "@sentry/core": "8.34.0", + "@sentry/types": "8.34.0", + "@sentry/utils": "8.34.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/replay-canvas": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.33.1.tgz", - "integrity": "sha512-nsxTFTPCT10Ty/v6+AiST3+yotGP1sUb8xqfKB9fPnS1hZHFryp0NnEls7xFjBsBbZPU1GpFkzrk/E6JFzixDQ==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.34.0.tgz", + "integrity": "sha512-x8KhZcCDpbKHqFOykYXiamX6x0LRxv6N1OJHoH+XCrMtiDBZr4Yo30d/MaS6rjmKGMtSRij30v+Uq+YWIgxUrg==", "license": "MIT", "dependencies": { - "@sentry-internal/replay": "8.33.1", - "@sentry/core": "8.33.1", - "@sentry/types": "8.33.1", - "@sentry/utils": "8.33.1" + "@sentry-internal/replay": "8.34.0", + "@sentry/core": "8.34.0", + "@sentry/types": "8.34.0", + "@sentry/utils": "8.34.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry/angular": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@sentry/angular/-/angular-8.33.1.tgz", - "integrity": "sha512-jt4oViLMl/eqOALQmD0dPzXsy75Xp8amfRExgXoPdyDg6sLDNdEzpzrX2p7nGl7vsW/0Vm8NZ2TkbEBCll5wfQ==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@sentry/angular/-/angular-8.34.0.tgz", + "integrity": "sha512-FjBN5s+SFzTFHQh5DqWUGUp19p3V7p86I7Dq1a7MBCzmQukGM1bcW8+n6wLj6CxlEoyLCPPZpTIXIO4ulheIwg==", "license": "MIT", "dependencies": { - "@sentry/browser": "8.33.1", - "@sentry/core": "8.33.1", - "@sentry/types": "8.33.1", - "@sentry/utils": "8.33.1", + "@sentry/browser": "8.34.0", + "@sentry/core": "8.34.0", + "@sentry/types": "8.34.0", + "@sentry/utils": "8.34.0", "tslib": "^2.4.1" }, "engines": { @@ -6344,52 +6667,52 @@ } }, "node_modules/@sentry/browser": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.33.1.tgz", - "integrity": "sha512-c6zI/igexkLwZuGk+u8Rj26ChjxGgkhe6ZbKFsXCYaKAp5ep5X7HQRkkqgbxApiqlC0LduHdd/ymzh139JLg8w==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.34.0.tgz", + "integrity": "sha512-3HHG2NXxzHq1lVmDy2uRjYjGNf9NsJsTPlOC70vbQdOb+S49EdH/XMPy+J3ruIoyv6Cu0LwvA6bMOM6rHZOgNQ==", "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "8.33.1", - "@sentry-internal/feedback": "8.33.1", - "@sentry-internal/replay": "8.33.1", - "@sentry-internal/replay-canvas": "8.33.1", - "@sentry/core": "8.33.1", - "@sentry/types": "8.33.1", - "@sentry/utils": "8.33.1" + "@sentry-internal/browser-utils": "8.34.0", + "@sentry-internal/feedback": "8.34.0", + "@sentry-internal/replay": "8.34.0", + "@sentry-internal/replay-canvas": "8.34.0", + "@sentry/core": "8.34.0", + "@sentry/types": "8.34.0", + "@sentry/utils": "8.34.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry/core": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.33.1.tgz", - "integrity": "sha512-3SS41suXLFzxL3OQvTMZ6q92ZapELVq2l2SoWlZopcamWhog2Ru0dp2vkunq97kFHb2TzKRTlFH4+4gbT8SJug==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.34.0.tgz", + "integrity": "sha512-adrXCTK/zsg5pJ67lgtZqdqHvyx6etMjQW3P82NgWdj83c8fb+zH+K79Z47pD4zQjX0ou2Ws5nwwi4wJbz4bfA==", "license": "MIT", "dependencies": { - "@sentry/types": "8.33.1", - "@sentry/utils": "8.33.1" + "@sentry/types": "8.34.0", + "@sentry/utils": "8.34.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry/types": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-8.33.1.tgz", - "integrity": "sha512-GjoAMvwtpIemoF/IiwZ7A60g4nQv3qwzR21GvJqDVUoKD0e8pv9OLX+HyXoUat4wEDGSuDUcUyUKD2G+od73QA==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-8.34.0.tgz", + "integrity": "sha512-zLRc60CzohGCo6zNsNeQ9JF3SiEeRE4aDCP9fDDdIVCOKovS+mn1rtSip0qd0Vp2fidOu0+2yY0ALCz1A3PJSQ==", "license": "MIT", "engines": { "node": ">=14.18" } }, "node_modules/@sentry/utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-8.33.1.tgz", - "integrity": "sha512-uzuYpiiJuFY3N4WNHMBWUQX5oNv2t/TbG0OHRp3Rr7yeu+HSfD542TIp9/gMZ+G0Cxd8AmVO3wkKIFbk0TL4Qg==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-8.34.0.tgz", + "integrity": "sha512-W1KoRlFUjprlh3t86DZPFxLfM6mzjRzshVfMY7vRlJFymBelJsnJ3A1lPeBZM9nCraOSiw6GtOWu6k5BAkiGIg==", "license": "MIT", "dependencies": { - "@sentry/types": "8.33.1" + "@sentry/types": "8.34.0" }, "engines": { "node": ">=14.18" @@ -7037,9 +7360,9 @@ } }, "node_modules/@types/node": { - "version": "22.7.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz", - "integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==", + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7243,17 +7566,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.0.tgz", - "integrity": "sha512-wORFWjU30B2WJ/aXBfOm1LX9v9nyt9D3jsSOxC3cCaTQGCW5k4jNpmjFv3U7p/7s4yvdjHzwtv2Sd2dOyhjS0A==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.1.tgz", + "integrity": "sha512-xfvdgA8AP/vxHgtgU310+WBnLB4uJQ9XdyP17RebG26rLtDrQJV3ZYrcopX91GrHmMoH8bdSwMRh2a//TiJ1jQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.8.0", - "@typescript-eslint/type-utils": "8.8.0", - "@typescript-eslint/utils": "8.8.0", - "@typescript-eslint/visitor-keys": "8.8.0", + "@typescript-eslint/scope-manager": "8.8.1", + "@typescript-eslint/type-utils": "8.8.1", + "@typescript-eslint/utils": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -7277,16 +7600,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.0.tgz", - "integrity": "sha512-uEFUsgR+tl8GmzmLjRqz+VrDv4eoaMqMXW7ruXfgThaAShO9JTciKpEsB+TvnfFfbg5IpujgMXVV36gOJRLtZg==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.1.tgz", + "integrity": "sha512-hQUVn2Lij2NAxVFEdvIGxT9gP1tq2yM83m+by3whWFsWC+1y8pxxxHUFE1UqDu2VsGi2i6RLcv4QvouM84U+ow==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "8.8.0", - "@typescript-eslint/types": "8.8.0", - "@typescript-eslint/typescript-estree": "8.8.0", - "@typescript-eslint/visitor-keys": "8.8.0", + "@typescript-eslint/scope-manager": "8.8.1", + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/typescript-estree": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1", "debug": "^4.3.4" }, "engines": { @@ -7306,14 +7629,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.0.tgz", - "integrity": "sha512-EL8eaGC6gx3jDd8GwEFEV091210U97J0jeEHrAYvIYosmEGet4wJ+g0SYmLu+oRiAwbSA5AVrt6DxLHfdd+bUg==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.1.tgz", + "integrity": "sha512-X4JdU+66Mazev/J0gfXlcC/dV6JI37h+93W9BRYXrSn0hrE64IoWgVkO9MSJgEzoWkxONgaQpICWg8vAN74wlA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.8.0", - "@typescript-eslint/visitor-keys": "8.8.0" + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7324,14 +7647,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.8.0.tgz", - "integrity": "sha512-IKwJSS7bCqyCeG4NVGxnOP6lLT9Okc3Zj8hLO96bpMkJab+10HIfJbMouLrlpyOr3yrQ1cA413YPFiGd1mW9/Q==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.8.1.tgz", + "integrity": "sha512-qSVnpcbLP8CALORf0za+vjLYj1Wp8HSoiI8zYU5tHxRVj30702Z1Yw4cLwfNKhTPWp5+P+k1pjmD5Zd1nhxiZA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.8.0", - "@typescript-eslint/utils": "8.8.0", + "@typescript-eslint/typescript-estree": "8.8.1", + "@typescript-eslint/utils": "8.8.1", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -7349,9 +7672,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.0.tgz", - "integrity": "sha512-QJwc50hRCgBd/k12sTykOJbESe1RrzmX6COk8Y525C9l7oweZ+1lw9JiU56im7Amm8swlz00DRIlxMYLizr2Vw==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.1.tgz", + "integrity": "sha512-WCcTP4SDXzMd23N27u66zTKMuEevH4uzU8C9jf0RO4E04yVHgQgW+r+TeVTNnO1KIfrL8ebgVVYYMMO3+jC55Q==", "dev": true, "license": "MIT", "engines": { @@ -7363,14 +7686,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.0.tgz", - "integrity": "sha512-ZaMJwc/0ckLz5DaAZ+pNLmHv8AMVGtfWxZe/x2JVEkD5LnmhWiQMMcYT7IY7gkdJuzJ9P14fRy28lUrlDSWYdw==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.1.tgz", + "integrity": "sha512-A5d1R9p+X+1js4JogdNilDuuq+EHZdsH9MjTVxXOdVFfTJXunKJR/v+fNNyO4TnoOn5HqobzfRlc70NC6HTcdg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.8.0", - "@typescript-eslint/visitor-keys": "8.8.0", + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -7392,16 +7715,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.0.tgz", - "integrity": "sha512-QE2MgfOTem00qrlPgyByaCHay9yb1+9BjnMFnSFkUKQfu7adBXDTnCAivURnuPPAG/qiB+kzKkZKmKfaMT0zVg==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.1.tgz", + "integrity": "sha512-/QkNJDbV0bdL7H7d0/y0qBbV2HTtf0TIyjSDTvvmQEzeVx8jEImEbLuOA4EsvE8gIgqMitns0ifb5uQhMj8d9w==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.8.0", - "@typescript-eslint/types": "8.8.0", - "@typescript-eslint/typescript-estree": "8.8.0" + "@typescript-eslint/scope-manager": "8.8.1", + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/typescript-estree": "8.8.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7415,13 +7738,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.0.tgz", - "integrity": "sha512-8mq51Lx6Hpmd7HnA2fcHQo3YgfX1qbccxQOgZcb4tvasu//zXRaA1j5ZRFeCw/VRAdFi4mRM9DnZw0Nu0Q2d1g==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.1.tgz", + "integrity": "sha512-0/TdC3aeRAsW7MDvYRwEc1Uwm0TIBfzjPFgg60UU2Haj5qsCs9cc3zNgY71edqE3LbWfF/WoZQd3lJoDXFQpag==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/types": "8.8.1", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -7929,6 +8252,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -7942,6 +8266,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -8441,6 +8766,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8785,9 +9111,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001666", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001666.tgz", - "integrity": "sha512-gD14ICmoV5ZZM1OdzPWmpx+q4GyefaK06zi8hmfHV5xe4/2nOQX3+Dw5o+fSqOws2xVwL9j+anOPFwHzdEdV4g==", + "version": "1.0.30001668", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001668.tgz", + "integrity": "sha512-nWLrdxqCdblixUO+27JtGJJE/txpJlyUy5YN1u53wLZkP0emYCo5zgS6QYft7VUYR42LGgi/S5hdLZTrnyIddw==", "funding": [ { "type": "opencollective", @@ -8855,6 +9181,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -8875,6 +9202,19 @@ "fsevents": "~2.3.2" } }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", @@ -9114,9 +9454,9 @@ } }, "node_modules/code-block-writer": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.2.tgz", - "integrity": "sha512-XfXzAGiStXSmCIwrkdfvc7FS5Dtj8yelCtyOf2p2skCAfvLd6zu0rGzuS9NSCO3bq1JKpFZ7tbKdKlcd5occQA==", + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", "dev": true, "license": "MIT" }, @@ -9366,19 +9706,6 @@ "webpack": "^5.1.0" } }, - "node_modules/copy-webpack-plugin/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/core-js": { "version": "3.38.1", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.38.1.tgz", @@ -10497,9 +10824,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.32", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.32.tgz", - "integrity": "sha512-M+7ph0VGBQqqpTT2YrabjNKSQ2fEl9PVx6AK3N558gDH9NO8O6XN9SXXFWRo9u9PbEg/bWq+tjXQr+eXmxubCw==", + "version": "1.5.36", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.36.tgz", + "integrity": "sha512-HYTX8tKge/VNp6FGO+f/uVDmUkq+cEfcxYhKf15Akc4M5yxt5YmorwlAitKWjWhWQnKcDRBAQKXkhqqXMqcrjw==", "license": "ISC" }, "node_modules/emittery": { @@ -11372,19 +11699,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/eslint/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -11737,6 +12051,18 @@ "node": ">=8.6.0" } }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fast-json-patch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", @@ -12018,9 +12344,9 @@ } }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", "dev": true, "license": "MIT", "dependencies": { @@ -12151,6 +12477,7 @@ "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, @@ -12346,15 +12673,16 @@ } }, "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "license": "ISC", "dependencies": { - "is-glob": "^4.0.1" + "is-glob": "^4.0.3" }, "engines": { - "node": ">= 6" + "node": ">=10.13.0" } }, "node_modules/glob-to-regexp": { @@ -13107,6 +13435,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -16558,9 +16887,9 @@ } }, "node_modules/memfs": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.12.0.tgz", - "integrity": "sha512-74wDsex5tQDSClVkeK1vtxqYCAgCoXxx+K4NSHzgU/muYVYByFqa+0RnrPO9NM6naWm1+G9JmZ0p6QHhXmeYfA==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.13.0.tgz", + "integrity": "sha512-dIs5KGy24fbdDhIAg0RxXpFqQp3RwL6wgSMRF9OSuphL/Uc9a4u2/SDJKPLj/zUgtOGKuHrRMrj563+IErj4Cg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -16960,7 +17289,8 @@ "node_modules/monaco-editor": { "version": "0.52.0", "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.0.tgz", - "integrity": "sha512-OeWhNpABLCeTqubfqLMXGsqf6OmPU6pHM85kF3dhy6kq5hnhuVS1p3VrEW/XhWHc71P2tHyS5JFySD8mgs1crw==" + "integrity": "sha512-OeWhNpABLCeTqubfqLMXGsqf6OmPU6pHM85kF3dhy6kq5hnhuVS1p3VrEW/XhWHc71P2tHyS5JFySD8mgs1crw==", + "license": "MIT" }, "node_modules/moo-color": { "version": "1.0.3", @@ -17063,9 +17393,9 @@ } }, "node_modules/nan": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz", - "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==", + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz", + "integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==", "license": "MIT", "optional": true }, @@ -17455,6 +17785,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -17619,15 +17950,15 @@ "license": "MIT" }, "node_modules/nx": { - "version": "19.8.3", - "resolved": "https://registry.npmjs.org/nx/-/nx-19.8.3.tgz", - "integrity": "sha512-/3FF4tgwPGRu4bV6O+aHqhTnOGHKF0/HNVkApUwjimSC+YzOX9VH1uBx2eReb4XC1scxDWkIzVi9gkFSXSQDjQ==", + "version": "19.8.4", + "resolved": "https://registry.npmjs.org/nx/-/nx-19.8.4.tgz", + "integrity": "sha512-fc833c3UKo6kuoG4z0kSKet17yWym3VzcQ+yPWYspxxxd8GFVVk42+9wieyVQDi9YqtKZQ6PdQfSEPm59/M7SA==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { "@napi-rs/wasm-runtime": "0.2.4", - "@nrwl/tao": "19.8.3", + "@nrwl/tao": "19.8.4", "@yarnpkg/lockfile": "^1.1.0", "@yarnpkg/parsers": "3.0.0-rc.46", "@zkochan/js-yaml": "0.0.7", @@ -17666,16 +17997,16 @@ "nx-cloud": "bin/nx-cloud.js" }, "optionalDependencies": { - "@nx/nx-darwin-arm64": "19.8.3", - "@nx/nx-darwin-x64": "19.8.3", - "@nx/nx-freebsd-x64": "19.8.3", - "@nx/nx-linux-arm-gnueabihf": "19.8.3", - "@nx/nx-linux-arm64-gnu": "19.8.3", - "@nx/nx-linux-arm64-musl": "19.8.3", - "@nx/nx-linux-x64-gnu": "19.8.3", - "@nx/nx-linux-x64-musl": "19.8.3", - "@nx/nx-win32-arm64-msvc": "19.8.3", - "@nx/nx-win32-x64-msvc": "19.8.3" + "@nx/nx-darwin-arm64": "19.8.4", + "@nx/nx-darwin-x64": "19.8.4", + "@nx/nx-freebsd-x64": "19.8.4", + "@nx/nx-linux-arm-gnueabihf": "19.8.4", + "@nx/nx-linux-arm64-gnu": "19.8.4", + "@nx/nx-linux-arm64-musl": "19.8.4", + "@nx/nx-linux-x64-gnu": "19.8.4", + "@nx/nx-linux-x64-musl": "19.8.4", + "@nx/nx-win32-arm64-msvc": "19.8.4", + "@nx/nx-win32-x64-msvc": "19.8.4" }, "peerDependencies": { "@swc-node/register": "^1.8.0", @@ -18554,9 +18885,9 @@ } }, "node_modules/pdfjs-dist": { - "version": "4.6.82", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-4.6.82.tgz", - "integrity": "sha512-BUOryeRFwvbLe0lOU6NhkJNuVQUp06WxlJVVCsxdmJ4y5cU3O3s3/0DunVdK1PMm7v2MUw52qKYaidhDH1Z9+w==", + "version": "4.7.76", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-4.7.76.tgz", + "integrity": "sha512-8y6wUgC/Em35IumlGjaJOCm3wV4aY/6sqnIT3fVW/67mXsOZ9HWBn8GDKmJUK0GSzpbmX3gQqwfoFayp78Mtqw==", "license": "Apache-2.0", "engines": { "node": ">=18" @@ -18891,9 +19222,9 @@ "license": "MIT" }, "node_modules/posthog-js": { - "version": "1.166.1", - "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.166.1.tgz", - "integrity": "sha512-K8IpV8FJTCdwhsXFSbKj5vZ6IXNV079lukpG3cRtst2q5vMmUXRQiks7W3lOZLrjWyuJLKZDUiCeeDIUFORRuQ==", + "version": "1.167.0", + "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.167.0.tgz", + "integrity": "sha512-/zXQ6tuJgiF1d4mgg3UsAi/uoyg7UnfFNQtikuALmaE53xFExpcAKbMfHPG/f54QgTvLxSHyGL1kFl/1uspkGg==", "license": "MIT", "dependencies": { "fflate": "^0.4.8", @@ -18902,9 +19233,9 @@ } }, "node_modules/preact": { - "version": "10.24.1", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.1.tgz", - "integrity": "sha512-PnBAwFI3Yjxxcxw75n6VId/5TFxNW/81zexzWD9jn1+eSrOP84NdsS38H5IkF/UH3frqRPT+MvuCoVHjTDTnDw==", + "version": "10.24.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.2.tgz", + "integrity": "sha512-1cSoF0aCC8uaARATfrlz4VCBqE8LwZwRfLgkxJOQwAlQt6ayTmi0D9OF7nXid1POI5SZidFuG9CnlXbDfLqY/Q==", "license": "MIT", "funding": { "type": "opencollective", @@ -19342,6 +19673,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -19354,6 +19686,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -19470,9 +19803,9 @@ "license": "MIT" }, "node_modules/regjsparser": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.11.0.tgz", - "integrity": "sha512-vTbzVAjQDzwQdKuvj7qEq6OlAprCjE656khuGQ4QaBLg7abQ9I9ISpmLuc6inWe7zP75AECjqUa4g4sdQvOXhg==", + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.11.1.tgz", + "integrity": "sha512-1DHODs4B8p/mQHU9kr+jv8+wIC9mtG4eBHxWxIq5mhjE3D5oORhCc6deRKzTjs9DcfRFmj9BHSDguZklqCGFWQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -19894,12 +20227,13 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.79.4", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.79.4.tgz", - "integrity": "sha512-K0QDSNPXgyqO4GZq2HO5Q70TLxTH6cIT59RdoCHMivrC8rqzaTw5ab9prjz9KUN1El4FLXrBXJhik61JR4HcGg==", + "version": "1.79.5", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.79.5.tgz", + "integrity": "sha512-W1h5kp6bdhqFh2tk3DsI771MoEJjvrSY/2ihJRJS4pjIyfJCw0nTsxqhnrUzaLMOJjFchj8rOvraI/YUVjtx5g==", "dev": true, "license": "MIT", "dependencies": { + "@parcel/watcher": "^2.4.1", "chokidar": "^4.0.0", "immutable": "^4.0.0", "source-map-js": ">=0.6.2 <2.0.0" @@ -21075,9 +21409,9 @@ "license": "MIT" }, "node_modules/synckit": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", - "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", + "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", "dev": true, "license": "MIT", "dependencies": { @@ -21429,22 +21763,22 @@ "license": "MIT" }, "node_modules/tldts": { - "version": "6.1.50", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.50.tgz", - "integrity": "sha512-q9GOap6q3KCsLMdOjXhWU5jVZ8/1dIib898JBRLsN+tBhENpBDcAVQbE0epADOjw11FhQQy9AcbqKGBQPUfTQA==", + "version": "6.1.51", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.51.tgz", + "integrity": "sha512-33lfQoL0JsDogIbZ8fgRyvv77GnRtwkNE/MOKocwUgPO1WrSfsq7+vQRKxRQZai5zd+zg97Iv9fpFQSzHyWdLA==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^6.1.50" + "tldts-core": "^6.1.51" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "6.1.50", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.50.tgz", - "integrity": "sha512-na2EcZqmdA2iV9zHV7OHQDxxdciEpxrjbkp+aHmZgnZKHzoElLajP59np5/4+sare9fQBfixgvXKx8ev1d7ytw==", + "version": "6.1.51", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.51.tgz", + "integrity": "sha512-bu9oCYYWC1iRjx+3UnAjqCsfrWNZV1ghNQf49b3w5xE8J/tNShHTzp5syWJfwGH+pxUgTTLUnzHnfuydW7wmbg==", "dev": true, "license": "MIT" }, @@ -22872,9 +23206,9 @@ } }, "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", - "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", + "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index b1063f9a90e0..755adfae3d3e 100644 --- a/package.json +++ b/package.json @@ -13,18 +13,18 @@ "node_modules" ], "dependencies": { - "@angular/animations": "18.2.7", - "@angular/cdk": "18.2.7", - "@angular/common": "18.2.7", - "@angular/compiler": "18.2.7", - "@angular/core": "18.2.7", - "@angular/forms": "18.2.7", - "@angular/localize": "18.2.7", - "@angular/material": "18.2.7", - "@angular/platform-browser": "18.2.7", - "@angular/platform-browser-dynamic": "18.2.7", - "@angular/router": "18.2.7", - "@angular/service-worker": "18.2.7", + "@angular/animations": "18.2.8", + "@angular/cdk": "18.2.8", + "@angular/common": "18.2.8", + "@angular/compiler": "18.2.8", + "@angular/core": "18.2.8", + "@angular/forms": "18.2.8", + "@angular/localize": "18.2.8", + "@angular/material": "18.2.8", + "@angular/platform-browser": "18.2.8", + "@angular/platform-browser-dynamic": "18.2.8", + "@angular/router": "18.2.8", + "@angular/service-worker": "18.2.8", "@ctrl/ngx-emoji-mart": "9.2.0", "@danielmoncada/angular-datetime-picker": "18.1.0", "@fingerprintjs/fingerprintjs": "4.5.0", @@ -36,7 +36,7 @@ "@ng-bootstrap/ng-bootstrap": "17.0.1", "@ngx-translate/core": "15.0.0", "@ngx-translate/http-loader": "8.0.0", - "@sentry/angular": "8.33.1", + "@sentry/angular": "8.34.0", "@siemens/ngx-datatable": "22.4.1", "@swimlane/ngx-charts": "20.5.0", "@swimlane/ngx-graph": "8.4.0", @@ -62,8 +62,8 @@ "ngx-infinite-scroll": "18.0.0", "ngx-webstorage": "18.0.0", "papaparse": "5.4.1", - "pdfjs-dist": "4.6.82", - "posthog-js": "1.166.1", + "pdfjs-dist": "4.7.76", + "posthog-js": "1.167.0", "rxjs": "7.8.1", "showdown": "2.1.0", "showdown-highlight": "3.1.0", @@ -119,29 +119,29 @@ }, "devDependencies": { "@angular-builders/jest": "18.0.0", - "@angular-devkit/build-angular": "18.2.7", + "@angular-devkit/build-angular": "18.2.8", "@angular-eslint/builder": "18.3.1", "@angular-eslint/eslint-plugin": "18.3.1", "@angular-eslint/eslint-plugin-template": "18.3.1", "@angular-eslint/schematics": "18.3.1", "@angular-eslint/template-parser": "18.3.1", - "@angular/cli": "18.2.7", - "@angular/compiler-cli": "18.2.7", - "@angular/language-service": "18.2.7", - "@sentry/types": "8.33.1", + "@angular/cli": "18.2.8", + "@angular/compiler-cli": "18.2.8", + "@angular/language-service": "18.2.8", + "@sentry/types": "8.34.0", "@types/crypto-js": "4.2.2", "@types/d3-shape": "3.1.6", "@types/dompurify": "3.0.5", "@types/jest": "29.5.13", "@types/lodash-es": "4.17.12", - "@types/node": "22.7.4", + "@types/node": "22.7.5", "@types/papaparse": "5.3.14", "@types/showdown": "2.0.6", "@types/smoothscroll-polyfill": "0.3.4", "@types/sockjs-client": "1.5.4", "@types/uuid": "10.0.0", - "@typescript-eslint/eslint-plugin": "8.8.0", - "@typescript-eslint/parser": "8.8.0", + "@typescript-eslint/eslint-plugin": "8.8.1", + "@typescript-eslint/parser": "8.8.1", "eslint": "9.12.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-deprecation": "3.0.0", @@ -162,7 +162,7 @@ "ng-mocks": "14.13.1", "prettier": "3.3.3", "rimraf": "6.0.1", - "sass": "1.79.4", + "sass": "1.79.5", "ts-jest": "29.2.5", "typescript": "5.5.4", "weak-napi": "2.0.2" diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/web/LearningPathResource.java b/src/main/java/de/tum/cit/aet/artemis/atlas/web/LearningPathResource.java index d940e50acc51..f69dae28f80c 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/web/LearningPathResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/web/LearningPathResource.java @@ -59,6 +59,7 @@ import de.tum.cit.aet.artemis.lecture.service.LearningObjectService; @Profile(PROFILE_CORE) +@FeatureToggle(Feature.LearningPaths) @RestController @RequestMapping("api/") public class LearningPathResource { @@ -108,7 +109,6 @@ public LearningPathResource(CourseService courseService, CourseRepository course * @return the ResponseEntity with status 200 (OK) */ @PutMapping("courses/{courseId}/learning-paths/enable") - @FeatureToggle(Feature.LearningPaths) @EnforceAtLeastInstructorInCourse public ResponseEntity enableLearningPathsForCourse(@PathVariable long courseId) { log.debug("REST request to enable learning paths for course with id: {}", courseId); @@ -129,7 +129,6 @@ public ResponseEntity enableLearningPathsForCourse(@PathVariable long cour * @return the ResponseEntity with status 200 (OK) */ @PutMapping("courses/{courseId}/learning-paths/generate-missing") - @FeatureToggle(Feature.LearningPaths) @EnforceAtLeastInstructorInCourse public ResponseEntity generateMissingLearningPathsForCourse(@PathVariable long courseId) { log.debug("REST request to generate missing learning paths for course with id: {}", courseId); @@ -147,7 +146,6 @@ public ResponseEntity generateMissingLearningPathsForCourse(@PathVariable * @return the ResponseEntity with status 200 (OK) and with body the desired page, sorted and matching the given query */ @GetMapping("courses/{courseId}/learning-paths") - @FeatureToggle(Feature.LearningPaths) @EnforceAtLeastInstructorInCourse public ResponseEntity> getLearningPathsOnPage(@PathVariable long courseId, SearchTermPageableSearchDTO search) { log.debug("REST request to get learning paths for course with id: {}", courseId); @@ -162,7 +160,6 @@ public ResponseEntity> getLearni * @return the ResponseEntity with status 200 (OK) and with body the health status */ @GetMapping("courses/{courseId}/learning-path-health") - @FeatureToggle(Feature.LearningPaths) @EnforceAtLeastInstructorInCourse public ResponseEntity getHealthStatusForCourse(@PathVariable long courseId) { log.debug("REST request to get health status of learning paths in course with id: {}", courseId); @@ -177,7 +174,6 @@ public ResponseEntity getHealthStatusForCourse(@PathVaria * @return the ResponseEntity with status 200 (OK) and with body the learning path */ @GetMapping("learning-path/{learningPathId}") - @FeatureToggle(Feature.LearningPaths) @EnforceAtLeastStudent public ResponseEntity getLearningPath(@PathVariable long learningPathId) { log.debug("REST request to get learning path with id: {}", learningPathId); @@ -196,7 +192,6 @@ public ResponseEntity getLearningPath(@PathVariable * @return the ResponseEntity with status 200 (OK) and with body the graph */ @GetMapping("learning-path/{learningPathId}/competency-graph") - @FeatureToggle(Feature.LearningPaths) @EnforceAtLeastStudent public ResponseEntity getLearningPathCompetencyGraph(@PathVariable long learningPathId) { log.debug("REST request to get competency graph for learning path with id: {}", learningPathId); @@ -215,7 +210,6 @@ public ResponseEntity getLearningPathCompetencyG * @return the ResponseEntity with status 200 (OK) and with body the ngx representation of the learning path */ @GetMapping("learning-path/{learningPathId}/graph") - @FeatureToggle(Feature.LearningPaths) @EnforceAtLeastStudent public ResponseEntity getLearningPathNgxGraph(@PathVariable long learningPathId) { log.debug("REST request to get ngx graph representation of learning path with id: {}", learningPathId); @@ -229,7 +223,6 @@ public ResponseEntity getLearningPathNgxGraph(@PathVariable * @return the ResponseEntity with status 200 (OK) and with body the ngx representation of the learning path */ @GetMapping("learning-path/{learningPathId}/path") - @FeatureToggle(Feature.LearningPaths) @EnforceAtLeastStudent public ResponseEntity getLearningPathNgxPath(@PathVariable long learningPathId) { log.debug("REST request to get ngx path representation of learning path with id: {}", learningPathId); @@ -246,7 +239,6 @@ public ResponseEntity getLearningPathNgxPath(@PathVariable l * @return the ResponseEntity with status 200 (OK) and with body the navigation information */ @GetMapping("learning-path/{learningPathId}/relative-navigation") - @FeatureToggle(Feature.LearningPaths) @EnforceAtLeastStudent public ResponseEntity getRelativeLearningPathNavigation(@PathVariable @Valid long learningPathId, @RequestParam long learningObjectId, @RequestParam LearningObjectType learningObjectType, @RequestParam long competencyId) { @@ -265,7 +257,6 @@ public ResponseEntity getRelativeLearningPathNavigati * @return the ResponseEntity with status 200 (OK) and with body the navigation information */ @GetMapping("learning-path/{learningPathId}/navigation") - @FeatureToggle(Feature.LearningPaths) @EnforceAtLeastStudent public ResponseEntity getLearningPathNavigation(@PathVariable long learningPathId) { log.debug("REST request to get navigation for learning path with id: {}", learningPathId); @@ -281,7 +272,6 @@ public ResponseEntity getLearningPathNavigation(@Path * @return the ResponseEntity with status 200 (OK) and with body the navigation overview */ @GetMapping("learning-path/{learningPathId}/navigation-overview") - @FeatureToggle(Feature.LearningPaths) @EnforceAtLeastStudent public ResponseEntity getLearningPathNavigationOverview(@PathVariable @Valid long learningPathId) { log.debug("REST request to get navigation overview for learning path with id: {}", learningPathId); diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/web/ScienceResource.java b/src/main/java/de/tum/cit/aet/artemis/atlas/web/ScienceResource.java index 7312cc824997..d4e1563df41c 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/web/ScienceResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/web/ScienceResource.java @@ -21,6 +21,7 @@ * REST controller providing the science related endpoints. */ @Profile(PROFILE_CORE) +@FeatureToggle(Feature.Science) @RestController @RequestMapping("api/") public class ScienceResource { @@ -40,7 +41,6 @@ public ScienceResource(ScienceEventService scienceEventService) { * @return the ResponseEntity with status 200 (OK) */ @PutMapping(value = "science") - @FeatureToggle(Feature.Science) @EnforceAtLeastStudent public ResponseEntity science(@RequestBody ScienceEventDTO event) { log.debug("REST request to log science event of type {}", event); diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/web/StandardizedCompetencyResource.java b/src/main/java/de/tum/cit/aet/artemis/atlas/web/StandardizedCompetencyResource.java index 8b6316fcc51c..b205db140bc3 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/web/StandardizedCompetencyResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/web/StandardizedCompetencyResource.java @@ -29,6 +29,7 @@ * REST controller for managing {@link StandardizedCompetency} entities. */ @Profile(PROFILE_CORE) +@FeatureToggle(Feature.StandardizedCompetencies) @RestController @RequestMapping("api/standardized-competencies/") public class StandardizedCompetencyResource { @@ -58,7 +59,6 @@ public StandardizedCompetencyResource(StandardizedCompetencyService standardized * @return the ResponseEntity with status 200 (OK) and with body containing the standardized competency, or with status 404 (Not Found) */ @GetMapping("{competencyId}") - @FeatureToggle(Feature.StandardizedCompetencies) @EnforceAtLeastInstructor public ResponseEntity getStandardizedCompetency(@PathVariable long competencyId) { log.debug("REST request to get standardized competency with id : {}", competencyId); @@ -74,7 +74,6 @@ public ResponseEntity getStandardizedCompetency(@PathVar * @return the ResponseEntity with status 200 (OK) and with body containing the knowledge areas */ @GetMapping("for-tree-view") - @FeatureToggle(Feature.StandardizedCompetencies) @EnforceAtLeastInstructor public ResponseEntity> getAllForTreeView() { log.debug("REST request to all knowledge areas for tree view"); @@ -91,7 +90,6 @@ public ResponseEntity> getAllForTreeView() { * @return the ResponseEntity with status 200 (OK) and with body containing the knowledge area, or with status 404 (Not Found) */ @GetMapping("knowledge-areas/{knowledgeAreaId}") - @FeatureToggle(Feature.StandardizedCompetencies) @EnforceAtLeastInstructor public ResponseEntity getKnowledgeArea(@PathVariable long knowledgeAreaId) { log.debug("REST request to get knowledge area with id : {}", knowledgeAreaId); @@ -107,7 +105,6 @@ public ResponseEntity getKnowledgeArea(@PathVariable long knowled * @return the ResponseEntity with status 200 (OK) and with body containing the list of sources */ @GetMapping("sources") - @FeatureToggle(Feature.StandardizedCompetencies) @EnforceAtLeastInstructor public ResponseEntity> getSources() { log.debug("REST request to get all sources"); diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/web/admin/AdminStandardizedCompetencyResource.java b/src/main/java/de/tum/cit/aet/artemis/atlas/web/admin/AdminStandardizedCompetencyResource.java index 376cedb132dc..5c974101f8bb 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/web/admin/AdminStandardizedCompetencyResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/web/admin/AdminStandardizedCompetencyResource.java @@ -38,6 +38,8 @@ * Admin REST controller for managing {@link StandardizedCompetency} entities. */ @Profile(PROFILE_CORE) +@FeatureToggle(Feature.StandardizedCompetencies) +@EnforceAdmin @RestController @RequestMapping("api/admin/") public class AdminStandardizedCompetencyResource { @@ -61,8 +63,6 @@ public AdminStandardizedCompetencyResource(StandardizedCompetencyService standar * @throws URISyntaxException if the Location URI syntax is incorrect */ @PostMapping("standardized-competencies") - @FeatureToggle(Feature.StandardizedCompetencies) - @EnforceAdmin public ResponseEntity createStandardizedCompetency(@RequestBody @Valid StandardizedCompetencyRequestDTO competency) throws URISyntaxException { log.debug("REST request to create standardized competency : {}", competency); @@ -79,8 +79,6 @@ public ResponseEntity createStandardizedCompete * @return the ResponseEntity with status 200 (OK) and with body the updated standardized competency */ @PutMapping("standardized-competencies/{competencyId}") - @FeatureToggle(Feature.StandardizedCompetencies) - @EnforceAdmin public ResponseEntity updateStandardizedCompetency(@PathVariable long competencyId, @RequestBody @Valid StandardizedCompetencyRequestDTO competency) { log.debug("REST request to update standardized competency : {}", competency); @@ -97,8 +95,6 @@ public ResponseEntity updateStandardizedCompete * @return the ResponseEntity with status 200 (OK) */ @DeleteMapping("standardized-competencies/{competencyId}") - @FeatureToggle(Feature.StandardizedCompetencies) - @EnforceAdmin public ResponseEntity deleteStandardizedCompetency(@PathVariable long competencyId) { log.debug("REST request to delete standardized competency : {}", competencyId); @@ -115,8 +111,6 @@ public ResponseEntity deleteStandardizedCompetency(@PathVariable long comp * @throws URISyntaxException if the Location URI syntax is incorrect */ @PostMapping("standardized-competencies/knowledge-areas") - @FeatureToggle(Feature.StandardizedCompetencies) - @EnforceAdmin public ResponseEntity createKnowledgeArea(@RequestBody @Valid KnowledgeAreaRequestDTO knowledgeArea) throws URISyntaxException { log.debug("REST request to create knowledge area : {}", knowledgeArea); @@ -134,8 +128,6 @@ public ResponseEntity createKnowledgeArea(@RequestBody @ * @return the ResponseEntity with status 200 (OK) and with body the updated knowledge area */ @PutMapping("standardized-competencies/knowledge-areas/{knowledgeAreaId}") - @FeatureToggle(Feature.StandardizedCompetencies) - @EnforceAdmin public ResponseEntity updateKnowledgeArea(@PathVariable long knowledgeAreaId, @RequestBody @Valid KnowledgeAreaRequestDTO knowledgeArea) { log.debug("REST request to update knowledge area : {}", knowledgeArea); @@ -151,8 +143,6 @@ public ResponseEntity updateKnowledgeArea(@PathVariable * @return the ResponseEntity with status 200 (OK) */ @DeleteMapping("standardized-competencies/knowledge-areas/{knowledgeAreaId}") - @FeatureToggle(Feature.StandardizedCompetencies) - @EnforceAdmin public ResponseEntity deleteKnowledgeArea(@PathVariable long knowledgeAreaId) { log.debug("REST request to delete knowledge area : {}", knowledgeAreaId); @@ -168,8 +158,6 @@ public ResponseEntity deleteKnowledgeArea(@PathVariable long knowledgeArea * @return the ResponseEntity with status 200 (OK) */ @PutMapping("standardized-competencies/import") - @FeatureToggle(Feature.StandardizedCompetencies) - @EnforceAdmin public ResponseEntity importStandardizedCompetencyCatalog(@RequestBody @Valid StandardizedCompetencyCatalogDTO standardizedCompetencyCatalogDTO) { log.debug("REST request to import standardized competency catalog"); @@ -184,8 +172,6 @@ public ResponseEntity importStandardizedCompetencyCatalog(@RequestBody @Va * @return the ResponseEntity with status 200 (OK) and the body containing the JSON string of the standardized competency catalog */ @GetMapping("standardized-competencies/export") - @FeatureToggle(Feature.StandardizedCompetencies) - @EnforceAdmin public ResponseEntity exportStandardizedCompetencyCatalog() { log.debug("REST request to export standardized competency catalog"); diff --git a/src/main/java/de/tum/cit/aet/artemis/communication/domain/Post.java b/src/main/java/de/tum/cit/aet/artemis/communication/domain/Post.java index d3bf4e217ea3..4ff2d48fedf5 100644 --- a/src/main/java/de/tum/cit/aet/artemis/communication/domain/Post.java +++ b/src/main/java/de/tum/cit/aet/artemis/communication/domain/Post.java @@ -167,6 +167,10 @@ public void addTag(String tag) { this.tags.add(tag); } + public void setCourse(Course course) { + this.course = course; + } + public Conversation getConversation() { return conversation; } diff --git a/src/main/java/de/tum/cit/aet/artemis/communication/domain/push_notification/PushNotificationDeviceConfiguration.java b/src/main/java/de/tum/cit/aet/artemis/communication/domain/push_notification/PushNotificationDeviceConfiguration.java index c6bfd3384110..b9a911ce6194 100644 --- a/src/main/java/de/tum/cit/aet/artemis/communication/domain/push_notification/PushNotificationDeviceConfiguration.java +++ b/src/main/java/de/tum/cit/aet/artemis/communication/domain/push_notification/PushNotificationDeviceConfiguration.java @@ -106,7 +106,9 @@ public boolean equals(Object object) { return false; } PushNotificationDeviceConfiguration that = (PushNotificationDeviceConfiguration) object; - return token.equals(that.token) && deviceType == that.deviceType && expirationDate.equals(that.expirationDate) && Arrays.equals(secretKey, that.secretKey) + // Use compareTo rather than equals for dates to ensure timestamps and dates with the same time are considered equal + // This is caused by Java internal design having different classes for Date (java.util) and Timestamp (java.sql) + return token.equals(that.token) && deviceType == that.deviceType && expirationDate.compareTo(that.expirationDate) == 0 && Arrays.equals(secretKey, that.secretKey) && owner.equals(that.owner); } diff --git a/src/main/java/de/tum/cit/aet/artemis/communication/repository/AnswerPostRepository.java b/src/main/java/de/tum/cit/aet/artemis/communication/repository/AnswerPostRepository.java index c6a915b994e2..db61138b3a73 100644 --- a/src/main/java/de/tum/cit/aet/artemis/communication/repository/AnswerPostRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/communication/repository/AnswerPostRepository.java @@ -30,4 +30,6 @@ default AnswerPost findAnswerPostByIdElseThrow(Long answerPostId) { default AnswerPost findAnswerMessageByIdElseThrow(Long answerPostId) { return getValueElseThrow(findById(answerPostId).filter(answerPost -> answerPost.getPost().getConversation() != null), answerPostId); } + + long countAnswerPostsByPostIdIn(List postIds); } diff --git a/src/main/java/de/tum/cit/aet/artemis/communication/repository/PostRepository.java b/src/main/java/de/tum/cit/aet/artemis/communication/repository/PostRepository.java index 449a629fb4af..aacfbc33d179 100644 --- a/src/main/java/de/tum/cit/aet/artemis/communication/repository/PostRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/communication/repository/PostRepository.java @@ -45,4 +45,8 @@ default Post findPostByIdElseThrow(Long postId) throws EntityNotFoundException { default Post findPostOrMessagePostByIdElseThrow(Long postId) throws EntityNotFoundException { return getValueElseThrow(findById(postId), postId); } + + List findAllByConversationId(Long conversationId); + + List findAllByCourseId(Long courseId); } diff --git a/src/main/java/de/tum/cit/aet/artemis/communication/web/admin/AdminSystemNotificationResource.java b/src/main/java/de/tum/cit/aet/artemis/communication/web/admin/AdminSystemNotificationResource.java index 6850598633e9..4e94766284d6 100644 --- a/src/main/java/de/tum/cit/aet/artemis/communication/web/admin/AdminSystemNotificationResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/communication/web/admin/AdminSystemNotificationResource.java @@ -30,6 +30,7 @@ * REST controller for administrating system notifications. */ @Profile(PROFILE_CORE) +@EnforceAdmin @RestController @RequestMapping("api/admin/") public class AdminSystemNotificationResource { @@ -58,7 +59,6 @@ public AdminSystemNotificationResource(SystemNotificationRepository systemNotifi * @throws URISyntaxException if the Location URI syntax is incorrect */ @PostMapping("system-notifications") - @EnforceAdmin public ResponseEntity createSystemNotification(@RequestBody SystemNotification systemNotification) throws URISyntaxException { log.debug("REST request to save SystemNotification : {}", systemNotification); if (systemNotification.getId() != null) { @@ -79,7 +79,6 @@ public ResponseEntity createSystemNotification(@RequestBody System * status 500 (Internal Server Error) if the system notification couldn't be updated */ @PutMapping("system-notifications") - @EnforceAdmin public ResponseEntity updateSystemNotification(@RequestBody SystemNotification systemNotification) { log.debug("REST request to update SystemNotification : {}", systemNotification); if (systemNotification.getId() == null) { @@ -101,7 +100,6 @@ public ResponseEntity updateSystemNotification(@RequestBody * @return the ResponseEntity with status 200 (OK) */ @DeleteMapping("system-notifications/{notificationId}") - @EnforceAdmin public ResponseEntity deleteSystemNotification(@PathVariable Long notificationId) { log.debug("REST request to delete SystemNotification : {}", notificationId); systemNotificationRepository.deleteById(notificationId); diff --git a/src/main/java/de/tum/cit/aet/artemis/core/config/websocket/WebsocketConfiguration.java b/src/main/java/de/tum/cit/aet/artemis/core/config/websocket/WebsocketConfiguration.java index 9163cfb7d7f1..d0c6941cc698 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/config/websocket/WebsocketConfiguration.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/config/websocket/WebsocketConfiguration.java @@ -64,7 +64,6 @@ import de.tum.cit.aet.artemis.core.security.jwt.TokenProvider; import de.tum.cit.aet.artemis.core.service.AuthorizationCheckService; import de.tum.cit.aet.artemis.exam.repository.ExamRepository; -import de.tum.cit.aet.artemis.exercise.domain.Exercise; import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; import de.tum.cit.aet.artemis.exercise.repository.StudentParticipationRepository; @@ -309,8 +308,7 @@ private boolean allowSubscription(@Nullable Principal principal, String destinat // TODO: Is it right that TAs are not allowed to subscribe to exam exercises? if (exerciseRepository.isExamExercise(exerciseId)) { - Exercise exercise = exerciseRepository.findByIdElseThrow(exerciseId); - return authorizationCheckService.isAtLeastInstructorInCourse(login, exercise.getCourseViaExerciseGroupOrCourseMember().getId()); + return authorizationCheckService.isAtLeastInstructorInExercise(login, exerciseId); } else { return authorizationCheckService.isAtLeastTeachingAssistantInExercise(login, exerciseId); diff --git a/src/main/java/de/tum/cit/aet/artemis/core/domain/User.java b/src/main/java/de/tum/cit/aet/artemis/core/domain/User.java index 7fa995658af1..6498340f3bc2 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/domain/User.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/domain/User.java @@ -561,4 +561,9 @@ public void hasAcceptedIrisElseThrow() { public String getSshPublicKey() { return sshPublicKey; } + + @Nullable + public @Size(max = 100) String getSshPublicKeyHash() { + return sshPublicKeyHash; + } } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/dto/CourseDeletionSummaryDTO.java b/src/main/java/de/tum/cit/aet/artemis/core/dto/CourseDeletionSummaryDTO.java new file mode 100644 index 000000000000..2f3b8d51596c --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/core/dto/CourseDeletionSummaryDTO.java @@ -0,0 +1,7 @@ +package de.tum.cit.aet.artemis.core.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public record CourseDeletionSummaryDTO(long numberOfBuilds, long numberOfCommunicationPosts, long numberOfAnswerPosts) { +} diff --git a/src/main/java/de/tum/cit/aet/artemis/core/dto/UserDTO.java b/src/main/java/de/tum/cit/aet/artemis/core/dto/UserDTO.java index 1ac72940f919..3d627425a8f7 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/dto/UserDTO.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/dto/UserDTO.java @@ -78,6 +78,8 @@ public class UserDTO extends AuditingEntityDTO { private String sshPublicKey; + private String sshKeyHash; + private ZonedDateTime irisAccepted; public UserDTO() { @@ -291,4 +293,12 @@ public ZonedDateTime getIrisAccepted() { public void setIrisAccepted(ZonedDateTime irisAccepted) { this.irisAccepted = irisAccepted; } + + public String getSshKeyHash() { + return sshKeyHash; + } + + public void setSshKeyHash(String sshKeyHash) { + this.sshKeyHash = sshKeyHash; + } } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/security/annotations/AnnotationUtils.java b/src/main/java/de/tum/cit/aet/artemis/core/security/annotations/AnnotationUtils.java index c5aef335defb..028342bfeee9 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/security/annotations/AnnotationUtils.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/security/annotations/AnnotationUtils.java @@ -3,7 +3,9 @@ import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Optional; import jakarta.validation.constraints.NotBlank; @@ -22,7 +24,8 @@ private AnnotationUtils() { } /** - * Extracts the annotation from the method or type + * Extracts the annotation from the method or type and all super classes. + * In case multiple versions of the annotation are present, the one closest to the method is returned. * * @param clazz the annotation class * @param joinPoint the join point @@ -33,26 +36,71 @@ private AnnotationUtils() { public static Optional getAnnotation(@NotNull Class clazz, @NotNull ProceedingJoinPoint joinPoint) { final var method = ((MethodSignature) joinPoint.getSignature()).getMethod(); T annotation = method.getAnnotation(clazz); + + Optional foundAnnotation = getAnnotation(clazz, method.getDeclaredAnnotations(), annotation); + if (foundAnnotation.isPresent()) { + return foundAnnotation; + } + + for (Class declaringClass = method.getDeclaringClass(); declaringClass != null; declaringClass = declaringClass.getSuperclass()) { + annotation = declaringClass.getAnnotation(clazz); + foundAnnotation = getAnnotation(clazz, declaringClass.getDeclaredAnnotations(), annotation); + if (foundAnnotation.isPresent()) { + return foundAnnotation; + } + } + + return Optional.empty(); + } + + private static Optional getAnnotation(Class clazz, Annotation[] declaredAnnotations, T annotation) { if (annotation != null) { return Optional.of(annotation); } - for (Annotation a : method.getDeclaredAnnotations()) { + for (Annotation a : declaredAnnotations) { annotation = a.annotationType().getAnnotation(clazz); if (annotation != null) { return Optional.of(annotation); } } - annotation = method.getDeclaringClass().getAnnotation(clazz); + return Optional.empty(); + } + + /** + * Extracts the annotations from the method or type and all super classes. + * In case multiple versions of the annotation are present, all are returned. + * + * @param clazz the annotation class + * @param joinPoint the join point + * @param the type of the annotation + * @return the annotations if they are present, empty otherwise + */ + public static List getAnnotations(@NotNull Class clazz, @NotNull ProceedingJoinPoint joinPoint) { + List annotations = new ArrayList<>(); + + final var method = ((MethodSignature) joinPoint.getSignature()).getMethod(); + T annotation = method.getAnnotation(clazz); + + addAnnotations(clazz, method.getDeclaredAnnotations(), annotation, annotations); + + for (Class declaringClass = method.getDeclaringClass(); declaringClass != null; declaringClass = declaringClass.getSuperclass()) { + annotation = declaringClass.getAnnotation(clazz); + addAnnotations(clazz, declaringClass.getDeclaredAnnotations(), annotation, annotations); + } + + return annotations; + } + + private static void addAnnotations(Class clazz, Annotation[] declaredAnnotations, T annotation, List annotations) { if (annotation != null) { - return Optional.of(annotation); + annotations.add(annotation); } - for (Annotation a : method.getDeclaringClass().getDeclaredAnnotations()) { + for (Annotation a : declaredAnnotations) { annotation = a.annotationType().getAnnotation(clazz); if (annotation != null) { - return Optional.of(annotation); + annotations.add(annotation); } } - return Optional.empty(); } /** diff --git a/src/main/java/de/tum/cit/aet/artemis/core/security/annotations/EnforceAdmin.java b/src/main/java/de/tum/cit/aet/artemis/core/security/annotations/EnforceAdmin.java index 5adbcd73c16c..9fdbf88d9d82 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/security/annotations/EnforceAdmin.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/security/annotations/EnforceAdmin.java @@ -10,11 +10,8 @@ /** * This annotation is used to enforce that the user is an admin. * It should only be used with endpoints starting with {@code /api/admin/} - *

- * It's only addable to methods. The intention is that a developer can see the required role without the need to scroll up. - * This also prevents overrides of the annotation. */ -@Target(ElementType.METHOD) +@Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @PreAuthorize("hasRole('ADMIN')") public @interface EnforceAdmin { diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/CourseService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/CourseService.java index 01bb68edc441..cba16e58ad7e 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/CourseService.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/CourseService.java @@ -58,9 +58,12 @@ import de.tum.cit.aet.artemis.atlas.repository.PrerequisiteRepository; import de.tum.cit.aet.artemis.atlas.service.learningpath.LearningPathService; import de.tum.cit.aet.artemis.communication.domain.NotificationType; +import de.tum.cit.aet.artemis.communication.domain.Post; import de.tum.cit.aet.artemis.communication.domain.notification.GroupNotification; +import de.tum.cit.aet.artemis.communication.repository.AnswerPostRepository; import de.tum.cit.aet.artemis.communication.repository.FaqRepository; import de.tum.cit.aet.artemis.communication.repository.GroupNotificationRepository; +import de.tum.cit.aet.artemis.communication.repository.PostRepository; import de.tum.cit.aet.artemis.communication.repository.conversation.ConversationRepository; import de.tum.cit.aet.artemis.communication.service.notifications.GroupNotificationService; import de.tum.cit.aet.artemis.core.config.Constants; @@ -68,6 +71,7 @@ import de.tum.cit.aet.artemis.core.domain.DomainObject; import de.tum.cit.aet.artemis.core.domain.User; import de.tum.cit.aet.artemis.core.dto.CourseContentCount; +import de.tum.cit.aet.artemis.core.dto.CourseDeletionSummaryDTO; import de.tum.cit.aet.artemis.core.dto.CourseManagementDetailViewDTO; import de.tum.cit.aet.artemis.core.dto.DueDateStat; import de.tum.cit.aet.artemis.core.dto.SearchResultPageDTO; @@ -104,6 +108,7 @@ import de.tum.cit.aet.artemis.plagiarism.domain.PlagiarismCase; import de.tum.cit.aet.artemis.plagiarism.repository.PlagiarismCaseRepository; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; +import de.tum.cit.aet.artemis.programming.repository.BuildJobRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository; import de.tum.cit.aet.artemis.tutorialgroup.repository.TutorialGroupNotificationRepository; import de.tum.cit.aet.artemis.tutorialgroup.repository.TutorialGroupRepository; @@ -201,6 +206,12 @@ public class CourseService { private final TutorialGroupNotificationRepository tutorialGroupNotificationRepository; + private final PostRepository postRepository; + + private final AnswerPostRepository answerPostRepository; + + private final BuildJobRepository buildJobRepository; + public CourseService(CourseRepository courseRepository, ExerciseService exerciseService, ExerciseDeletionService exerciseDeletionService, AuthorizationCheckService authCheckService, UserRepository userRepository, LectureService lectureService, GroupNotificationRepository groupNotificationRepository, ExerciseGroupRepository exerciseGroupRepository, AuditEventRepository auditEventRepository, UserService userService, ExamDeletionService examDeletionService, @@ -213,7 +224,8 @@ public CourseService(CourseRepository courseRepository, ExerciseService exercise TutorialGroupRepository tutorialGroupRepository, PlagiarismCaseRepository plagiarismCaseRepository, ConversationRepository conversationRepository, LearningPathService learningPathService, Optional irisSettingsService, LectureRepository lectureRepository, TutorialGroupNotificationRepository tutorialGroupNotificationRepository, TutorialGroupChannelManagementService tutorialGroupChannelManagementService, - PrerequisiteRepository prerequisiteRepository, CompetencyRelationRepository competencyRelationRepository, FaqRepository faqRepository) { + PrerequisiteRepository prerequisiteRepository, CompetencyRelationRepository competencyRelationRepository, PostRepository postRepository, + AnswerPostRepository answerPostRepository, BuildJobRepository buildJobRepository, FaqRepository faqRepository) { this.courseRepository = courseRepository; this.exerciseService = exerciseService; this.exerciseDeletionService = exerciseDeletionService; @@ -253,6 +265,9 @@ public CourseService(CourseRepository courseRepository, ExerciseService exercise this.tutorialGroupChannelManagementService = tutorialGroupChannelManagementService; this.prerequisiteRepository = prerequisiteRepository; this.competencyRelationRepository = competencyRelationRepository; + this.buildJobRepository = buildJobRepository; + this.postRepository = postRepository; + this.answerPostRepository = answerPostRepository; this.faqRepository = faqRepository; } @@ -444,6 +459,22 @@ public Set findAllOnlineCoursesForPlatformForUser(String registrationId, .collect(Collectors.toSet()); } + /** + * Get the course deletion summary for the given course. + * + * @param course the course for which to get the deletion summary + * @return the course deletion summary + */ + public CourseDeletionSummaryDTO getDeletionSummary(Course course) { + List programmingExerciseIds = course.getExercises().stream().map(Exercise::getId).toList(); + long numberOfBuilds = buildJobRepository.countBuildJobsByExerciseIds(programmingExerciseIds); + + List posts = postRepository.findAllByCourseId(course.getId()); + long numberOfCommunicationPosts = posts.size(); + long numberOfAnswerPosts = answerPostRepository.countAnswerPostsByPostIdIn(posts.stream().map(Post::getId).toList()); + return new CourseDeletionSummaryDTO(numberOfBuilds, numberOfCommunicationPosts, numberOfAnswerPosts); + } + /** * Deletes all elements associated with the course including: *

    diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/ZipFileService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/ZipFileService.java index 4d40473c4eb9..5871cd7ed7d4 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/ZipFileService.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/ZipFileService.java @@ -6,6 +6,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; +import java.util.Set; import java.util.function.Predicate; import java.util.stream.Stream; import java.util.zip.ZipEntry; @@ -33,6 +34,12 @@ public class ZipFileService { private final FileService fileService; + /** + * Set of file names that should be ignored when zipping. + * This currently only includes the gc.log.lock (garbage collector) file created by JGit in programming repositories. + */ + private static final Set IGNORED_ZIP_FILE_NAMES = Set.of(Path.of("gc.log.lock")); + public ZipFileService(FileService fileService) { this.fileService = fileService; } @@ -113,7 +120,7 @@ private void createZipFileFromPathStream(Path zipFilePath, Stream paths, P if (extraFilter != null) { filteredPaths = filteredPaths.filter(extraFilter); } - filteredPaths.forEach(path -> { + filteredPaths.filter(path -> !IGNORED_ZIP_FILE_NAMES.contains(path)).forEach(path -> { ZipEntry zipEntry = new ZipEntry(pathsRoot.relativize(path).toString()); copyToZipFile(zipOutputStream, path, zipEntry); }); diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/feature/FeatureToggleAspect.java b/src/main/java/de/tum/cit/aet/artemis/core/service/feature/FeatureToggleAspect.java index 87785e123cec..ffeed9cfa513 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/feature/FeatureToggleAspect.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/feature/FeatureToggleAspect.java @@ -1,8 +1,11 @@ package de.tum.cit.aet.artemis.core.service.feature; import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; +import static de.tum.cit.aet.artemis.core.security.annotations.AnnotationUtils.getAnnotations; import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; @@ -26,25 +29,24 @@ public FeatureToggleAspect(FeatureToggleService featureToggleService) { /** * Pointcut around all methods or classes annotated with {@link FeatureToggle}. - * - * @param featureToggle The feature toggle annotation containing the relevant features */ - @Pointcut("@within(featureToggle) || @annotation(featureToggle)") - public void callAt(FeatureToggle featureToggle) { + @Pointcut("@within(de.tum.cit.aet.artemis.core.service.feature.FeatureToggle) || @annotation(de.tum.cit.aet.artemis.core.service.feature.FeatureToggle) || execution(@(@de.tum.cit.aet.artemis.core.service.feature.FeatureToggle *) * *(..))") + protected void callAt() { } /** * Aspect around all methods for which a feature toggle has been activated. Will check all specified features and only * execute the underlying method if all features are enabled. Will otherwise return forbidden (as response entity) * - * @param joinPoint Proceeding join point of the aspect - * @param featureToggle The feature toggle annotation containing all features that should get checked + * @param joinPoint Proceeding join point of the aspect * @return The original return value of the called method, if all features are enabled, a forbidden response entity otherwise * @throws Throwable If there was any error during method execution (both the aspect or the actual called method) */ - @Around(value = "callAt(featureToggle)", argNames = "joinPoint,featureToggle") - public Object around(ProceedingJoinPoint joinPoint, FeatureToggle featureToggle) throws Throwable { - if (Arrays.stream(featureToggle.value()).allMatch(featureToggleService::isFeatureEnabled)) { + @Around(value = "callAt()", argNames = "joinPoint") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + List featureToggleAnnotations = getAnnotations(FeatureToggle.class, joinPoint); + Stream features = featureToggleAnnotations.stream().flatMap(featureToggle -> Arrays.stream(featureToggle.value())); + if (features.allMatch(featureToggleService::isFeatureEnabled)) { return joinPoint.proceed(); } else { diff --git a/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AuditResource.java b/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminAuditResource.java similarity index 96% rename from src/main/java/de/tum/cit/aet/artemis/core/web/admin/AuditResource.java rename to src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminAuditResource.java index fe8af9ff7eb0..7c0a339d9717 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AuditResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminAuditResource.java @@ -30,13 +30,14 @@ * REST controller for getting the audit events. */ @Profile(PROFILE_CORE) +@EnforceAdmin @RestController @RequestMapping("api/admin/") -public class AuditResource { +public class AdminAuditResource { private final AuditEventService auditEventService; - public AuditResource(AuditEventService auditEventService) { + public AdminAuditResource(AuditEventService auditEventService) { this.auditEventService = auditEventService; } @@ -47,7 +48,6 @@ public AuditResource(AuditEventService auditEventService) { * @return the ResponseEntity with status 200 (OK) and the list of AuditEvents in body */ @GetMapping("audits") - @EnforceAdmin public ResponseEntity> getAll(Pageable pageable) { Page page = auditEventService.findAll(pageable); HttpHeaders headers = generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page); @@ -63,7 +63,6 @@ public ResponseEntity> getAll(Pageable pageable) { * @return the ResponseEntity with status 200 (OK) and the list of AuditEvents in body */ @GetMapping(value = "audits", params = { "fromDate", "toDate" }) - @EnforceAdmin public ResponseEntity> getByDates(@RequestParam(value = "fromDate") LocalDate fromDate, @RequestParam(value = "toDate") LocalDate toDate, Pageable pageable) { Instant from = fromDate.atStartOfDay(ZoneId.systemDefault()).toInstant(); @@ -81,7 +80,6 @@ public ResponseEntity> getByDates(@RequestParam(value = "fromDa * @return the ResponseEntity with status 200 (OK) and the AuditEvent in body, or status 404 (Not Found) */ @GetMapping("audits/{id:.+}") - @EnforceAdmin public ResponseEntity get(@PathVariable Long id) { return ResponseUtil.wrapOrNotFound(auditEventService.find(id)); } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminBuildJobQueueResource.java b/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminBuildJobQueueResource.java index bd1bcc4dea1b..db71ab34c05a 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminBuildJobQueueResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminBuildJobQueueResource.java @@ -33,6 +33,7 @@ import tech.jhipster.web.util.PaginationUtil; @Profile(PROFILE_LOCALCI) +@EnforceAdmin @RestController @RequestMapping("api/admin/") public class AdminBuildJobQueueResource { @@ -54,7 +55,6 @@ public AdminBuildJobQueueResource(SharedQueueManagementService localCIBuildJobQu * @return the queued build jobs */ @GetMapping("queued-jobs") - @EnforceAdmin public ResponseEntity> getQueuedBuildJobs() { log.debug("REST request to get the queued build jobs"); List buildJobQueue = localCIBuildJobQueueService.getQueuedJobs(); @@ -67,7 +67,6 @@ public ResponseEntity> getQueuedBuildJobs() { * @return the running build jobs */ @GetMapping("running-jobs") - @EnforceAdmin public ResponseEntity> getRunningBuildJobs() { log.debug("REST request to get the running build jobs"); List runningBuildJobs = localCIBuildJobQueueService.getProcessingJobs(); @@ -80,7 +79,6 @@ public ResponseEntity> getRunningBuildJobs() { * @return list of build agents information */ @GetMapping("build-agents") - @EnforceAdmin public ResponseEntity> getBuildAgentSummary() { log.debug("REST request to get information on available build agents"); List buildAgentSummary = localCIBuildJobQueueService.getBuildAgentInformationWithoutRecentBuildJobs(); @@ -94,7 +92,6 @@ public ResponseEntity> getBuildAgentSummary() { * @return the build agent information */ @GetMapping("build-agent") - @EnforceAdmin public ResponseEntity getBuildAgentDetails(@RequestParam String agentName) { log.debug("REST request to get information on build agent {}", agentName); BuildAgentInformation buildAgentDetails = localCIBuildJobQueueService.getBuildAgentInformation().stream().filter(agent -> agent.name().equals(agentName)).findFirst() @@ -109,7 +106,6 @@ public ResponseEntity getBuildAgentDetails(@RequestParam * @return the ResponseEntity with the result of the cancellation */ @DeleteMapping("cancel-job/{buildJobId}") - @EnforceAdmin public ResponseEntity cancelBuildJob(@PathVariable String buildJobId) { log.debug("REST request to cancel the build job with id {}", buildJobId); // Call the cancelBuildJob method in LocalCIBuildJobManagementService @@ -124,7 +120,6 @@ public ResponseEntity cancelBuildJob(@PathVariable String buildJobId) { * @return the ResponseEntity with the result of the cancellation */ @DeleteMapping("cancel-all-queued-jobs") - @EnforceAdmin public ResponseEntity cancelAllQueuedBuildJobs() { log.debug("REST request to cancel all queued build jobs"); // Call the cancelAllQueuedBuildJobs method in LocalCIBuildJobManagementService @@ -139,7 +134,6 @@ public ResponseEntity cancelAllQueuedBuildJobs() { * @return the ResponseEntity with the result of the cancellation */ @DeleteMapping("cancel-all-running-jobs") - @EnforceAdmin public ResponseEntity cancelAllRunningBuildJobs() { log.debug("REST request to cancel all running build jobs"); // Call the cancelAllRunningBuildJobs method in LocalCIBuildJobManagementService @@ -155,7 +149,6 @@ public ResponseEntity cancelAllRunningBuildJobs() { * @return the ResponseEntity with the result of the cancellation */ @DeleteMapping("cancel-all-running-jobs-for-agent") - @EnforceAdmin public ResponseEntity cancelAllRunningBuildJobsForAgent(@RequestParam String agentName) { log.debug("REST request to cancel all running build jobs for agent {}", agentName); // Call the cancelAllRunningBuildJobsForAgent method in LocalCIBuildJobManagementService @@ -171,7 +164,6 @@ public ResponseEntity cancelAllRunningBuildJobsForAgent(@RequestParam Stri * @return the page of finished build jobs */ @GetMapping("finished-jobs") - @EnforceAdmin public ResponseEntity> getFinishedBuildJobs(FinishedBuildJobPageableSearchDTO search) { log.debug("REST request to get a page of finished build jobs with build status {}, build agent address {}, start date {} and end date {}", search.buildStatus(), search.buildAgentAddress(), search.startDate(), search.endDate()); @@ -190,7 +182,6 @@ public ResponseEntity> getFinishedBuildJobs(FinishedBu * @return the build job statistics */ @GetMapping("build-job-statistics") - @EnforceAdmin public ResponseEntity getBuildJobStatistics(@RequestParam(required = false, defaultValue = "7") int span) { log.debug("REST request to get the build job statistics"); List buildJobResultCountDtos = buildJobRepository.getBuildJobsResultsStatistics(ZonedDateTime.now().minusDays(span), null); diff --git a/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminCourseResource.java b/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminCourseResource.java index a75b2e2d4a0a..b83f757209c4 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminCourseResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminCourseResource.java @@ -34,6 +34,7 @@ import de.tum.cit.aet.artemis.core.config.Constants; import de.tum.cit.aet.artemis.core.domain.Course; import de.tum.cit.aet.artemis.core.domain.User; +import de.tum.cit.aet.artemis.core.dto.CourseDeletionSummaryDTO; import de.tum.cit.aet.artemis.core.exception.BadRequestAlertException; import de.tum.cit.aet.artemis.core.repository.CourseRepository; import de.tum.cit.aet.artemis.core.repository.UserRepository; @@ -48,6 +49,7 @@ * REST controller for managing Course. */ @Profile(PROFILE_CORE) +@EnforceAdmin @RestController @RequestMapping("api/admin/") public class AdminCourseResource { @@ -90,7 +92,6 @@ public AdminCourseResource(UserRepository userRepository, CourseService courseSe * @return the list of groups (the user has access to) */ @GetMapping("courses/groups") - @EnforceAdmin public ResponseEntity> getAllGroupsForAllCourses() { log.debug("REST request to get all Groups for all Courses"); List courses = courseRepository.findAll(); @@ -113,7 +114,6 @@ public ResponseEntity> getAllGroupsForAllCourses() { * @throws URISyntaxException if the Location URI syntax is incorrect */ @PostMapping(value = "courses", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - @EnforceAdmin public ResponseEntity createCourse(@RequestPart Course course, @RequestPart(required = false) MultipartFile file) throws URISyntaxException { log.debug("REST request to save Course : {}", course); if (course.getId() != null) { @@ -167,7 +167,6 @@ public ResponseEntity createCourse(@RequestPart Course course, @RequestP * @return the ResponseEntity with status 200 (OK) */ @DeleteMapping("courses/{courseId}") - @EnforceAdmin public ResponseEntity deleteCourse(@PathVariable long courseId) { log.info("REST request to delete Course : {}", courseId); Course course = courseRepository.findByIdWithExercisesAndLecturesAndLectureUnitsAndCompetenciesElseThrow(courseId); @@ -183,6 +182,21 @@ public ResponseEntity deleteCourse(@PathVariable long courseId) { return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert(applicationName, true, Course.ENTITY_NAME, course.getTitle())).build(); } + /** + * GET /courses/:courseId/deletion-summary : get the deletion summary for the course with the given id. + * + * @param courseId the id of the course + * @return the ResponseEntity with status 200 (OK) and the deletion summary in the body + */ + @GetMapping("courses/{courseId}/deletion-summary") + @EnforceAdmin + public ResponseEntity getDeletionSummary(@PathVariable long courseId) { + log.debug("REST request to get deletion summary course: {}", courseId); + final Course course = courseRepository.findByIdWithEagerExercisesElseThrow(courseId); + + return ResponseEntity.ok().body(courseService.getDeletionSummary(course)); + } + /** * Creates a default channel with the given name and adds all students, tutors and instructors as participants. * diff --git a/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminDataExportResource.java b/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminDataExportResource.java index 0e1f17de42c1..6547fd7ef57a 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminDataExportResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminDataExportResource.java @@ -17,6 +17,7 @@ * REST controller for requesting data exports for another user as admin. */ @Profile(PROFILE_CORE) +@EnforceAdmin @RestController @RequestMapping("api/admin/") public class AdminDataExportResource { @@ -34,7 +35,6 @@ public AdminDataExportResource(DataExportService dataExportService) { * @return the ResponseEntity with status 200 (OK) and with body a DTO containing the id, the state and the request date of the data export */ @PostMapping("data-exports/{login}") - @EnforceAdmin public ResponseEntity requestDataExportForUser(@PathVariable String login) { return ResponseEntity.ok(dataExportService.requestDataExportForUserAsAdmin(login)); } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/web/admin/FeatureToggleResource.java b/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminFeatureToggleResource.java similarity index 91% rename from src/main/java/de/tum/cit/aet/artemis/core/web/admin/FeatureToggleResource.java rename to src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminFeatureToggleResource.java index 84a58f7918aa..e2d24720efbd 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/web/admin/FeatureToggleResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminFeatureToggleResource.java @@ -18,13 +18,14 @@ import de.tum.cit.aet.artemis.core.service.feature.FeatureToggleService; @Profile(PROFILE_CORE) +@EnforceAdmin @RestController @RequestMapping("api/admin/") -public class FeatureToggleResource { +public class AdminFeatureToggleResource { private final FeatureToggleService featureToggleService; - public FeatureToggleResource(FeatureToggleService featureToggleService) { + public AdminFeatureToggleResource(FeatureToggleService featureToggleService) { this.featureToggleService = featureToggleService; } @@ -36,7 +37,6 @@ public FeatureToggleResource(FeatureToggleService featureToggleService) { * @see FeatureToggleService */ @PutMapping("feature-toggle") - @EnforceAdmin public ResponseEntity> toggleFeatures(@RequestBody Map features) { featureToggleService.updateFeatureToggles(features); diff --git a/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminImprintResource.java b/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminImprintResource.java index 1e705926232c..58c99393679a 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminImprintResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminImprintResource.java @@ -22,6 +22,7 @@ * REST controller for editing the imprint as an admin. */ @Profile(PROFILE_CORE) +@EnforceAdmin @RestController @RequestMapping("api/admin/") public class AdminImprintResource { @@ -40,7 +41,6 @@ public AdminImprintResource(LegalDocumentService legalDocumentService) { * @return the ResponseEntity with status 200 (OK) and with body the imprint with the given language */ @GetMapping("imprint-for-update") - @EnforceAdmin public ResponseEntity getImprintForUpdate(@RequestParam("language") String language) { if (!Language.isValidShortName(language)) { throw new BadRequestException("Language not supported"); @@ -55,7 +55,6 @@ public ResponseEntity getImprintForUpdate(@RequestParam("language") * @return the ResponseEntity with status 200 (OK) and with body the updated imprint */ @PutMapping("imprint") - @EnforceAdmin public ResponseEntity updateImprint(@RequestBody ImprintDTO imprint) { return ResponseEntity.ok(legalDocumentService.updateImprint(imprint)); } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/web/admin/LogResource.java b/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminLogResource.java similarity index 96% rename from src/main/java/de/tum/cit/aet/artemis/core/web/admin/LogResource.java rename to src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminLogResource.java index 375deea352f6..a0855aa92929 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/web/admin/LogResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminLogResource.java @@ -23,9 +23,10 @@ * Controller for view and managing Log Level at runtime. */ @Profile(PROFILE_CORE) +@EnforceAdmin @RestController @RequestMapping("api/admin/") -public class LogResource { +public class AdminLogResource { /** * GET logs -- Gets the current log levels. @@ -33,7 +34,6 @@ public class LogResource { * @return A list of all loggers with their log level */ @GetMapping("logs") - @EnforceAdmin public ResponseEntity> getList() { LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); return ResponseEntity.ok(context.getLoggerList().stream().map(LoggerVM::new).toList()); @@ -46,7 +46,6 @@ public ResponseEntity> getList() { * @return The updated logger */ @PutMapping("logs") - @EnforceAdmin public ResponseEntity changeLevel(@RequestBody LoggerVM jsonLogger) { LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); Logger logger = context.getLogger(jsonLogger.getName()); diff --git a/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminOrganizationResource.java b/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminOrganizationResource.java index 75d3719b9c6e..c62673b6178f 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminOrganizationResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminOrganizationResource.java @@ -36,6 +36,7 @@ * REST controller for administrating the Organization entities */ @Profile(PROFILE_CORE) +@EnforceAdmin @RestController @RequestMapping("api/admin/") public class AdminOrganizationResource { @@ -72,7 +73,6 @@ public AdminOrganizationResource(OrganizationService organizationService, Organi * @return empty ResponseEntity with status 200 (OK), or 404 (Not Found) otherwise */ @PostMapping("organizations/{organizationId}/courses/{courseId}") - @EnforceAdmin public ResponseEntity addCourseToOrganization(@PathVariable Long courseId, @PathVariable Long organizationId) { log.debug("REST request to add course to organization : {}", organizationId); Organization organization = organizationRepository.findByIdElseThrow(organizationId); @@ -90,7 +90,6 @@ public ResponseEntity addCourseToOrganization(@PathVariable Long courseId, * @return empty ResponseEntity with status 200 (OK), or 404 (Not Found) otherwise */ @DeleteMapping("organizations/{organizationId}/courses/{courseId}") - @EnforceAdmin public ResponseEntity removeCourseFromOrganization(@PathVariable Long courseId, @PathVariable Long organizationId) { Organization organization = organizationRepository.findByIdElseThrow(organizationId); courseRepository.removeOrganizationFromCourse(courseId, organization); @@ -107,7 +106,6 @@ public ResponseEntity removeCourseFromOrganization(@PathVariable Long cour * @return empty ResponseEntity with status 200 (OK), or 404 (Not Found) otherwise */ @PostMapping("organizations/{organizationId}/users/{userLogin}") - @EnforceAdmin public ResponseEntity addUserToOrganization(@PathVariable String userLogin, @PathVariable Long organizationId) { User user = userRepository.getUserByLoginElseThrow(userLogin); Organization organization = organizationRepository.findByIdElseThrow(organizationId); @@ -128,7 +126,6 @@ public ResponseEntity addUserToOrganization(@PathVariable String userLogin * @return empty ResponseEntity with status 200 (OK), or 404 (Not Found) otherwise */ @DeleteMapping("organizations/{organizationId}/users/{userLogin}") - @EnforceAdmin public ResponseEntity removeUserFromOrganization(@PathVariable String userLogin, @PathVariable Long organizationId) { log.debug("REST request to remove course to organization : {}", organizationId); User user = userRepository.getUserByLoginElseThrow(userLogin); @@ -145,7 +142,6 @@ public ResponseEntity removeUserFromOrganization(@PathVariable String user * @return the ResponseEntity containing the added organization with status 200 (OK), or 404 (Not Found) otherwise */ @PostMapping("organizations") - @EnforceAdmin public ResponseEntity addOrganization(@RequestBody Organization organization) { log.debug("REST request to add new organization : {}", organization); Organization created = organizationService.add(organization); @@ -161,7 +157,6 @@ public ResponseEntity addOrganization(@RequestBody Organization or * @return the ResponseEntity containing the updated organization with status 200 (OK), or 404 (Not Found) otherwise */ @PutMapping("organizations/{organizationId}") - @EnforceAdmin public ResponseEntity updateOrganization(@PathVariable Long organizationId, @RequestBody Organization organization) { log.debug("REST request to update organization : {}", organization); if (organization.getId() == null) { @@ -182,7 +177,6 @@ public ResponseEntity updateOrganization(@PathVariable Long organi * @return empty ResponseEntity with status 200 (OK), or 404 (Not Found) otherwise */ @DeleteMapping("organizations/{organizationId}") - @EnforceAdmin public ResponseEntity deleteOrganization(@PathVariable Long organizationId) { log.debug("REST request to delete organization : {}", organizationId); organizationService.deleteOrganization(organizationId); @@ -195,7 +189,6 @@ public ResponseEntity deleteOrganization(@PathVariable Long organizationId * @return ResponseEntity containing a list of all organizations with status 200 (OK) */ @GetMapping("organizations") - @EnforceAdmin public ResponseEntity> getAllOrganizations() { log.debug("REST request to get all organizations"); // TODO: we should avoid findAll() and instead load batches of organizations @@ -210,7 +203,6 @@ public ResponseEntity> getAllOrganizations() { * @return ResponseEntity containing a map containing the numbers of users and courses */ @GetMapping("organizations/{organizationId}/count") - @EnforceAdmin public ResponseEntity getNumberOfUsersAndCoursesByOrganization(@PathVariable long organizationId) { log.debug("REST request to get number of users and courses of organization : {}", organizationId); @@ -227,7 +219,6 @@ public ResponseEntity getNumberOfUsersAndCoursesByOrganiza * containing their relative numbers of users and courses */ @GetMapping("organizations/count-all") - @EnforceAdmin public ResponseEntity> getNumberOfUsersAndCoursesOfAllOrganizations() { log.debug("REST request to get number of users and courses of all organizations"); @@ -250,7 +241,6 @@ public ResponseEntity> getNumberOfUsersAndCoursesOfAl * if exists, else with status 404 (Not Found) */ @GetMapping("organizations/{organizationId}") - @EnforceAdmin public ResponseEntity getOrganizationById(@PathVariable long organizationId) { log.debug("REST request to get organization : {}", organizationId); Organization organization = organizationRepository.findByIdElseThrow(organizationId); @@ -265,7 +255,6 @@ public ResponseEntity getOrganizationById(@PathVariable long organ * if exists, else with status 404 (Not Found) */ @GetMapping("organizations/{organizationId}/full") - @EnforceAdmin public ResponseEntity getOrganizationByIdWithUsersAndCourses(@PathVariable long organizationId) { log.debug("REST request to get organization with users and courses : {}", organizationId); Organization organization = organizationRepository.findByIdWithEagerUsersAndCoursesElseThrow(organizationId); @@ -279,7 +268,6 @@ public ResponseEntity getOrganizationByIdWithUsersAndCourses(@Path * @return ResponseEntity containing a set of organizations containing the given user */ @GetMapping("organizations/users/{userId}") - @EnforceAdmin public ResponseEntity> getAllOrganizationsByUser(@PathVariable Long userId) { log.debug("REST request to get all organizations of user : {}", userId); Set organizations = organizationRepository.findAllOrganizationsByUserId(userId); @@ -293,7 +281,6 @@ public ResponseEntity> getAllOrganizationsByUser(@PathVariable * @return the title of the organization wrapped in an ResponseEntity or 404 Not Found if no organization with that id exists */ @GetMapping("organizations/{organizationId}/title") - @EnforceAdmin public ResponseEntity getOrganizationTitle(@PathVariable Long organizationId) { final var title = organizationRepository.getOrganizationTitle(organizationId); return title == null ? ResponseEntity.notFound().build() : ResponseEntity.ok(title); diff --git a/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminPrivacyStatementResource.java b/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminPrivacyStatementResource.java index af48dc8f3565..736d926218ab 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminPrivacyStatementResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminPrivacyStatementResource.java @@ -22,6 +22,7 @@ * REST controller for editing the Privacy Statement as an admin. */ @Profile(PROFILE_CORE) +@EnforceAdmin @RestController @RequestMapping("api/admin/") public class AdminPrivacyStatementResource { @@ -39,7 +40,6 @@ public AdminPrivacyStatementResource(LegalDocumentService legalDocumentService) * @param language the language of the privacy statement * @return the ResponseEntity with status 200 (OK) and with body the privacy statement */ - @EnforceAdmin @GetMapping("privacy-statement-for-update") public ResponseEntity getPrivacyStatementForUpdate(@RequestParam("language") String language) { if (!Language.isValidShortName(language)) { @@ -54,7 +54,6 @@ public ResponseEntity getPrivacyStatementForUpdate(@Request * @param privacyStatement the privacy statement to update * @return the ResponseEntity with status 200 (OK) and with body the updated privacy statement */ - @EnforceAdmin @PutMapping("privacy-statement") public ResponseEntity updatePrivacyStatement(@RequestBody PrivacyStatementDTO privacyStatement) { return ResponseEntity.ok(legalDocumentService.updatePrivacyStatement(privacyStatement)); diff --git a/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminStatisticsResource.java b/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminStatisticsResource.java index 9c6db4cc7616..c91d7df176c3 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminStatisticsResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminStatisticsResource.java @@ -23,6 +23,7 @@ * REST controller for administrating statistics. */ @Profile(PROFILE_CORE) +@EnforceAdmin @RestController @RequestMapping("api/admin/") public class AdminStatisticsResource { @@ -44,7 +45,6 @@ public AdminStatisticsResource(StatisticsService statisticsService) { * @return the ResponseEntity with status 200 (OK) and the data in body, or status 404 (Not Found) */ @GetMapping("management/statistics/data") - @EnforceAdmin public ResponseEntity> getChartData(@RequestParam SpanType span, @RequestParam Integer periodIndex, @RequestParam GraphType graphType) { log.debug("REST request to get graph data"); return ResponseEntity.ok(this.statisticsService.getChartData(span, periodIndex, graphType, StatisticsView.ARTEMIS, null)); diff --git a/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminUserResource.java b/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminUserResource.java index 289d296c5624..b2fb3b4fadd2 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminUserResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminUserResource.java @@ -69,6 +69,7 @@ * Another option would be to have a specific JPA entity graph to handle this case. */ @Profile(PROFILE_CORE) +@EnforceAdmin @RestController @RequestMapping("api/admin/") public class AdminUserResource { @@ -108,7 +109,6 @@ public AdminUserResource(UserRepository userRepository, UserService userService, * @throws BadRequestAlertException 400 (Bad Request) if the login or email is already in use */ @PostMapping("users") - @EnforceAdmin public ResponseEntity createUser(@Valid @RequestBody ManagedUserVM managedUserVM) throws URISyntaxException { this.userService.checkUsernameAndPasswordValidityElseThrow(managedUserVM.getLogin(), managedUserVM.getPassword()); @@ -144,7 +144,6 @@ else if (userRepository.findOneByEmailIgnoreCase(managedUserVM.getEmail()).isPre * @throws LoginAlreadyUsedException 400 (Bad Request) if the login is already in use */ @PutMapping("users") - @EnforceAdmin public ResponseEntity updateUser(@Valid @RequestBody ManagedUserVM managedUserVM) { this.userService.checkUsernameAndPasswordValidityElseThrow(managedUserVM.getLogin(), managedUserVM.getPassword()); log.debug("REST request to update User : {}", managedUserVM); @@ -181,7 +180,6 @@ public ResponseEntity updateUser(@Valid @RequestBody ManagedUserVM mana * @return the ResponseEntity with status 200 (OK) and with body the "login" user, or with status 404 (Not Found) */ @GetMapping("users/{login:" + Constants.LOGIN_REGEX + "}") - @EnforceAdmin public ResponseEntity getUser(@PathVariable String login) { log.debug("REST request to get User : {}", login); return ResponseUtil.wrapOrNotFound(userRepository.findOneWithGroupsAndAuthoritiesByLogin(login).map(user -> { @@ -201,7 +199,6 @@ public ResponseEntity getUser(@PathVariable String login) { * @return the list of users who could not be imported, because they could NOT be found in the Artemis database and could NOT be found in the connected LDAP */ @PostMapping("users/import") - @EnforceAdmin public ResponseEntity> importUsers(@RequestBody List userDtos) { log.debug("REST request to import {} to Artemis", userDtos); List notFoundStudentsDtos = userService.importUsers(userDtos); @@ -215,7 +212,6 @@ public ResponseEntity> importUsers(@RequestBody List syncUserViaLdap(@PathVariable Long userId) { log.debug("REST request to update ldap information User : {}", userId); @@ -235,7 +231,6 @@ public ResponseEntity syncUserViaLdap(@PathVariable Long userId) { * @return the ResponseEntity with status 200 (OK) and with body all users */ @GetMapping("users") - @EnforceAdmin public ResponseEntity> getAllUsers(UserPageableSearchDTO userSearch) { final Page page = userRepository.getAllManagedUsers(userSearch); HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page); @@ -248,7 +243,6 @@ public ResponseEntity> getAllUsers(UserPageableSearchDTO userSearc * @return the ResponseEntity with status 200 (OK) and with body all logins of not enrolled users */ @GetMapping("users/not-enrolled") - @EnforceAdmin public ResponseEntity> getNotEnrolledUsers() { List logins = userRepository.findAllNotEnrolledUsers(); return new ResponseEntity<>(logins, HttpStatus.OK); @@ -260,7 +254,6 @@ public ResponseEntity> getNotEnrolledUsers() { * @return the ResponseEntity with status 200 (OK) and with body a string list of the all the roles */ @GetMapping("users/authorities") - @EnforceAdmin public ResponseEntity> getAuthorities() { return ResponseEntity.ok(authorityRepository.getAuthorities()); } @@ -272,7 +265,6 @@ public ResponseEntity> getAuthorities() { * @return the ResponseEntity with status 200 (OK) */ @DeleteMapping("users/{login:" + Constants.LOGIN_REGEX + "}") - @EnforceAdmin public ResponseEntity deleteUser(@PathVariable String login) { log.debug("REST request to delete User: {}", login); if (userRepository.isCurrentUser(login)) { @@ -289,7 +281,6 @@ public ResponseEntity deleteUser(@PathVariable String login) { * @return the ResponseEntity with status 200 (OK) */ @DeleteMapping("users") - @EnforceAdmin public ResponseEntity> deleteUsers(@RequestParam(name = "login") List logins) { log.debug("REST request to delete {} users", logins.size()); List deletedUsers = Collections.synchronizedList(new java.util.ArrayList<>()); diff --git a/src/main/java/de/tum/cit/aet/artemis/core/web/open/PublicAccountResource.java b/src/main/java/de/tum/cit/aet/artemis/core/web/open/PublicAccountResource.java index 992b340359a1..bd673ece51d4 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/web/open/PublicAccountResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/web/open/PublicAccountResource.java @@ -167,6 +167,7 @@ public ResponseEntity getAccount() { userDTO.setVcsAccessToken(user.getVcsAccessToken()); userDTO.setVcsAccessTokenExpiryDate(user.getVcsAccessTokenExpiryDate()); userDTO.setSshPublicKey(user.getSshPublicKey()); + userDTO.setSshKeyHash(user.getSshPublicKeyHash()); log.info("GET /account {} took {}ms", user.getLogin(), System.currentTimeMillis() - start); return ResponseEntity.ok(userDTO); } diff --git a/src/main/java/de/tum/cit/aet/artemis/exam/dto/ExamDeletionSummaryDTO.java b/src/main/java/de/tum/cit/aet/artemis/exam/dto/ExamDeletionSummaryDTO.java new file mode 100644 index 000000000000..c0cd6608ec2c --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/exam/dto/ExamDeletionSummaryDTO.java @@ -0,0 +1,8 @@ +package de.tum.cit.aet.artemis.exam.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public record ExamDeletionSummaryDTO(long numberOfBuilds, long numberOfCommunicationPosts, long numberOfAnswerPosts, long numberRegisteredStudents, long numberNotStartedExams, + long numberStartedExams, long numberSubmittedExams) { +} diff --git a/src/main/java/de/tum/cit/aet/artemis/exam/repository/StudentExamRepository.java b/src/main/java/de/tum/cit/aet/artemis/exam/repository/StudentExamRepository.java index 2236d4d2035c..4d3bcfe61369 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exam/repository/StudentExamRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/exam/repository/StudentExamRepository.java @@ -245,6 +245,10 @@ SELECT MAX(se.workingTime) """) Set findAllUnsubmittedWithExercisesByExamId(@Param("examId") Long examId); + List findAllByExamId(Long examId); + + List findAllByExamId_AndTestRunIsTrue(Long examId); + @Query(""" SELECT DISTINCT se FROM StudentExam se diff --git a/src/main/java/de/tum/cit/aet/artemis/exam/service/ExamDeletionService.java b/src/main/java/de/tum/cit/aet/artemis/exam/service/ExamDeletionService.java index a8fd46512528..1c05e1d4be65 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exam/service/ExamDeletionService.java +++ b/src/main/java/de/tum/cit/aet/artemis/exam/service/ExamDeletionService.java @@ -20,7 +20,10 @@ import de.tum.cit.aet.artemis.assessment.domain.GradingScale; import de.tum.cit.aet.artemis.assessment.repository.GradingScaleRepository; +import de.tum.cit.aet.artemis.communication.domain.Post; import de.tum.cit.aet.artemis.communication.domain.conversation.Channel; +import de.tum.cit.aet.artemis.communication.repository.AnswerPostRepository; +import de.tum.cit.aet.artemis.communication.repository.PostRepository; import de.tum.cit.aet.artemis.communication.repository.conversation.ChannelRepository; import de.tum.cit.aet.artemis.communication.service.conversation.ChannelService; import de.tum.cit.aet.artemis.core.config.Constants; @@ -29,13 +32,16 @@ import de.tum.cit.aet.artemis.exam.domain.Exam; import de.tum.cit.aet.artemis.exam.domain.ExerciseGroup; import de.tum.cit.aet.artemis.exam.domain.StudentExam; +import de.tum.cit.aet.artemis.exam.dto.ExamDeletionSummaryDTO; import de.tum.cit.aet.artemis.exam.repository.ExamLiveEventRepository; import de.tum.cit.aet.artemis.exam.repository.ExamRepository; import de.tum.cit.aet.artemis.exam.repository.StudentExamRepository; import de.tum.cit.aet.artemis.exercise.domain.Exercise; +import de.tum.cit.aet.artemis.exercise.domain.ExerciseType; import de.tum.cit.aet.artemis.exercise.repository.StudentParticipationRepository; import de.tum.cit.aet.artemis.exercise.service.ExerciseDeletionService; import de.tum.cit.aet.artemis.exercise.service.ParticipationService; +import de.tum.cit.aet.artemis.programming.repository.BuildJobRepository; import de.tum.cit.aet.artemis.quiz.domain.QuizPool; import de.tum.cit.aet.artemis.quiz.repository.QuizPoolRepository; @@ -71,10 +77,17 @@ public class ExamDeletionService { private final QuizPoolRepository quizPoolRepository; + private final BuildJobRepository buildJobRepository; + + private final PostRepository postRepository; + + private final AnswerPostRepository answerPostRepository; + public ExamDeletionService(ExerciseDeletionService exerciseDeletionService, ParticipationService participationService, CacheManager cacheManager, UserRepository userRepository, ExamRepository examRepository, AuditEventRepository auditEventRepository, StudentExamRepository studentExamRepository, GradingScaleRepository gradingScaleRepository, StudentParticipationRepository studentParticipationRepository, ChannelRepository channelRepository, ChannelService channelService, - ExamLiveEventRepository examLiveEventRepository, QuizPoolRepository quizPoolRepository) { + ExamLiveEventRepository examLiveEventRepository, QuizPoolRepository quizPoolRepository, BuildJobRepository buildJobRepository, PostRepository postRepository, + AnswerPostRepository answerPostRepository) { this.exerciseDeletionService = exerciseDeletionService; this.participationService = participationService; this.cacheManager = cacheManager; @@ -88,6 +101,9 @@ public ExamDeletionService(ExerciseDeletionService exerciseDeletionService, Part this.channelService = channelService; this.examLiveEventRepository = examLiveEventRepository; this.quizPoolRepository = quizPoolRepository; + this.buildJobRepository = buildJobRepository; + this.postRepository = postRepository; + this.answerPostRepository = answerPostRepository; } /** @@ -240,4 +256,34 @@ public void deleteTestRun(Long testRunId) { log.info("Request to delete Test Run {}", testRunId); studentExamRepository.deleteById(testRunId); } + + /** + * Get the exam deletion summary for the given exam. + * + * @param examId the ID of the exam for which the deletion summary should be fetched + * @return the exam deletion summary + */ + public ExamDeletionSummaryDTO getExamDeletionSummary(@NotNull long examId) { + Exam exam = examRepository.findOneWithEagerExercisesGroupsAndStudentExams(examId); + long numberOfBuilds = exam.getExerciseGroups().stream().flatMap(group -> group.getExercises().stream()) + .filter(exercise -> ExerciseType.PROGRAMMING.equals(exercise.getExerciseType())) + .mapToLong(exercise -> buildJobRepository.countBuildJobsByExerciseIds(List.of(exercise.getId()))).sum(); + + Channel channel = channelRepository.findChannelByExamId(examId); + Long conversationId = channel.getId(); + + List postIds = postRepository.findAllByConversationId(conversationId).stream().map(Post::getId).toList(); + long numberOfCommunicationPosts = postIds.size(); + long numberOfAnswerPosts = answerPostRepository.countAnswerPostsByPostIdIn(postIds); + + Set studentExams = exam.getStudentExams(); + long numberRegisteredStudents = studentExams.size(); + + // Boolean.TRUE/Boolean.FALSE are used to handle the case where isStarted/isSubmitted is null + long notStartedExams = studentExams.stream().filter(studentExam -> studentExam.isStarted() == null || !studentExam.isStarted()).count(); + long startedExams = studentExams.stream().filter(studentExam -> Boolean.TRUE.equals(studentExam.isStarted())).count(); + long submittedExams = studentExams.stream().filter(studentExam -> Boolean.TRUE.equals(studentExam.isStarted()) && Boolean.TRUE.equals(studentExam.isSubmitted())).count(); + + return new ExamDeletionSummaryDTO(numberOfBuilds, numberOfCommunicationPosts, numberOfAnswerPosts, numberRegisteredStudents, notStartedExams, startedExams, submittedExams); + } } diff --git a/src/main/java/de/tum/cit/aet/artemis/exam/web/ExamResource.java b/src/main/java/de/tum/cit/aet/artemis/exam/web/ExamResource.java index 209f2a4fa040..8223ba8e54a9 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exam/web/ExamResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/exam/web/ExamResource.java @@ -77,6 +77,7 @@ import de.tum.cit.aet.artemis.core.security.annotations.EnforceAtLeastInstructor; import de.tum.cit.aet.artemis.core.security.annotations.EnforceAtLeastStudent; import de.tum.cit.aet.artemis.core.security.annotations.EnforceAtLeastTutor; +import de.tum.cit.aet.artemis.core.security.annotations.enforceRoleInCourse.EnforceAtLeastInstructorInCourse; import de.tum.cit.aet.artemis.core.service.AuthorizationCheckService; import de.tum.cit.aet.artemis.core.service.feature.Feature; import de.tum.cit.aet.artemis.core.service.feature.FeatureToggle; @@ -88,6 +89,7 @@ import de.tum.cit.aet.artemis.exam.domain.StudentExam; import de.tum.cit.aet.artemis.exam.domain.SuspiciousSessionsAnalysisOptions; import de.tum.cit.aet.artemis.exam.dto.ExamChecklistDTO; +import de.tum.cit.aet.artemis.exam.dto.ExamDeletionSummaryDTO; import de.tum.cit.aet.artemis.exam.dto.ExamInformationDTO; import de.tum.cit.aet.artemis.exam.dto.ExamScoresDTO; import de.tum.cit.aet.artemis.exam.dto.ExamUserDTO; @@ -1320,4 +1322,19 @@ public ResponseEntity> getAllSuspiciousExamSessio analyzeSessionsIpOutsideOfRange); return ResponseEntity.ok(examSessionService.retrieveAllSuspiciousExamSessionsByExamId(examId, options, Optional.ofNullable(ipSubnet))); } + + /** + * GET /courses/{courseId}/exams/{examId}/deletion-summary : Get a summary of the deletion of an exam. + * + * @param courseId the id of the course + * @param examId the id of the exam + * + * @return the ResponseEntity with status 200 (OK) and with body a summary of the deletion of the exam + */ + @GetMapping("courses/{courseId}/exams/{examId}/deletion-summary") + @EnforceAtLeastInstructorInCourse + public ResponseEntity getDeletionSummary(@PathVariable long courseId, @PathVariable long examId) { + log.debug("REST request to get deletion summary for exam : {}", examId); + return ResponseEntity.ok(examDeletionService.getExamDeletionSummary(examId)); + } } diff --git a/src/main/java/de/tum/cit/aet/artemis/exam/web/admin/AdminExamResource.java b/src/main/java/de/tum/cit/aet/artemis/exam/web/admin/AdminExamResource.java index add9135f8c54..d1f70035e858 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exam/web/admin/AdminExamResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/exam/web/admin/AdminExamResource.java @@ -20,6 +20,7 @@ * REST controller for administrating Exam. */ @Profile(PROFILE_CORE) +@EnforceAdmin @RestController @RequestMapping("api/admin/") public class AdminExamResource { @@ -38,7 +39,6 @@ public AdminExamResource(ExamRepository examRepository) { * @return the ResponseEntity with status 200 (OK) and a list of exams. */ @GetMapping("courses/upcoming-exams") - @EnforceAdmin public ResponseEntity> getCurrentAndUpcomingExams() { log.debug("REST request to get all upcoming exams"); diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/web/admin/AdminExerciseResource.java b/src/main/java/de/tum/cit/aet/artemis/exercise/web/admin/AdminExerciseResource.java index 306ad29ddd4f..76546cbe6289 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/web/admin/AdminExerciseResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/web/admin/AdminExerciseResource.java @@ -20,6 +20,7 @@ * REST controller for administrating Exercise. */ @Profile(PROFILE_CORE) +@EnforceAdmin @RestController @RequestMapping("api/admin/") public class AdminExerciseResource { @@ -38,7 +39,6 @@ public AdminExerciseResource(ExerciseRepository exerciseRepository) { * @return the ResponseEntity with status 200 (OK) and a list of exercises. */ @GetMapping("exercises/upcoming") - @EnforceAdmin public ResponseEntity> getUpcomingExercises() { log.debug("REST request to get all upcoming exercises"); Set upcomingExercises = exerciseRepository.findAllExercisesWithCurrentOrUpcomingDueDate(); diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/domain/IrisTemplate.java b/src/main/java/de/tum/cit/aet/artemis/iris/domain/IrisTemplate.java deleted file mode 100644 index e1b486a34cbf..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/iris/domain/IrisTemplate.java +++ /dev/null @@ -1,65 +0,0 @@ -package de.tum.cit.aet.artemis.iris.domain; - -import java.util.Objects; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Table; - -import org.hibernate.annotations.Cache; -import org.hibernate.annotations.CacheConcurrencyStrategy; - -import com.fasterxml.jackson.annotation.JsonInclude; - -import de.tum.cit.aet.artemis.core.domain.DomainObject; - -/** - * An IrisTemplate represents a handlebars template for Iris. - * It is sent to the Iris Python server to generate a response. - */ -@Entity -@Table(name = "iris_template") -@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) -@JsonInclude(JsonInclude.Include.NON_EMPTY) -public class IrisTemplate extends DomainObject { - - @Column(name = "content", columnDefinition = "LONGTEXT") - private String content; - - /** - * Empty constructor required for Hibernate and Jackson. - */ - public IrisTemplate() { - } - - /** - * Create a new IrisTemplate with content. - * - * @param content the content of the template - */ - public IrisTemplate(String content) { - this.content = content; - } - - public String getContent() { - return content; - } - - public void setContent(String template) { - this.content = template; - } - - @Override - public boolean equals(Object other) { - if (!super.equals(other)) { - return false; - } - IrisTemplate template = (IrisTemplate) other; - return Objects.equals(content, template.content); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), content); - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/domain/session/IrisHestiaSession.java b/src/main/java/de/tum/cit/aet/artemis/iris/domain/session/IrisHestiaSession.java deleted file mode 100644 index f23603711cf5..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/iris/domain/session/IrisHestiaSession.java +++ /dev/null @@ -1,37 +0,0 @@ -package de.tum.cit.aet.artemis.iris.domain.session; - -import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.Entity; -import jakarta.persistence.ManyToOne; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonInclude; - -import de.tum.cit.aet.artemis.programming.domain.hestia.CodeHint; - -/** - * An Iris session for a hestia code hint. - * Currently used to generate descriptions for code hints. - */ -@Entity -@DiscriminatorValue("HESTIA") -@JsonInclude(JsonInclude.Include.NON_EMPTY) -public class IrisHestiaSession extends IrisSession { - - @ManyToOne - @JsonIgnore - private CodeHint codeHint; - - public CodeHint getCodeHint() { - return codeHint; - } - - public void setCodeHint(CodeHint codeHint) { - this.codeHint = codeHint; - } - - @Override - public String toString() { - return "IrisHestiaSession{" + "id=" + getId() + ", codeHint=" + (codeHint == null ? "null" : codeHint.getId()) + '}'; - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/domain/session/IrisSession.java b/src/main/java/de/tum/cit/aet/artemis/iris/domain/session/IrisSession.java index 3e6240a383de..13a4bd6f8b2b 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/domain/session/IrisSession.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/domain/session/IrisSession.java @@ -28,7 +28,7 @@ /** * An IrisSession represents a list of messages of Artemis, a user, and an LLM. - * See {@link IrisExerciseChatSession} and {@link IrisHestiaSession} for concrete implementations. + * See {@link IrisExerciseChatSession} and {@link IrisCourseChatSession} for concrete implementations. */ @Entity @Table(name = "iris_session") @@ -40,7 +40,6 @@ @JsonSubTypes({ @JsonSubTypes.Type(value = IrisExerciseChatSession.class, name = "chat"), // TODO: Legacy. Should ideally be "exercise_chat" @JsonSubTypes.Type(value = IrisCourseChatSession.class, name = "course_chat"), - @JsonSubTypes.Type(value = IrisHestiaSession.class, name = "hestia"), }) // @formatter:on @JsonInclude(JsonInclude.Include.NON_EMPTY) diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisChatSubSettings.java b/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisChatSubSettings.java index 4305461b71cf..bf2851ae7979 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisChatSubSettings.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisChatSubSettings.java @@ -1,31 +1,21 @@ package de.tum.cit.aet.artemis.iris.domain.settings; import jakarta.annotation.Nullable; -import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.OneToOne; import com.fasterxml.jackson.annotation.JsonInclude; -import de.tum.cit.aet.artemis.iris.domain.IrisTemplate; - /** * An {@link IrisSubSettings} implementation for chat settings. * Chat settings notably provide settings for the rate limit. - * Chat settings provide a single {@link IrisTemplate} for the chat messages. */ @Entity @DiscriminatorValue("CHAT") @JsonInclude(JsonInclude.Include.NON_EMPTY) public class IrisChatSubSettings extends IrisSubSettings { - @Nullable - @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) - private IrisTemplate template; - @Nullable @Column(name = "rate_limit") private Integer rateLimit; @@ -34,15 +24,6 @@ public class IrisChatSubSettings extends IrisSubSettings { @Column(name = "rate_limit_timeframe_hours") private Integer rateLimitTimeframeHours; - @Nullable - public IrisTemplate getTemplate() { - return template; - } - - public void setTemplate(@Nullable IrisTemplate template) { - this.template = template; - } - @Nullable public Integer getRateLimit() { return rateLimit; diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisCompetencyGenerationSubSettings.java b/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisCompetencyGenerationSubSettings.java index f68ae30d4b53..b8447b1bb378 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisCompetencyGenerationSubSettings.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisCompetencyGenerationSubSettings.java @@ -1,36 +1,16 @@ package de.tum.cit.aet.artemis.iris.domain.settings; -import jakarta.annotation.Nullable; -import jakarta.persistence.CascadeType; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.OneToOne; import com.fasterxml.jackson.annotation.JsonInclude; -import de.tum.cit.aet.artemis.iris.domain.IrisTemplate; - /** * An {@link IrisSubSettings} implementation for the settings for competency generation. - * CompetencyGeneration settings provide a single {@link IrisTemplate} */ @Entity @DiscriminatorValue("COMPETENCY_GENERATION") @JsonInclude(JsonInclude.Include.NON_EMPTY) public class IrisCompetencyGenerationSubSettings extends IrisSubSettings { - @Nullable - @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) - private IrisTemplate template; - - @Nullable - public IrisTemplate getTemplate() { - return template; - } - - public void setTemplate(@Nullable IrisTemplate template) { - this.template = template; - } - } diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisCourseSettings.java b/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisCourseSettings.java index 2354ffd3c142..ca9d8723781f 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisCourseSettings.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisCourseSettings.java @@ -32,19 +32,10 @@ public class IrisCourseSettings extends IrisSettings { @JoinColumn(name = "iris_lecture_ingestion_settings_id") private IrisLectureIngestionSubSettings irisLectureIngestionSettings; - @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) - @JoinColumn(name = "iris_hestia_settings_id") - private IrisHestiaSubSettings irisHestiaSettings; - @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, optional = false) @JoinColumn(name = "iris_competency_generation_settings_id") private IrisCompetencyGenerationSubSettings irisCompetencyGenerationSettings; - @Override - public boolean isValid() { - return course != null; - } - public Course getCourse() { return course; } @@ -73,16 +64,6 @@ public void setIrisChatSettings(IrisChatSubSettings irisChatSettings) { this.irisChatSettings = irisChatSettings; } - @Override - public IrisHestiaSubSettings getIrisHestiaSettings() { - return irisHestiaSettings; - } - - @Override - public void setIrisHestiaSettings(IrisHestiaSubSettings irisHestiaSettings) { - this.irisHestiaSettings = irisHestiaSettings; - } - @Override public IrisCompetencyGenerationSubSettings getIrisCompetencyGenerationSettings() { return irisCompetencyGenerationSettings; diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisExerciseSettings.java b/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisExerciseSettings.java index 410bbde1954c..fbfede75714c 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisExerciseSettings.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisExerciseSettings.java @@ -28,11 +28,6 @@ public class IrisExerciseSettings extends IrisSettings { @JoinColumn(name = "iris_chat_settings_id") private IrisChatSubSettings irisChatSettings; - @Override - public boolean isValid() { - return exercise != null; - } - public Exercise getExercise() { return exercise; } @@ -60,16 +55,6 @@ public void setIrisChatSettings(IrisChatSubSettings irisChatSettings) { this.irisChatSettings = irisChatSettings; } - @Override - public IrisHestiaSubSettings getIrisHestiaSettings() { - return null; - } - - @Override - public void setIrisHestiaSettings(IrisHestiaSubSettings irisHestiaSettings) { - - } - @Override public IrisCompetencyGenerationSubSettings getIrisCompetencyGenerationSettings() { return null; diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisGlobalSettings.java b/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisGlobalSettings.java index 0ae60c36edd8..587a85b37b22 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisGlobalSettings.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisGlobalSettings.java @@ -1,15 +1,12 @@ package de.tum.cit.aet.artemis.iris.domain.settings; import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToOne; -import org.hibernate.Hibernate; - import com.fasterxml.jackson.annotation.JsonInclude; /** @@ -22,21 +19,6 @@ @JsonInclude(JsonInclude.Include.NON_EMPTY) public class IrisGlobalSettings extends IrisSettings { - @Column(name = "current_version") - private int currentVersion; - - @Column(name = "enable_auto_update_chat") - private boolean enableAutoUpdateChat; - - @Column(name = "enable_auto_update_hestia") - private boolean enableAutoUpdateHestia; - - @Column(name = "enable_auto_update_lecture_ingestion") - private boolean enableAutoUpdateLectureIngestion; - - @Column(name = "enable_auto_update_competency_generation") - private boolean enableAutoUpdateCompetencyGeneration; - @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, optional = false) @JoinColumn(name = "iris_chat_settings_id") private IrisChatSubSettings irisChatSettings; @@ -45,66 +27,10 @@ public class IrisGlobalSettings extends IrisSettings { @JoinColumn(name = "iris_lecture_ingestion_settings_id") private IrisLectureIngestionSubSettings irisLectureIngestionSettings; - @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, optional = false) - @JoinColumn(name = "iris_hestia_settings_id") - private IrisHestiaSubSettings irisHestiaSettings; - @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, optional = false) @JoinColumn(name = "iris_competency_generation_settings_id") private IrisCompetencyGenerationSubSettings irisCompetencyGenerationSettings; - @Override - public boolean isValid() { - var chatSettingsValid = !Hibernate.isInitialized(irisChatSettings) || irisChatSettings == null - || (irisChatSettings.getTemplate() != null && irisChatSettings.getTemplate().getContent() != null && !irisChatSettings.getTemplate().getContent().isEmpty()); - var hestiaSettingsValid = !Hibernate.isInitialized(irisHestiaSettings) || irisHestiaSettings == null - || (irisHestiaSettings.getTemplate() != null && irisHestiaSettings.getTemplate().getContent() != null && !irisHestiaSettings.getTemplate().getContent().isEmpty()); - var competencyGenerationSettingsValid = !Hibernate.isInitialized(irisCompetencyGenerationSettings) || irisCompetencyGenerationSettings == null - || (irisCompetencyGenerationSettings.getTemplate() != null && irisCompetencyGenerationSettings.getTemplate().getContent() != null - && !irisCompetencyGenerationSettings.getTemplate().getContent().isEmpty()); - return chatSettingsValid && hestiaSettingsValid && competencyGenerationSettingsValid; - } - - public int getCurrentVersion() { - return currentVersion; - } - - public void setCurrentVersion(int currentVersion) { - this.currentVersion = currentVersion; - } - - public boolean isEnableAutoUpdateChat() { - return enableAutoUpdateChat; - } - - public void setEnableAutoUpdateChat(boolean enableAutoUpdateChat) { - this.enableAutoUpdateChat = enableAutoUpdateChat; - } - - public boolean isEnableAutoUpdateLectureIngestion() { - return enableAutoUpdateLectureIngestion; - } - - public void setEnableAutoUpdateLectureIngestion(boolean enableAutoUpdateLectureIngestion) { - this.enableAutoUpdateLectureIngestion = enableAutoUpdateLectureIngestion; - } - - public boolean isEnableAutoUpdateHestia() { - return enableAutoUpdateHestia; - } - - public void setEnableAutoUpdateHestia(boolean enableAutoUpdateHestia) { - this.enableAutoUpdateHestia = enableAutoUpdateHestia; - } - - public boolean isEnableAutoUpdateCompetencyGeneration() { - return enableAutoUpdateCompetencyGeneration; - } - - public void setEnableAutoUpdateCompetencyGeneration(boolean enableAutoUpdateCompetencyGeneration) { - this.enableAutoUpdateCompetencyGeneration = enableAutoUpdateCompetencyGeneration; - } - @Override public IrisLectureIngestionSubSettings getIrisLectureIngestionSettings() { return irisLectureIngestionSettings; @@ -125,16 +51,6 @@ public void setIrisChatSettings(IrisChatSubSettings irisChatSettings) { this.irisChatSettings = irisChatSettings; } - @Override - public IrisHestiaSubSettings getIrisHestiaSettings() { - return irisHestiaSettings; - } - - @Override - public void setIrisHestiaSettings(IrisHestiaSubSettings irisHestiaSettings) { - this.irisHestiaSettings = irisHestiaSettings; - } - @Override public IrisCompetencyGenerationSubSettings getIrisCompetencyGenerationSettings() { return irisCompetencyGenerationSettings; diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisHestiaSubSettings.java b/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisHestiaSubSettings.java deleted file mode 100644 index 1c478a8ccfbe..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisHestiaSubSettings.java +++ /dev/null @@ -1,35 +0,0 @@ -package de.tum.cit.aet.artemis.iris.domain.settings; - -import jakarta.annotation.Nullable; -import jakarta.persistence.CascadeType; -import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.OneToOne; - -import com.fasterxml.jackson.annotation.JsonInclude; - -import de.tum.cit.aet.artemis.iris.domain.IrisTemplate; - -/** - * An {@link IrisSubSettings} implementation for the Hestia integration settings. - * Hestia settings provide a single {@link IrisTemplate} for the hestia code hint generation requests. - */ -@Entity -@DiscriminatorValue("HESTIA") -@JsonInclude(JsonInclude.Include.NON_EMPTY) -public class IrisHestiaSubSettings extends IrisSubSettings { - - @Nullable - @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) - private IrisTemplate template; - - @Nullable - public IrisTemplate getTemplate() { - return template; - } - - public void setTemplate(@Nullable IrisTemplate template) { - this.template = template; - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisModelListConverter.java b/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisListConverter.java similarity index 88% rename from src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisModelListConverter.java rename to src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisListConverter.java index 938ce5dae0c7..be4cf5199a0b 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisModelListConverter.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisListConverter.java @@ -9,7 +9,7 @@ import jakarta.persistence.Converter; @Converter -public class IrisModelListConverter implements AttributeConverter, String> { +public class IrisListConverter implements AttributeConverter, String> { @Override public String convertToDatabaseColumn(SortedSet type) { diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisSettings.java b/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisSettings.java index 5ca715a2f688..5efb75e76ea0 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisSettings.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisSettings.java @@ -49,13 +49,7 @@ public abstract class IrisSettings extends DomainObject { public abstract void setIrisLectureIngestionSettings(IrisLectureIngestionSubSettings irisLectureIngestionSettings); - public abstract IrisHestiaSubSettings getIrisHestiaSettings(); - - public abstract void setIrisHestiaSettings(IrisHestiaSubSettings irisHestiaSettings); - public abstract IrisCompetencyGenerationSubSettings getIrisCompetencyGenerationSettings(); public abstract void setIrisCompetencyGenerationSettings(IrisCompetencyGenerationSubSettings irisCompetencyGenerationSubSettings); - - public abstract boolean isValid(); } diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisSubSettings.java b/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisSubSettings.java index 16588cf448a5..5288c247e891 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisSubSettings.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisSubSettings.java @@ -26,7 +26,6 @@ * IrisSubSettings is an abstract super class for the specific sub settings types. * Sub Settings are settings for a specific feature of Iris. * {@link IrisChatSubSettings} are used to specify settings for the chat feature. - * {@link IrisHestiaSubSettings} are used to specify settings for the Hestia integration. * {@link IrisCompetencyGenerationSubSettings} are used to specify settings for the competency generation feature. *

    * Also see {@link de.tum.cit.aet.artemis.iris.service.settings.IrisSettingsService} for more information. @@ -41,7 +40,6 @@ @JsonSubTypes({ @JsonSubTypes.Type(value = IrisChatSubSettings.class, name = "chat"), @JsonSubTypes.Type(value = IrisLectureIngestionSubSettings.class, name = "lecture-ingestion"), - @JsonSubTypes.Type(value = IrisHestiaSubSettings.class, name = "hestia"), @JsonSubTypes.Type(value = IrisCompetencyGenerationSubSettings.class, name = "competency-generation") }) // @formatter:on @@ -51,13 +49,12 @@ public abstract class IrisSubSettings extends DomainObject { @Column(name = "enabled") private boolean enabled = false; - @Column(name = "allowed_models") - @Convert(converter = IrisModelListConverter.class) - private SortedSet allowedModels = new TreeSet<>(); + @Column(name = "allowed_variants", nullable = false) + @Convert(converter = IrisListConverter.class) + private SortedSet allowedVariants = new TreeSet<>(); - @Nullable - @Column(name = "preferred_model") - private String preferredModel; + @Column(name = "selected_variant", nullable = false) + private String selectedVariant; public boolean isEnabled() { return enabled; @@ -67,20 +64,20 @@ public void setEnabled(boolean enabled) { this.enabled = enabled; } - public SortedSet getAllowedModels() { - return allowedModels; + public SortedSet getAllowedVariants() { + return allowedVariants; } - public void setAllowedModels(SortedSet allowedModels) { - this.allowedModels = allowedModels; + public void setAllowedVariants(SortedSet allowedVariants) { + this.allowedVariants = allowedVariants; } @Nullable - public String getPreferredModel() { - return preferredModel; + public String getSelectedVariant() { + return selectedVariant; } - public void setPreferredModel(@Nullable String preferredModel) { - this.preferredModel = preferredModel; + public void setSelectedVariant(@Nullable String selectedVariant) { + this.selectedVariant = selectedVariant; } } diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisSubSettingsType.java b/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisSubSettingsType.java index d938134f4555..2a8270d420bc 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisSubSettingsType.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/domain/settings/IrisSubSettingsType.java @@ -1,5 +1,5 @@ package de.tum.cit.aet.artemis.iris.domain.settings; public enum IrisSubSettingsType { - CHAT, HESTIA, COMPETENCY_GENERATION, LECTURE_INGESTION + CHAT, COMPETENCY_GENERATION, LECTURE_INGESTION } diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/dto/IrisCombinedChatSubSettingsDTO.java b/src/main/java/de/tum/cit/aet/artemis/iris/dto/IrisCombinedChatSubSettingsDTO.java index 72d8e599ed70..c5589e824507 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/dto/IrisCombinedChatSubSettingsDTO.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/dto/IrisCombinedChatSubSettingsDTO.java @@ -6,10 +6,8 @@ import com.fasterxml.jackson.annotation.JsonInclude; -import de.tum.cit.aet.artemis.iris.domain.IrisTemplate; - @JsonInclude(JsonInclude.Include.NON_EMPTY) -public record IrisCombinedChatSubSettingsDTO(boolean enabled, Integer rateLimit, Integer rateLimitTimeframeHours, @Nullable Set allowedModels, - @Nullable String preferredModel, @Nullable IrisTemplate template) { +public record IrisCombinedChatSubSettingsDTO(boolean enabled, Integer rateLimit, Integer rateLimitTimeframeHours, @Nullable Set allowedVariants, + @Nullable String selectedVariant) { } diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/dto/IrisCombinedCompetencyGenerationSubSettingsDTO.java b/src/main/java/de/tum/cit/aet/artemis/iris/dto/IrisCombinedCompetencyGenerationSubSettingsDTO.java index 18ffcbc17b50..414b422e0f64 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/dto/IrisCombinedCompetencyGenerationSubSettingsDTO.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/dto/IrisCombinedCompetencyGenerationSubSettingsDTO.java @@ -6,9 +6,6 @@ import com.fasterxml.jackson.annotation.JsonInclude; -import de.tum.cit.aet.artemis.iris.domain.IrisTemplate; - @JsonInclude(JsonInclude.Include.NON_EMPTY) -public record IrisCombinedCompetencyGenerationSubSettingsDTO(boolean enabled, @Nullable Set allowedModels, @Nullable String preferredModel, - @Nullable IrisTemplate template) { +public record IrisCombinedCompetencyGenerationSubSettingsDTO(boolean enabled, @Nullable Set allowedVariants, @Nullable String selectedVariant) { } diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/dto/IrisCombinedHestiaSubSettingsDTO.java b/src/main/java/de/tum/cit/aet/artemis/iris/dto/IrisCombinedHestiaSubSettingsDTO.java deleted file mode 100644 index c70ce4825a92..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/iris/dto/IrisCombinedHestiaSubSettingsDTO.java +++ /dev/null @@ -1,13 +0,0 @@ -package de.tum.cit.aet.artemis.iris.dto; - -import java.util.Set; - -import jakarta.annotation.Nullable; - -import com.fasterxml.jackson.annotation.JsonInclude; - -import de.tum.cit.aet.artemis.iris.domain.IrisTemplate; - -@JsonInclude(JsonInclude.Include.NON_EMPTY) -public record IrisCombinedHestiaSubSettingsDTO(boolean enabled, @Nullable Set allowedModels, @Nullable String preferredModel, @Nullable IrisTemplate template) { -} diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/dto/IrisCombinedSettingsDTO.java b/src/main/java/de/tum/cit/aet/artemis/iris/dto/IrisCombinedSettingsDTO.java index 9353757c782e..355b05ae551a 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/dto/IrisCombinedSettingsDTO.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/dto/IrisCombinedSettingsDTO.java @@ -4,5 +4,5 @@ @JsonInclude(JsonInclude.Include.NON_EMPTY) public record IrisCombinedSettingsDTO(IrisCombinedChatSubSettingsDTO irisChatSettings, IrisCombinedLectureIngestionSubSettingsDTO irisLectureIngestionSettings, - IrisCombinedHestiaSubSettingsDTO irisHestiaSettings, IrisCombinedCompetencyGenerationSubSettingsDTO irisCompetencyGenerationSettings) { + IrisCombinedCompetencyGenerationSubSettingsDTO irisCompetencyGenerationSettings) { } diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/repository/IrisHestiaSessionRepository.java b/src/main/java/de/tum/cit/aet/artemis/iris/repository/IrisHestiaSessionRepository.java deleted file mode 100644 index 22a14bd98bd7..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/iris/repository/IrisHestiaSessionRepository.java +++ /dev/null @@ -1,28 +0,0 @@ -package de.tum.cit.aet.artemis.iris.repository; - -import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_IRIS; - -import java.util.List; - -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Repository; - -import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; -import de.tum.cit.aet.artemis.iris.domain.session.IrisHestiaSession; - -/** - * Repository interface for managing {@link IrisHestiaSession} entities. - * Provides custom queries for finding hestia sessions based on different criteria. - */ -@Repository -@Profile(PROFILE_IRIS) -public interface IrisHestiaSessionRepository extends ArtemisJpaRepository { - - /** - * Finds a list of {@link IrisHestiaSession} based on the exercise and user IDs. - * - * @param codeHintId The ID of the code hint. - * @return A list of hestia sessions sorted by creation date in descending order. - */ - List findByCodeHintIdOrderByCreationDateDesc(Long codeHintId); -} diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/repository/IrisSettingsRepository.java b/src/main/java/de/tum/cit/aet/artemis/iris/repository/IrisSettingsRepository.java index 8c4ffd56068e..4dd037d54c77 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/repository/IrisSettingsRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/repository/IrisSettingsRepository.java @@ -29,7 +29,6 @@ public interface IrisSettingsRepository extends ArtemisJpaRepository findAllGlobalSettings(); @@ -43,7 +42,6 @@ default IrisGlobalSettings findGlobalSettingsElseThrow() { FROM IrisCourseSettings irisSettings LEFT JOIN FETCH irisSettings.irisChatSettings LEFT JOIN FETCH irisSettings.irisLectureIngestionSettings - LEFT JOIN FETCH irisSettings.irisHestiaSettings LEFT JOIN FETCH irisSettings.irisCompetencyGenerationSettings WHERE irisSettings.course.id = :courseId """) diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/repository/IrisTemplateRepository.java b/src/main/java/de/tum/cit/aet/artemis/iris/repository/IrisTemplateRepository.java deleted file mode 100644 index 2b1a930d7aef..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/iris/repository/IrisTemplateRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package de.tum.cit.aet.artemis.iris.repository; - -import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; -import de.tum.cit.aet.artemis.iris.domain.IrisTemplate; - -/** - * Spring Data repository for the IrisTemplate entity. - */ -public interface IrisTemplateRepository extends ArtemisJpaRepository { - -} diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/IrisDefaultTemplateService.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/IrisDefaultTemplateService.java deleted file mode 100644 index 2a4757596bf1..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/iris/service/IrisDefaultTemplateService.java +++ /dev/null @@ -1,76 +0,0 @@ -package de.tum.cit.aet.artemis.iris.service; - -import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_IRIS; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.util.Optional; - -import org.apache.commons.io.IOUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Profile; -import org.springframework.core.io.Resource; -import org.springframework.stereotype.Service; - -import de.tum.cit.aet.artemis.core.service.ResourceLoaderService; -import de.tum.cit.aet.artemis.iris.domain.IrisTemplate; - -/** - * Service that loads default Iris templates from the resources/templates/iris folder. - */ -@Profile(PROFILE_IRIS) -@Service -public class IrisDefaultTemplateService { - - private static final Logger log = LoggerFactory.getLogger(IrisDefaultTemplateService.class); - - private final ResourceLoaderService resourceLoaderService; - - public IrisDefaultTemplateService(ResourceLoaderService resourceLoaderService) { - this.resourceLoaderService = resourceLoaderService; - } - - /** - * Loads the default Iris template with the given file name. - * For example, "chat.hbs" will load the template from "resources/templates/iris/chat.hbs". - * - * @param templateFileName The file name of the template to load. - * @return The loaded Iris template, or an empty template if an IO error occurred. - */ - public IrisTemplate load(String templateFileName) { - Path filePath = Path.of("templates", "iris", templateFileName); - Resource resource = resourceLoaderService.getResource(filePath); - try { - String fileContent = IOUtils.toString(resource.getInputStream(), StandardCharsets.UTF_8); - return new IrisTemplate(fileContent); - } - catch (IOException e) { - log.error("Error while loading Iris template from file: {}", filePath, e); - return new IrisTemplate(""); - } - } - - /** - * Loads the global template version from the "resources/templates/iris/template-version.txt" file. - * - * @return an Optional containing the version loaded from the file, or an empty Optional if there was an error. - */ - public Optional loadGlobalTemplateVersion() { - Path filePath = Path.of("templates", "iris", "template-version.txt"); - Resource resource = resourceLoaderService.getResource(filePath); - try { - String fileContent = IOUtils.toString(resource.getInputStream(), StandardCharsets.UTF_8); - int version = Integer.parseInt(fileContent.trim()); - return Optional.of(version); - } - catch (IOException e) { - log.error("Error while loading global template version from file: {}", filePath, e); - } - catch (NumberFormatException e) { - log.error("Content of {} was not a parseable int!", filePath, e); - } - return Optional.empty(); - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/IrisSessionService.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/IrisSessionService.java index 41fa1247b739..c37844d9740a 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/service/IrisSessionService.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/IrisSessionService.java @@ -14,12 +14,10 @@ import de.tum.cit.aet.artemis.iris.domain.message.IrisMessage; import de.tum.cit.aet.artemis.iris.domain.session.IrisCourseChatSession; import de.tum.cit.aet.artemis.iris.domain.session.IrisExerciseChatSession; -import de.tum.cit.aet.artemis.iris.domain.session.IrisHestiaSession; import de.tum.cit.aet.artemis.iris.domain.session.IrisSession; import de.tum.cit.aet.artemis.iris.service.session.IrisChatBasedFeatureInterface; import de.tum.cit.aet.artemis.iris.service.session.IrisCourseChatSessionService; import de.tum.cit.aet.artemis.iris.service.session.IrisExerciseChatSessionService; -import de.tum.cit.aet.artemis.iris.service.session.IrisHestiaSessionService; import de.tum.cit.aet.artemis.iris.service.session.IrisRateLimitedFeatureInterface; import de.tum.cit.aet.artemis.iris.service.session.IrisSubFeatureInterface; @@ -36,14 +34,11 @@ public class IrisSessionService { private final IrisCourseChatSessionService irisCourseChatSessionService; - private final IrisHestiaSessionService irisHestiaSessionService; - public IrisSessionService(UserRepository userRepository, IrisExerciseChatSessionService irisExerciseChatSessionService, - IrisCourseChatSessionService irisCourseChatSessionService, IrisHestiaSessionService irisHestiaSessionService) { + IrisCourseChatSessionService irisCourseChatSessionService) { this.userRepository = userRepository; this.irisExerciseChatSessionService = irisExerciseChatSessionService; this.irisCourseChatSessionService = irisCourseChatSessionService; - this.irisHestiaSessionService = irisHestiaSessionService; } /** @@ -138,7 +133,6 @@ private IrisSubFeatureWrapper getIrisSessionSubServic return switch (session) { case IrisExerciseChatSession chatSession -> (IrisSubFeatureWrapper) new IrisSubFeatureWrapper<>(irisExerciseChatSessionService, chatSession); case IrisCourseChatSession courseChatSession -> (IrisSubFeatureWrapper) new IrisSubFeatureWrapper<>(irisCourseChatSessionService, courseChatSession); - case IrisHestiaSession hestiaSession -> (IrisSubFeatureWrapper) new IrisSubFeatureWrapper<>(irisHestiaSessionService, hestiaSession); case null, default -> throw new BadRequestException("Unknown Iris session type " + session.getClass().getSimpleName()); }; } diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisConnectorService.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisConnectorService.java index 785fc59b9ed7..f41de6b6c97d 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisConnectorService.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisConnectorService.java @@ -19,10 +19,11 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import de.tum.cit.aet.artemis.iris.domain.settings.IrisSubSettingsType; import de.tum.cit.aet.artemis.iris.exception.IrisException; import de.tum.cit.aet.artemis.iris.exception.IrisForbiddenException; import de.tum.cit.aet.artemis.iris.exception.IrisInternalPyrisErrorException; -import de.tum.cit.aet.artemis.iris.service.pyris.dto.PyrisModelDTO; +import de.tum.cit.aet.artemis.iris.service.pyris.dto.PyrisVariantDTO; import de.tum.cit.aet.artemis.iris.service.pyris.dto.lectureingestionwebhook.PyrisWebhookLectureIngestionExecutionDTO; import de.tum.cit.aet.artemis.iris.web.open.PublicPyrisStatusUpdateResource; @@ -50,13 +51,14 @@ public PyrisConnectorService(@Qualifier("pyrisRestTemplate") RestTemplate restTe } /** - * Requests all available models from Pyris + * Requests all available variants from Pyris for a feature * - * @return A list of available Models as IrisModelDTO + * @param feature The feature to get the variants for + * @return A list of available Models as IrisVariantDTO */ - public List getOfferedModels() throws PyrisConnectorException { + public List getOfferedVariants(IrisSubSettingsType feature) throws PyrisConnectorException { try { - var response = restTemplate.getForEntity(pyrisUrl + "/api/v1/models", PyrisModelDTO[].class); + var response = restTemplate.getForEntity(pyrisUrl + "/api/v1/pipelines/" + feature.name() + "/variants", PyrisVariantDTO[].class); if (!response.getStatusCode().is2xxSuccessful() || !response.hasBody()) { throw new PyrisConnectorException("Could not fetch offered models"); } diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/dto/PyrisModelDTO.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/dto/PyrisVariantDTO.java similarity index 67% rename from src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/dto/PyrisModelDTO.java rename to src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/dto/PyrisVariantDTO.java index 705fada64870..ccfbecf7ee9a 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/dto/PyrisModelDTO.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/dto/PyrisVariantDTO.java @@ -3,5 +3,5 @@ import com.fasterxml.jackson.annotation.JsonInclude; @JsonInclude(JsonInclude.Include.NON_EMPTY) -public record PyrisModelDTO(String id, String name, String description) { +public record PyrisVariantDTO(String id, String name, String description) { } diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/session/IrisCourseChatSessionService.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/session/IrisCourseChatSessionService.java index a2c404b13103..6dea7a728ca6 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/service/session/IrisCourseChatSessionService.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/session/IrisCourseChatSessionService.java @@ -116,7 +116,8 @@ public void checkRateLimit(User user) { */ @Override public void requestAndHandleResponse(IrisCourseChatSession session) { - requestAndHandleResponse(session, "default", null); + var variant = irisSettingsService.getCombinedIrisSettingsFor(session.getCourse(), false).irisChatSettings().selectedVariant(); + requestAndHandleResponse(session, variant, null); } private void requestAndHandleResponse(IrisCourseChatSession session, String variant, CompetencyJol competencyJol) { diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/session/IrisExerciseChatSessionService.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/session/IrisExerciseChatSessionService.java index cec0a9322134..47e0da357ea9 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/service/session/IrisExerciseChatSessionService.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/session/IrisExerciseChatSessionService.java @@ -146,9 +146,8 @@ public void requestAndHandleResponse(IrisExerciseChatSession session) { var exercise = programmingExerciseRepository.findByIdWithTemplateAndSolutionParticipationElseThrow(chatSession.getExercise().getId()); var latestSubmission = getLatestSubmissionIfExists(exercise, chatSession.getUser()); - // TODO: Use settings to determine the variant - // var irisSettings = irisSettingsService.getCombinedIrisSettingsFor(chatSession.getExercise(), false); - pyrisPipelineService.executeExerciseChatPipeline("default", latestSubmission, exercise, chatSession); + var variant = irisSettingsService.getCombinedIrisSettingsFor(session.getExercise(), false).irisChatSettings().selectedVariant(); + pyrisPipelineService.executeExerciseChatPipeline(variant, latestSubmission, exercise, chatSession); } private Optional getLatestSubmissionIfExists(ProgrammingExercise exercise, User user) { diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/session/IrisHestiaSessionService.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/session/IrisHestiaSessionService.java deleted file mode 100644 index 6762b6d23d43..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/iris/service/session/IrisHestiaSessionService.java +++ /dev/null @@ -1,113 +0,0 @@ -package de.tum.cit.aet.artemis.iris.service.session; - -import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_IRIS; - -import java.time.ZonedDateTime; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Service; - -import com.fasterxml.jackson.annotation.JsonInclude; - -import de.tum.cit.aet.artemis.core.domain.User; -import de.tum.cit.aet.artemis.core.security.Role; -import de.tum.cit.aet.artemis.core.service.AuthorizationCheckService; -import de.tum.cit.aet.artemis.iris.domain.session.IrisHestiaSession; -import de.tum.cit.aet.artemis.iris.domain.settings.IrisSubSettingsType; -import de.tum.cit.aet.artemis.iris.repository.IrisHestiaSessionRepository; -import de.tum.cit.aet.artemis.iris.repository.IrisSessionRepository; -import de.tum.cit.aet.artemis.iris.service.pyris.PyrisConnectorService; -import de.tum.cit.aet.artemis.iris.service.settings.IrisSettingsService; -import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; -import de.tum.cit.aet.artemis.programming.domain.hestia.CodeHint; - -/** - * Service to handle the Hestia integration of Iris. - */ -@Service -@Profile(PROFILE_IRIS) -public class IrisHestiaSessionService implements IrisButtonBasedFeatureInterface { - - private static final Logger log = LoggerFactory.getLogger(IrisHestiaSessionService.class); - - private final IrisSettingsService irisSettingsService; - - private final AuthorizationCheckService authCheckService; - - private final IrisSessionRepository irisSessionRepository; - - private final IrisHestiaSessionRepository irisHestiaSessionRepository; - - public IrisHestiaSessionService(PyrisConnectorService pyrisConnectorService, IrisSettingsService irisSettingsService, AuthorizationCheckService authCheckService, - IrisSessionRepository irisSessionRepository, IrisHestiaSessionRepository irisHestiaSessionRepository) { - this.irisSettingsService = irisSettingsService; - this.authCheckService = authCheckService; - this.irisSessionRepository = irisSessionRepository; - this.irisHestiaSessionRepository = irisHestiaSessionRepository; - } - - /** - * Creates a new Iris session for the given code hint. - * If there is already an existing session for the code hint from the last hour, it will be returned instead. - * - * @param codeHint The code hint to create the session for - * @return The Iris session for the code hint - */ - public IrisHestiaSession getOrCreateSession(CodeHint codeHint) { - var existingSessions = irisHestiaSessionRepository.findByCodeHintIdOrderByCreationDateDesc(codeHint.getId()); - // Return the newest session if there is one and it is not older than 1 hour - if (!existingSessions.isEmpty() && existingSessions.getFirst().getCreationDate().plusHours(1).isAfter(ZonedDateTime.now())) { - checkHasAccessTo(null, existingSessions.getFirst()); - return existingSessions.getFirst(); - } - - // Otherwise create a new session - var irisSession = new IrisHestiaSession(); - irisSession.setCodeHint(codeHint); - checkHasAccessTo(null, irisSession); - irisSession = irisSessionRepository.save(irisSession); - return irisSession; - } - - @JsonInclude(JsonInclude.Include.NON_EMPTY) - record HestiaDTO(CodeHint codeHint, IrisHestiaSession session, ProgrammingExercise exercise) { - } - - /** - * Generates the description and content for a code hint. - * It does not directly save the code hint, but instead returns it with the generated description and content. - * This way the instructor can still modify the code hint before saving it or discard the changes. - * - * @param session The Iris session to generate the description for - * @return The code hint with the generated description and content - */ - @Override - public CodeHint executeRequest(IrisHestiaSession session) { - // TODO: Re-add in a future PR. Remember to reenable the test cases! - return null; - } - - /** - * Checks if the user has at least the given role for the exercise of the code hint. - * - * @param user The user to check the access for - * @param session The Iris session to check the access for - */ - @Override - public void checkHasAccessTo(User user, IrisHestiaSession session) { - var exercise = session.getCodeHint().getExercise(); - authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.EDITOR, exercise, user); - } - - /** - * Not supported for Iris Hestia sessions. - * - * @param session The session to get a message for - */ - @Override - public void checkIsFeatureActivatedFor(IrisHestiaSession session) { - irisSettingsService.isEnabledForElseThrow(IrisSubSettingsType.HESTIA, session.getCodeHint().getExercise()); - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/settings/IrisSettingsService.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/settings/IrisSettingsService.java index 39bb14ff31cd..bf9d4a8d35d3 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/service/settings/IrisSettingsService.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/settings/IrisSettingsService.java @@ -9,7 +9,8 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.Objects; -import java.util.Optional; +import java.util.Set; +import java.util.TreeSet; import java.util.function.Supplier; import org.springframework.boot.context.event.ApplicationReadyEvent; @@ -24,20 +25,17 @@ import de.tum.cit.aet.artemis.core.exception.ConflictException; import de.tum.cit.aet.artemis.core.service.AuthorizationCheckService; import de.tum.cit.aet.artemis.exercise.domain.Exercise; -import de.tum.cit.aet.artemis.iris.domain.IrisTemplate; import de.tum.cit.aet.artemis.iris.domain.settings.IrisChatSubSettings; import de.tum.cit.aet.artemis.iris.domain.settings.IrisCompetencyGenerationSubSettings; import de.tum.cit.aet.artemis.iris.domain.settings.IrisCourseSettings; import de.tum.cit.aet.artemis.iris.domain.settings.IrisExerciseSettings; import de.tum.cit.aet.artemis.iris.domain.settings.IrisGlobalSettings; -import de.tum.cit.aet.artemis.iris.domain.settings.IrisHestiaSubSettings; import de.tum.cit.aet.artemis.iris.domain.settings.IrisLectureIngestionSubSettings; import de.tum.cit.aet.artemis.iris.domain.settings.IrisSettings; import de.tum.cit.aet.artemis.iris.domain.settings.IrisSubSettings; import de.tum.cit.aet.artemis.iris.domain.settings.IrisSubSettingsType; import de.tum.cit.aet.artemis.iris.dto.IrisCombinedSettingsDTO; import de.tum.cit.aet.artemis.iris.repository.IrisSettingsRepository; -import de.tum.cit.aet.artemis.iris.service.IrisDefaultTemplateService; /** * Service for managing {@link IrisSettings}. @@ -54,34 +52,14 @@ public class IrisSettingsService { private final IrisSubSettingsService irisSubSettingsService; - private final IrisDefaultTemplateService irisDefaultTemplateService; - private final AuthorizationCheckService authCheckService; - public IrisSettingsService(IrisSettingsRepository irisSettingsRepository, IrisSubSettingsService irisSubSettingsService, IrisDefaultTemplateService irisDefaultTemplateService, - AuthorizationCheckService authCheckService) { + public IrisSettingsService(IrisSettingsRepository irisSettingsRepository, IrisSubSettingsService irisSubSettingsService, AuthorizationCheckService authCheckService) { this.irisSettingsRepository = irisSettingsRepository; this.irisSubSettingsService = irisSubSettingsService; - this.irisDefaultTemplateService = irisDefaultTemplateService; this.authCheckService = authCheckService; } - private Optional loadGlobalTemplateVersion() { - return irisDefaultTemplateService.loadGlobalTemplateVersion(); - } - - private IrisTemplate loadDefaultChatTemplate() { - return irisDefaultTemplateService.load("chat.hbs"); - } - - private IrisTemplate loadDefaultHestiaTemplate() { - return irisDefaultTemplateService.load("hestia.hbs"); - } - - private IrisTemplate loadDefaultCompetencyGenerationTemplate() { - return irisDefaultTemplateService.load("competency-generation.hbs"); - } - /** * Hooks into the {@link ApplicationReadyEvent} and creates or updates the global IrisSettings object on startup. * @@ -98,10 +76,6 @@ public void execute(ApplicationReadyEvent event) throws Exception { if (allGlobalSettings.size() > 1) { var maxIdSettings = allGlobalSettings.stream().max(Comparator.comparingLong(IrisSettings::getId)).orElseThrow(); allGlobalSettings.stream().filter(settings -> !Objects.equals(settings.getId(), maxIdSettings.getId())).forEach(irisSettingsRepository::delete); - autoUpdateGlobalSettings(maxIdSettings); - } - else { - autoUpdateGlobalSettings(allGlobalSettings.stream().findFirst().get()); } } @@ -110,46 +84,20 @@ public void execute(ApplicationReadyEvent event) throws Exception { */ private void createInitialGlobalSettings() { var settings = new IrisGlobalSettings(); - settings.setCurrentVersion(loadGlobalTemplateVersion().orElse(0)); initializeIrisChatSettings(settings); initializeIrisLectureIngestionSettings(settings); - initializeIrisHestiaSettings(settings); initializeIrisCompetencyGenerationSettings(settings); irisSettingsRepository.save(settings); } - /** - * Auto updates the global IrisSettings object if the current version is outdated. - * - * @param settings The global IrisSettings object to update - */ - private void autoUpdateGlobalSettings(IrisGlobalSettings settings) { - Optional globalVersion = loadGlobalTemplateVersion(); - if (globalVersion.isEmpty() || settings.getCurrentVersion() < globalVersion.get()) { - if (settings.isEnableAutoUpdateChat() || settings.getIrisChatSettings() == null) { - initializeIrisChatSettings(settings); - } - if (settings.isEnableAutoUpdateLectureIngestion() || settings.getIrisLectureIngestionSettings() == null) { - initializeIrisLectureIngestionSettings(settings); - } - if (settings.isEnableAutoUpdateHestia() || settings.getIrisHestiaSettings() == null) { - initializeIrisHestiaSettings(settings); - } - if (settings.isEnableAutoUpdateCompetencyGeneration() || settings.getIrisCompetencyGenerationSettings() == null) { - initializeIrisCompetencyGenerationSettings(settings); - } - - globalVersion.ifPresent(settings::setCurrentVersion); - saveIrisSettings(settings); - } - } - private static T initializeSettings(T settings, Supplier constructor) { if (settings == null) { settings = constructor.get(); settings.setEnabled(false); + settings.setAllowedVariants(new TreeSet<>(Set.of("default"))); + settings.setSelectedVariant("default"); } return settings; } @@ -157,7 +105,6 @@ private static T initializeSettings(T settings, Supp private void initializeIrisChatSettings(IrisGlobalSettings settings) { var irisChatSettings = settings.getIrisChatSettings(); irisChatSettings = initializeSettings(irisChatSettings, IrisChatSubSettings::new); - irisChatSettings.setTemplate(loadDefaultChatTemplate()); settings.setIrisChatSettings(irisChatSettings); } @@ -167,17 +114,9 @@ private void initializeIrisLectureIngestionSettings(IrisGlobalSettings settings) settings.setIrisLectureIngestionSettings(irisLectureIngestionSettings); } - private void initializeIrisHestiaSettings(IrisGlobalSettings settings) { - var irisHestiaSettings = settings.getIrisHestiaSettings(); - irisHestiaSettings = initializeSettings(irisHestiaSettings, IrisHestiaSubSettings::new); - irisHestiaSettings.setTemplate(loadDefaultHestiaTemplate()); - settings.setIrisHestiaSettings(irisHestiaSettings); - } - private void initializeIrisCompetencyGenerationSettings(IrisGlobalSettings settings) { var irisCompetencyGenerationSettings = settings.getIrisCompetencyGenerationSettings(); irisCompetencyGenerationSettings = initializeSettings(irisCompetencyGenerationSettings, IrisCompetencyGenerationSubSettings::new); - irisCompetencyGenerationSettings.setTemplate(loadDefaultCompetencyGenerationTemplate()); settings.setIrisCompetencyGenerationSettings(irisCompetencyGenerationSettings); } @@ -214,9 +153,6 @@ private T saveNewIrisSettings(T settings) { if (settings instanceof IrisGlobalSettings) { throw new BadRequestAlertException("You can not create new global settings", "IrisSettings", "notGlobal"); } - if (!settings.isValid()) { - throw new BadRequestAlertException("New Iris settings are not valid", "IrisSettings", "notValid"); - } if (settings instanceof IrisCourseSettings courseSettings && irisSettingsRepository.findCourseSettings(courseSettings.getCourse().getId()).isPresent()) { throw new ConflictException("Iris settings for this course already exist", "IrisSettings", "alreadyExists"); } @@ -241,9 +177,6 @@ private T updateIrisSettings(long existingSettingsId, T if (!Objects.equals(existingSettingsId, settingsUpdate.getId())) { throw new ConflictException("Existing Iris settings ID does not match update ID", "IrisSettings", "idMismatch"); } - if (!settingsUpdate.isValid()) { - throw new BadRequestAlertException("Updated Iris settings are not valid", "IrisSettings", "notValid"); - } var existingSettings = irisSettingsRepository.findByIdElseThrow(existingSettingsId); @@ -269,17 +202,9 @@ else if (existingSettings instanceof IrisExerciseSettings exerciseSettings && se * @return The updated global Iris settings */ private IrisGlobalSettings updateGlobalSettings(IrisGlobalSettings existingSettings, IrisGlobalSettings settingsUpdate) { - existingSettings.setCurrentVersion(settingsUpdate.getCurrentVersion()); - - existingSettings.setEnableAutoUpdateChat(settingsUpdate.isEnableAutoUpdateChat()); - existingSettings.setEnableAutoUpdateLectureIngestion(settingsUpdate.isEnableAutoUpdateLectureIngestion()); - existingSettings.setEnableAutoUpdateHestia(settingsUpdate.isEnableAutoUpdateHestia()); - existingSettings.setEnableAutoUpdateCompetencyGeneration(settingsUpdate.isEnableAutoUpdateCompetencyGeneration()); - existingSettings.setIrisLectureIngestionSettings( irisSubSettingsService.update(existingSettings.getIrisLectureIngestionSettings(), settingsUpdate.getIrisLectureIngestionSettings(), null, GLOBAL)); existingSettings.setIrisChatSettings(irisSubSettingsService.update(existingSettings.getIrisChatSettings(), settingsUpdate.getIrisChatSettings(), null, GLOBAL)); - existingSettings.setIrisHestiaSettings(irisSubSettingsService.update(existingSettings.getIrisHestiaSettings(), settingsUpdate.getIrisHestiaSettings(), null, GLOBAL)); existingSettings.setIrisCompetencyGenerationSettings( irisSubSettingsService.update(existingSettings.getIrisCompetencyGenerationSettings(), settingsUpdate.getIrisCompetencyGenerationSettings(), null, GLOBAL)); @@ -299,8 +224,6 @@ private IrisCourseSettings updateCourseSettings(IrisCourseSettings existingSetti irisSubSettingsService.update(existingSettings.getIrisChatSettings(), settingsUpdate.getIrisChatSettings(), parentSettings.irisChatSettings(), COURSE)); existingSettings.setIrisLectureIngestionSettings(irisSubSettingsService.update(existingSettings.getIrisLectureIngestionSettings(), settingsUpdate.getIrisLectureIngestionSettings(), parentSettings.irisLectureIngestionSettings(), COURSE)); - existingSettings.setIrisHestiaSettings( - irisSubSettingsService.update(existingSettings.getIrisHestiaSettings(), settingsUpdate.getIrisHestiaSettings(), parentSettings.irisHestiaSettings(), COURSE)); existingSettings.setIrisCompetencyGenerationSettings(irisSubSettingsService.update(existingSettings.getIrisCompetencyGenerationSettings(), settingsUpdate.getIrisCompetencyGenerationSettings(), parentSettings.irisCompetencyGenerationSettings(), COURSE)); @@ -382,8 +305,7 @@ public IrisCombinedSettingsDTO getCombinedIrisGlobalSettings() { settingsList.add(getGlobalSettings()); return new IrisCombinedSettingsDTO(irisSubSettingsService.combineChatSettings(settingsList, false), - irisSubSettingsService.combineLectureIngestionSubSettings(settingsList, false), irisSubSettingsService.combineHestiaSettings(settingsList, false), - irisSubSettingsService.combineCompetencyGenerationSettings(settingsList, false)); + irisSubSettingsService.combineLectureIngestionSubSettings(settingsList, false), irisSubSettingsService.combineCompetencyGenerationSettings(settingsList, false)); } /** @@ -402,7 +324,7 @@ public IrisCombinedSettingsDTO getCombinedIrisSettingsFor(Course course, boolean settingsList.add(irisSettingsRepository.findCourseSettings(course.getId()).orElse(null)); return new IrisCombinedSettingsDTO(irisSubSettingsService.combineChatSettings(settingsList, minimal), - irisSubSettingsService.combineLectureIngestionSubSettings(settingsList, minimal), irisSubSettingsService.combineHestiaSettings(settingsList, minimal), + irisSubSettingsService.combineLectureIngestionSubSettings(settingsList, minimal), irisSubSettingsService.combineCompetencyGenerationSettings(settingsList, minimal)); } @@ -423,7 +345,7 @@ public IrisCombinedSettingsDTO getCombinedIrisSettingsFor(Exercise exercise, boo settingsList.add(getRawIrisSettingsFor(exercise)); return new IrisCombinedSettingsDTO(irisSubSettingsService.combineChatSettings(settingsList, minimal), - irisSubSettingsService.combineLectureIngestionSubSettings(settingsList, minimal), irisSubSettingsService.combineHestiaSettings(settingsList, minimal), + irisSubSettingsService.combineLectureIngestionSubSettings(settingsList, minimal), irisSubSettingsService.combineCompetencyGenerationSettings(settingsList, minimal)); } @@ -450,7 +372,6 @@ public IrisCourseSettings getDefaultSettingsFor(Course course) { settings.setCourse(course); settings.setIrisLectureIngestionSettings(new IrisLectureIngestionSubSettings()); settings.setIrisChatSettings(new IrisChatSubSettings()); - settings.setIrisHestiaSettings(new IrisHestiaSubSettings()); settings.setIrisCompetencyGenerationSettings(new IrisCompetencyGenerationSubSettings()); return settings; } @@ -523,7 +444,6 @@ public void deleteSettingsFor(Exercise exercise) { private boolean isFeatureEnabledInSettings(IrisCombinedSettingsDTO settings, IrisSubSettingsType type) { return switch (type) { case CHAT -> settings.irisChatSettings().enabled(); - case HESTIA -> settings.irisHestiaSettings().enabled(); case COMPETENCY_GENERATION -> settings.irisCompetencyGenerationSettings().enabled(); case LECTURE_INGESTION -> settings.irisLectureIngestionSettings().enabled(); }; diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/settings/IrisSubSettingsService.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/settings/IrisSubSettingsService.java index dfd555bb59c5..5a3c20b4c810 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/service/settings/IrisSubSettingsService.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/settings/IrisSubSettingsService.java @@ -15,18 +15,15 @@ import org.springframework.stereotype.Service; import de.tum.cit.aet.artemis.core.service.AuthorizationCheckService; -import de.tum.cit.aet.artemis.iris.domain.IrisTemplate; import de.tum.cit.aet.artemis.iris.domain.settings.IrisChatSubSettings; import de.tum.cit.aet.artemis.iris.domain.settings.IrisCompetencyGenerationSubSettings; import de.tum.cit.aet.artemis.iris.domain.settings.IrisExerciseSettings; -import de.tum.cit.aet.artemis.iris.domain.settings.IrisHestiaSubSettings; import de.tum.cit.aet.artemis.iris.domain.settings.IrisLectureIngestionSubSettings; import de.tum.cit.aet.artemis.iris.domain.settings.IrisSettings; import de.tum.cit.aet.artemis.iris.domain.settings.IrisSettingsType; import de.tum.cit.aet.artemis.iris.domain.settings.IrisSubSettings; import de.tum.cit.aet.artemis.iris.dto.IrisCombinedChatSubSettingsDTO; import de.tum.cit.aet.artemis.iris.dto.IrisCombinedCompetencyGenerationSubSettingsDTO; -import de.tum.cit.aet.artemis.iris.dto.IrisCombinedHestiaSubSettingsDTO; import de.tum.cit.aet.artemis.iris.dto.IrisCombinedLectureIngestionSubSettingsDTO; /** @@ -76,10 +73,9 @@ public IrisChatSubSettings update(IrisChatSubSettings currentSettings, IrisChatS currentSettings.setRateLimit(newSettings.getRateLimit()); currentSettings.setRateLimitTimeframeHours(newSettings.getRateLimitTimeframeHours()); } - currentSettings.setAllowedModels(selectAllowedModels(currentSettings.getAllowedModels(), newSettings.getAllowedModels())); - currentSettings.setPreferredModel(validatePreferredModel(currentSettings.getPreferredModel(), newSettings.getPreferredModel(), currentSettings.getAllowedModels(), - parentSettings != null ? parentSettings.allowedModels() : null)); - currentSettings.setTemplate(newSettings.getTemplate()); + currentSettings.setAllowedVariants(selectAllowedVariants(currentSettings.getAllowedVariants(), newSettings.getAllowedVariants())); + currentSettings.setSelectedVariant(validateSelectedVariant(currentSettings.getSelectedVariant(), newSettings.getSelectedVariant(), currentSettings.getAllowedVariants(), + parentSettings != null ? parentSettings.allowedVariants() : null)); return currentSettings; } @@ -114,40 +110,6 @@ public IrisLectureIngestionSubSettings update(IrisLectureIngestionSubSettings cu return currentSettings; } - /** - * Updates a Hestia sub settings object. - * If the new settings are null, the current settings will be deleted (except if the parent settings are null == if the settings are global). - * Special notes: - * - If the user is not an admin the allowed models will not be updated. - * - If the user is not an admin the preferred model will only be updated if it is included in the allowed models. - * - * @param currentSettings Current Hestia sub settings. - * @param newSettings Updated Hestia sub settings. - * @param parentSettings Parent Hestia sub settings. - * @param settingsType Type of the settings the sub settings belong to. - * @return Updated Hestia sub settings. - */ - public IrisHestiaSubSettings update(IrisHestiaSubSettings currentSettings, IrisHestiaSubSettings newSettings, IrisCombinedHestiaSubSettingsDTO parentSettings, - IrisSettingsType settingsType) { - if (newSettings == null) { - if (parentSettings == null) { - throw new IllegalArgumentException("Cannot delete the Hestia settings"); - } - return null; - } - if (currentSettings == null) { - currentSettings = new IrisHestiaSubSettings(); - } - if (settingsType == IrisSettingsType.EXERCISE || authCheckService.isAdmin()) { - currentSettings.setEnabled(newSettings.isEnabled()); - } - currentSettings.setAllowedModels(selectAllowedModels(currentSettings.getAllowedModels(), newSettings.getAllowedModels())); - currentSettings.setPreferredModel(validatePreferredModel(currentSettings.getPreferredModel(), newSettings.getPreferredModel(), currentSettings.getAllowedModels(), - parentSettings != null ? parentSettings.allowedModels() : null)); - currentSettings.setTemplate(newSettings.getTemplate()); - return currentSettings; - } - /** * Updates a Competency Generation sub settings object. * If the new settings are null, the current settings will be deleted (except if the parent settings are null == if the settings are global). @@ -174,11 +136,10 @@ public IrisCompetencyGenerationSubSettings update(IrisCompetencyGenerationSubSet } if (authCheckService.isAdmin()) { currentSettings.setEnabled(newSettings.isEnabled()); - currentSettings.setAllowedModels(selectAllowedModels(currentSettings.getAllowedModels(), newSettings.getAllowedModels())); + currentSettings.setAllowedVariants(selectAllowedVariants(currentSettings.getAllowedVariants(), newSettings.getAllowedVariants())); } - currentSettings.setPreferredModel(validatePreferredModel(currentSettings.getPreferredModel(), newSettings.getPreferredModel(), currentSettings.getAllowedModels(), - parentSettings != null ? parentSettings.allowedModels() : null)); - currentSettings.setTemplate(newSettings.getTemplate()); + currentSettings.setSelectedVariant(validateSelectedVariant(currentSettings.getSelectedVariant(), newSettings.getSelectedVariant(), currentSettings.getAllowedVariants(), + parentSettings != null ? parentSettings.allowedVariants() : null)); return currentSettings; } @@ -187,12 +148,12 @@ public IrisCompetencyGenerationSubSettings update(IrisCompetencyGenerationSubSet * If the user is an admin, all models are allowed. * Otherwise, only models that are allowed by the parent settings or the current settings are allowed. * - * @param allowedModels The allowed models of the current settings. - * @param updatedAllowedModels The allowed models of the updated settings. + * @param allowedVariants The allowed models of the current settings. + * @param updatedAllowedVariants The allowed models of the updated settings. * @return The filtered allowed models. */ - private SortedSet selectAllowedModels(SortedSet allowedModels, SortedSet updatedAllowedModels) { - return authCheckService.isAdmin() ? updatedAllowedModels : allowedModels; + private SortedSet selectAllowedVariants(SortedSet allowedVariants, SortedSet updatedAllowedVariants) { + return authCheckService.isAdmin() ? updatedAllowedVariants : allowedVariants; } /** @@ -200,23 +161,23 @@ private SortedSet selectAllowedModels(SortedSet allowedModels, S * If the user is an admin, all models are allowed. * Otherwise, only models that are allowed by the current settings are allowed. * - * @param preferredModel The preferred model of the current settings. - * @param newPreferredModel The preferred model of the updated settings. - * @param allowedModels The allowed models of the current settings. - * @param parentAllowedModels The allowed models of the parent settings. + * @param selectedVariant The preferred model of the current settings. + * @param newSelectedVariant The preferred model of the updated settings. + * @param allowedVariants The allowed models of the current settings. + * @param parentAllowedVariants The allowed models of the parent settings. * @return The validated preferred model. */ - private String validatePreferredModel(String preferredModel, String newPreferredModel, Set allowedModels, Set parentAllowedModels) { - if (newPreferredModel == null || newPreferredModel.isBlank()) { + private String validateSelectedVariant(String selectedVariant, String newSelectedVariant, Set allowedVariants, Set parentAllowedVariants) { + if (newSelectedVariant == null || newSelectedVariant.isBlank()) { return null; } - var canChangePreferredModel = authCheckService.isAdmin() || (allowedModels != null && !allowedModels.isEmpty() && allowedModels.contains(newPreferredModel)) - || ((allowedModels == null || allowedModels.isEmpty()) && parentAllowedModels != null && parentAllowedModels.contains(newPreferredModel)); - if (canChangePreferredModel) { - return newPreferredModel; + var canChangeSelectedVariant = authCheckService.isAdmin() || (allowedVariants != null && !allowedVariants.isEmpty() && allowedVariants.contains(newSelectedVariant)) + || ((allowedVariants == null || allowedVariants.isEmpty()) && parentAllowedVariants != null && parentAllowedVariants.contains(newSelectedVariant)); + if (canChangeSelectedVariant) { + return newSelectedVariant; } - return preferredModel; + return selectedVariant; } /** @@ -231,10 +192,9 @@ private String validatePreferredModel(String preferredModel, String newPreferred public IrisCombinedChatSubSettingsDTO combineChatSettings(ArrayList settingsList, boolean minimal) { var enabled = getCombinedEnabled(settingsList, IrisSettings::getIrisChatSettings); var rateLimit = getCombinedRateLimit(settingsList); - var allowedModels = minimal ? getCombinedAllowedModels(settingsList, IrisSettings::getIrisChatSettings) : null; - var preferredModel = minimal ? getCombinedPreferredModel(settingsList, IrisSettings::getIrisChatSettings) : null; - var template = minimal ? getCombinedTemplate(settingsList, IrisSettings::getIrisChatSettings, IrisChatSubSettings::getTemplate) : null; - return new IrisCombinedChatSubSettingsDTO(enabled, rateLimit, null, allowedModels, preferredModel, template); + var allowedVariants = !minimal ? getCombinedAllowedVariants(settingsList, IrisSettings::getIrisChatSettings) : null; + var selectedVariant = !minimal ? getCombinedSelectedVariant(settingsList, IrisSettings::getIrisChatSettings) : null; + return new IrisCombinedChatSubSettingsDTO(enabled, rateLimit, null, allowedVariants, selectedVariant); } /** @@ -251,24 +211,6 @@ public IrisCombinedLectureIngestionSubSettingsDTO combineLectureIngestionSubSett return new IrisCombinedLectureIngestionSubSettingsDTO(enabled); } - /** - * Combines the Hestia settings of multiple {@link IrisSettings} objects. - * If minimal is true, the returned object will only contain the enabled field. - * The minimal version can safely be sent to students. - * - * @param settingsList List of {@link IrisSettings} objects to combine. - * @param minimal Whether to return a minimal version of the combined settings. - * @return Combined Hestia settings. - */ - public IrisCombinedHestiaSubSettingsDTO combineHestiaSettings(ArrayList settingsList, boolean minimal) { - var actualSettingsList = settingsList.stream().filter(settings -> !(settings instanceof IrisExerciseSettings)).toList(); - var enabled = getCombinedEnabled(actualSettingsList, IrisSettings::getIrisHestiaSettings); - var allowedModels = minimal ? getCombinedAllowedModels(actualSettingsList, IrisSettings::getIrisHestiaSettings) : null; - var preferredModel = minimal ? getCombinedPreferredModel(actualSettingsList, IrisSettings::getIrisHestiaSettings) : null; - var template = minimal ? getCombinedTemplate(actualSettingsList, IrisSettings::getIrisHestiaSettings, IrisHestiaSubSettings::getTemplate) : null; - return new IrisCombinedHestiaSubSettingsDTO(enabled, allowedModels, preferredModel, template); - } - /** * Combines the Competency Generation settings of multiple {@link IrisSettings} objects. * If minimal is true, the returned object will only contain the enabled field. @@ -281,11 +223,9 @@ public IrisCombinedHestiaSubSettingsDTO combineHestiaSettings(ArrayList settingsList, boolean minimal) { var actualSettingsList = settingsList.stream().filter(settings -> !(settings instanceof IrisExerciseSettings)).toList(); var enabled = getCombinedEnabled(actualSettingsList, IrisSettings::getIrisCompetencyGenerationSettings); - var allowedModels = minimal ? getCombinedAllowedModels(actualSettingsList, IrisSettings::getIrisCompetencyGenerationSettings) : null; - var preferredModel = minimal ? getCombinedPreferredModel(actualSettingsList, IrisSettings::getIrisCompetencyGenerationSettings) : null; - var template = minimal ? getCombinedTemplate(actualSettingsList, IrisSettings::getIrisCompetencyGenerationSettings, IrisCompetencyGenerationSubSettings::getTemplate) - : null; - return new IrisCombinedCompetencyGenerationSubSettingsDTO(enabled, allowedModels, preferredModel, template); + var allowedVariants = !minimal ? getCombinedAllowedVariants(actualSettingsList, IrisSettings::getIrisCompetencyGenerationSettings) : null; + var selectedVariant = !minimal ? getCombinedSelectedVariant(actualSettingsList, IrisSettings::getIrisCompetencyGenerationSettings) : null; + return new IrisCombinedCompetencyGenerationSubSettingsDTO(enabled, allowedVariants, selectedVariant); } /** @@ -322,43 +262,28 @@ private Integer getCombinedRateLimit(List settingsList) { } /** - * Combines the allowedModels field of multiple {@link IrisSettings} objects. - * Simply takes the last allowedModels. + * Combines the allowedVariants field of multiple {@link IrisSettings} objects. + * Simply takes the last allowedVariants. * * @param settingsList List of {@link IrisSettings} objects to combine. * @param subSettingsFunction Function to get the sub settings from an IrisSettings object. - * @return Combined allowedModels field. + * @return Combined allowedVariants field. */ - private Set getCombinedAllowedModels(List settingsList, Function subSettingsFunction) { - return settingsList.stream().filter(Objects::nonNull).map(subSettingsFunction).filter(Objects::nonNull).map(IrisSubSettings::getAllowedModels).filter(Objects::nonNull) + private Set getCombinedAllowedVariants(List settingsList, Function subSettingsFunction) { + return settingsList.stream().filter(Objects::nonNull).map(subSettingsFunction).filter(Objects::nonNull).map(IrisSubSettings::getAllowedVariants).filter(Objects::nonNull) .filter(models -> !models.isEmpty()).reduce((first, second) -> second).orElse(new TreeSet<>()); } /** - * Combines the preferredModel field of multiple {@link IrisSettings} objects. - * Simply takes the last preferredModel. - * TODO + * Combines the selectedVariant field of multiple {@link IrisSettings} objects. + * Simply takes the last selectedVariant. * * @param settingsList List of {@link IrisSettings} objects to combine. * @param subSettingsFunction Function to get the sub settings from an IrisSettings object. - * @return Combined preferredModel field. + * @return Combined selectedVariant field. */ - private String getCombinedPreferredModel(List settingsList, Function subSettingsFunction) { - return settingsList.stream().filter(Objects::nonNull).map(subSettingsFunction).filter(Objects::nonNull).map(IrisSubSettings::getPreferredModel) + private String getCombinedSelectedVariant(List settingsList, Function subSettingsFunction) { + return settingsList.stream().filter(Objects::nonNull).map(subSettingsFunction).filter(Objects::nonNull).map(IrisSubSettings::getSelectedVariant) .filter(model -> model != null && !model.isBlank()).reduce((first, second) -> second).orElse(null); } - - /** - * Combines the template field of multiple {@link IrisSettings} objects. - * Simply takes the last template. - * - * @param settingsList List of {@link IrisSettings} objects to combine. - * @param templateFunction Function to get the template from the sub settings from an IrisSettings object. - * @return Combined template field. - */ - private IrisTemplate getCombinedTemplate(List settingsList, Function subSettingsFunction, - Function templateFunction) { - return settingsList.stream().filter(Objects::nonNull).map(subSettingsFunction).filter(Objects::nonNull).map(templateFunction) - .filter(template -> template != null && template.getContent() != null && !template.getContent().isBlank()).reduce((first, second) -> second).orElse(null); - } } diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/web/IrisModelsResource.java b/src/main/java/de/tum/cit/aet/artemis/iris/web/IrisModelsResource.java deleted file mode 100644 index ae4bedb82493..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/iris/web/IrisModelsResource.java +++ /dev/null @@ -1,49 +0,0 @@ -package de.tum.cit.aet.artemis.iris.web; - -import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_IRIS; - -import java.util.List; - -import org.springframework.context.annotation.Profile; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import de.tum.cit.aet.artemis.core.exception.InternalServerErrorException; -import de.tum.cit.aet.artemis.core.security.annotations.EnforceAtLeastEditor; -import de.tum.cit.aet.artemis.iris.service.pyris.PyrisConnectorException; -import de.tum.cit.aet.artemis.iris.service.pyris.PyrisConnectorService; -import de.tum.cit.aet.artemis.iris.service.pyris.dto.PyrisModelDTO; - -/** - * REST controller for managing the models Pyris provides. - */ -@Profile(PROFILE_IRIS) -@RestController -@RequestMapping("api/") -public class IrisModelsResource { - - private final PyrisConnectorService pyrisConnectorService; - - public IrisModelsResource(PyrisConnectorService pyrisConnectorService) { - this.pyrisConnectorService = pyrisConnectorService; - } - - /** - * GET iris/models: Retrieve all available models offered by Pyris - * - * @return the {@link ResponseEntity} with status {@code 200 (Ok)} and with body a List of the models - */ - @GetMapping("iris/models") - @EnforceAtLeastEditor - public ResponseEntity> getAllModels() { - try { - var models = pyrisConnectorService.getOfferedModels(); - return ResponseEntity.ok(models); - } - catch (PyrisConnectorException e) { - throw new InternalServerErrorException("Could not fetch available Iris models"); - } - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/web/IrisSettingsResource.java b/src/main/java/de/tum/cit/aet/artemis/iris/web/IrisSettingsResource.java index 32676da073bb..a4f51180b159 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/web/IrisSettingsResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/web/IrisSettingsResource.java @@ -14,9 +14,13 @@ import de.tum.cit.aet.artemis.core.repository.CourseRepository; import de.tum.cit.aet.artemis.core.repository.UserRepository; import de.tum.cit.aet.artemis.core.security.Role; -import de.tum.cit.aet.artemis.core.security.annotations.EnforceAtLeastEditor; import de.tum.cit.aet.artemis.core.security.annotations.EnforceAtLeastInstructor; -import de.tum.cit.aet.artemis.core.security.annotations.EnforceAtLeastStudent; +import de.tum.cit.aet.artemis.core.security.annotations.enforceRoleInCourse.EnforceAtLeastEditorInCourse; +import de.tum.cit.aet.artemis.core.security.annotations.enforceRoleInCourse.EnforceAtLeastInstructorInCourse; +import de.tum.cit.aet.artemis.core.security.annotations.enforceRoleInCourse.EnforceAtLeastStudentInCourse; +import de.tum.cit.aet.artemis.core.security.annotations.enforceRoleInExercise.EnforceAtLeastEditorInExercise; +import de.tum.cit.aet.artemis.core.security.annotations.enforceRoleInExercise.EnforceAtLeastInstructorInExercise; +import de.tum.cit.aet.artemis.core.security.annotations.enforceRoleInExercise.EnforceAtLeastStudentInExercise; import de.tum.cit.aet.artemis.core.service.AuthorizationCheckService; import de.tum.cit.aet.artemis.iris.domain.settings.IrisCourseSettings; import de.tum.cit.aet.artemis.iris.domain.settings.IrisExerciseSettings; @@ -71,10 +75,9 @@ public ResponseEntity getGlobalSettings() { * @return the {@link ResponseEntity} with status {@code 200 (Ok)} and with body the settings, or with status {@code 404 (Not Found)} if the course could not be found. */ @GetMapping("courses/{courseId}/raw-iris-settings") - @EnforceAtLeastEditor + @EnforceAtLeastEditorInCourse public ResponseEntity getRawCourseSettings(@PathVariable Long courseId) { var course = courseRepository.findByIdElseThrow(courseId); - authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.EDITOR, course, null); var irisSettings = irisSettingsService.getRawIrisSettingsFor(course); return ResponseEntity.ok(irisSettings); } @@ -86,7 +89,7 @@ public ResponseEntity getRawCourseSettings(@PathVariable Long cour * @return the {@link ResponseEntity} with status {@code 200 (Ok)} and with body the settings, or with status {@code 404 (Not Found)} if the exercise could not be found. */ @GetMapping("programming-exercises/{exerciseId}/raw-iris-settings") - @EnforceAtLeastEditor + @EnforceAtLeastEditorInExercise public ResponseEntity getRawProgrammingExerciseSettings(@PathVariable Long exerciseId) { var exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); var user = userRepository.getUserWithGroupsAndAuthorities(); @@ -103,11 +106,10 @@ public ResponseEntity getRawProgrammingExerciseSettings(@PathVaria * @return the {@link ResponseEntity} with status {@code 200 (Ok)} and with body the settings, or with status {@code 404 (Not Found)} if the course could not be found. */ @GetMapping("courses/{courseId}/iris-settings") - @EnforceAtLeastStudent + @EnforceAtLeastStudentInCourse public ResponseEntity getCourseSettings(@PathVariable Long courseId) { var course = courseRepository.findByIdElseThrow(courseId); var user = userRepository.getUserWithGroupsAndAuthorities(); - authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.STUDENT, course, user); // Editors can see the full settings, students only the reduced settings var getReduced = !authCheckService.isAtLeastEditorInCourse(course, user); @@ -122,11 +124,10 @@ public ResponseEntity getCourseSettings(@PathVariable L * @return the {@link ResponseEntity} with status {@code 200 (Ok)} and with body the settings, or with status {@code 404 (Not Found)} if the exercise could not be found. */ @GetMapping("programming-exercises/{exerciseId}/iris-settings") - @EnforceAtLeastStudent + @EnforceAtLeastStudentInExercise public ResponseEntity getProgrammingExerciseSettings(@PathVariable Long exerciseId) { var exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); var user = userRepository.getUserWithGroupsAndAuthorities(); - authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.STUDENT, exercise, user); var combinedIrisSettings = irisSettingsService.getCombinedIrisSettingsFor(exercise, irisSettingsService.shouldShowMinimalSettings(exercise, user)); return ResponseEntity.ok(combinedIrisSettings); @@ -140,10 +141,9 @@ public ResponseEntity getProgrammingExerciseSettings(@P * @return the {@link ResponseEntity} with status {@code 200 (Ok)} and with body the updated settings, or with status {@code 404 (Not Found)} if the course could not be found. */ @PutMapping("courses/{courseId}/raw-iris-settings") - @EnforceAtLeastEditor + @EnforceAtLeastInstructorInCourse public ResponseEntity updateCourseSettings(@PathVariable Long courseId, @RequestBody IrisCourseSettings settings) { var course = courseRepository.findByIdElseThrow(courseId); - authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.EDITOR, course, null); settings.setCourse(course); var updatedSettings = irisSettingsService.saveIrisSettings(settings); return ResponseEntity.ok(updatedSettings); @@ -158,11 +158,9 @@ public ResponseEntity updateCourseSettings(@PathVariable Lon * found. */ @PutMapping("programming-exercises/{exerciseId}/raw-iris-settings") - @EnforceAtLeastInstructor + @EnforceAtLeastInstructorInExercise public ResponseEntity updateProgrammingExerciseSettings(@PathVariable Long exerciseId, @RequestBody IrisExerciseSettings settings) { var exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); - var user = userRepository.getUserWithGroupsAndAuthorities(); - authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.INSTRUCTOR, exercise, user); settings.setExercise(exercise); var updatedSettings = irisSettingsService.saveIrisSettings(settings); return ResponseEntity.ok(updatedSettings); diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/web/IrisVariantsResource.java b/src/main/java/de/tum/cit/aet/artemis/iris/web/IrisVariantsResource.java new file mode 100644 index 000000000000..9342d1522023 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/iris/web/IrisVariantsResource.java @@ -0,0 +1,56 @@ +package de.tum.cit.aet.artemis.iris.web; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Profile; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import de.tum.cit.aet.artemis.core.exception.InternalServerErrorException; +import de.tum.cit.aet.artemis.core.security.annotations.EnforceAtLeastEditor; +import de.tum.cit.aet.artemis.iris.domain.settings.IrisSubSettingsType; +import de.tum.cit.aet.artemis.iris.service.pyris.PyrisConnectorException; +import de.tum.cit.aet.artemis.iris.service.pyris.PyrisConnectorService; +import de.tum.cit.aet.artemis.iris.service.pyris.dto.PyrisVariantDTO; + +/** + * REST controller for managing the variants Pyris provides. + */ +@Profile("iris") +@RestController +@RequestMapping("api/") +public class IrisVariantsResource { + + private static final Logger log = LoggerFactory.getLogger(IrisVariantsResource.class); + + private final PyrisConnectorService pyrisConnectorService; + + public IrisVariantsResource(PyrisConnectorService pyrisConnectorService) { + this.pyrisConnectorService = pyrisConnectorService; + } + + /** + * GET iris/variants/{feature}: Retrieve all available variants offered by Pyris for a certain feature + * + * @param featureRaw the feature for which to retrieve the variants + * @return the {@link ResponseEntity} with status {@code 200 (Ok)} and with body a List of the variants + */ + @GetMapping("iris/variants/{feature}") + @EnforceAtLeastEditor + public ResponseEntity> getAllVariants(@PathVariable("feature") String featureRaw) { + var feature = IrisSubSettingsType.valueOf(featureRaw.toUpperCase().replace("-", "_")); + try { + var variants = pyrisConnectorService.getOfferedVariants(feature); + return ResponseEntity.ok(variants); + } + catch (PyrisConnectorException e) { + log.error("Could not fetch available variants for feature {}", feature, e); + throw new InternalServerErrorException("Could not fetch available variants for feature " + feature); + } + } +} diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/web/admin/AdminIrisSettingsResource.java b/src/main/java/de/tum/cit/aet/artemis/iris/web/admin/AdminIrisSettingsResource.java index b8ea1f92ba31..40da3e5ee431 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/web/admin/AdminIrisSettingsResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/web/admin/AdminIrisSettingsResource.java @@ -17,6 +17,7 @@ * REST controller for managing {@link IrisSettings}. */ @Profile(PROFILE_IRIS) +@EnforceAdmin @RestController @RequestMapping("api/admin/") public class AdminIrisSettingsResource { @@ -34,7 +35,6 @@ public AdminIrisSettingsResource(IrisSettingsService irisSettingsService) { * @return the {@link ResponseEntity} with status {@code 200 (Ok)} and with body the updated settings. */ @PutMapping("iris/global-iris-settings") - @EnforceAdmin public ResponseEntity updateGlobalSettings(@RequestBody IrisSettings settings) { var updatedSettings = irisSettingsService.saveIrisSettings(settings); return ResponseEntity.ok(updatedSettings); diff --git a/src/main/java/de/tum/cit/aet/artemis/lti/web/admin/AdminLtiConfigurationResource.java b/src/main/java/de/tum/cit/aet/artemis/lti/web/admin/AdminLtiConfigurationResource.java index 654facd44302..0b5d3f71c2f3 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lti/web/admin/AdminLtiConfigurationResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/lti/web/admin/AdminLtiConfigurationResource.java @@ -33,6 +33,7 @@ * Handles administrative actions for LTI platforms, including configuration, deletion, and dynamic registration. */ @RestController +@EnforceAdmin @RequestMapping("api/admin/") @Profile(PROFILE_LTI) public class AdminLtiConfigurationResource { @@ -75,7 +76,6 @@ public AdminLtiConfigurationResource(LtiPlatformConfigurationRepository ltiPlatf * @return a {@code ResponseEntity} with an {@code Optional} and HTTP status. */ @GetMapping("lti-platform/{platformId}") - @EnforceAdmin public ResponseEntity getLtiPlatformConfiguration(@PathVariable("platformId") String platformId) { log.debug("REST request to configured lti platform"); LtiPlatformConfiguration platform = ltiPlatformConfigurationRepository.findByIdElseThrow(Long.parseLong(platformId)); @@ -89,7 +89,6 @@ public ResponseEntity getLtiPlatformConfiguration(@Pat * @return a {@code ResponseEntity} with status {@code 200 (OK)} and a header indicating the deletion. */ @DeleteMapping("lti-platform/{platformId}") - @EnforceAdmin public ResponseEntity deleteLtiPlatformConfiguration(@PathVariable("platformId") String platformId) { log.debug("REST request to configured lti platform"); LtiPlatformConfiguration platform = ltiPlatformConfigurationRepository.findByIdElseThrow(Long.parseLong(platformId)); @@ -105,7 +104,6 @@ public ResponseEntity deleteLtiPlatformConfiguration(@PathVariable("platfo * or with status 400 (Bad Request) if the provided platform configuration is invalid (e.g., missing ID) */ @PutMapping("lti-platform") - @EnforceAdmin public ResponseEntity updateLtiPlatformConfiguration(@RequestBody LtiPlatformConfiguration platform) { log.debug("REST request to update configured lti platform"); @@ -125,7 +123,6 @@ public ResponseEntity updateLtiPlatformConfiguration(@RequestBody LtiPlatf * or with status 400 (Bad Request) if the provided platform configuration is invalid (e.g., missing ID) */ @PostMapping("lti-platform") - @EnforceAdmin public ResponseEntity addLtiPlatformConfiguration(@RequestBody LtiPlatformConfiguration platform) { log.debug("REST request to add new lti platform"); @@ -147,7 +144,6 @@ public ResponseEntity addLtiPlatformConfiguration(@RequestBody LtiPlatform * @return a {@link ResponseEntity} with status 200 (OK) if the dynamic registration process was successful. */ @PostMapping("lti13/dynamic-registration") - @EnforceAdmin public ResponseEntity lti13DynamicRegistration(@RequestParam(name = "openid_configuration") String openIdConfiguration, @RequestParam(name = "registration_token", required = false) String registrationToken) { diff --git a/src/main/java/de/tum/cit/aet/artemis/modeling/web/admin/AdminModelingExerciseResource.java b/src/main/java/de/tum/cit/aet/artemis/modeling/web/admin/AdminModelingExerciseResource.java index 215ea0a05293..cf3c6d2ee627 100644 --- a/src/main/java/de/tum/cit/aet/artemis/modeling/web/admin/AdminModelingExerciseResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/modeling/web/admin/AdminModelingExerciseResource.java @@ -25,6 +25,7 @@ * REST controller for administrating ModelingExercise. */ @Profile(PROFILE_CORE) +@EnforceAdmin @RestController @RequestMapping("api/admin/") public class AdminModelingExerciseResource { @@ -59,7 +60,6 @@ public AdminModelingExerciseResource(ModelingExerciseRepository modelingExercise * @return the ResponseEntity with status 200 (OK) */ @GetMapping("modeling-exercises/{exerciseId}/check-clusters") - @EnforceAdmin public ResponseEntity checkClusters(@PathVariable Long exerciseId) { log.info("REST request to check clusters of ModelingExercise : {}", exerciseId); int clusterCount = modelClusterRepository.countByExerciseIdWithEagerElements(exerciseId); @@ -73,7 +73,6 @@ public ResponseEntity checkClusters(@PathVariable Long exerciseId) { * @return the ResponseEntity with status 200 (OK) */ @DeleteMapping("modeling-exercises/{exerciseId}/clusters") - @EnforceAdmin public ResponseEntity deleteModelingExerciseClustersAndElements(@PathVariable Long exerciseId) { log.info("REST request to delete ModelingExercise : {}", exerciseId); var modelingExercise = modelingExerciseRepository.findByIdElseThrow(exerciseId); @@ -91,7 +90,6 @@ public ResponseEntity deleteModelingExerciseClustersAndElements(@PathVaria * @return the ResponseEntity with status 200 (OK) */ @PostMapping("modeling-exercises/{exerciseId}/trigger-automatic-assessment") - @EnforceAdmin public ResponseEntity triggerAutomaticAssessment(@PathVariable Long exerciseId) { instanceMessageSendService.sendModelingExerciseInstantClustering(exerciseId); return ResponseEntity.ok().build(); diff --git a/src/main/java/de/tum/cit/aet/artemis/plagiarism/service/ProgrammingPlagiarismDetectionService.java b/src/main/java/de/tum/cit/aet/artemis/plagiarism/service/ProgrammingPlagiarismDetectionService.java index 9fe9b1cc0f8c..ea6ebfdf3b81 100644 --- a/src/main/java/de/tum/cit/aet/artemis/plagiarism/service/ProgrammingPlagiarismDetectionService.java +++ b/src/main/java/de/tum/cit/aet/artemis/plagiarism/service/ProgrammingPlagiarismDetectionService.java @@ -37,6 +37,7 @@ import de.jplag.options.JPlagOptions; import de.jplag.python3.PythonLanguage; import de.jplag.reporting.reportobject.ReportObjectFactory; +import de.jplag.rlang.RLanguage; import de.jplag.rust.RustLanguage; import de.jplag.swift.SwiftLanguage; import de.tum.cit.aet.artemis.core.exception.BadRequestAlertException; @@ -310,14 +311,15 @@ public void deleteTempLocalRepository(Repository repository) { private Language getJPlagProgrammingLanguage(ProgrammingExercise programmingExercise) { return switch (programmingExercise.getProgrammingLanguage()) { - case JAVA -> new JavaLanguage(); case C -> new CLanguage(); - case PYTHON -> new PythonLanguage(); - case SWIFT -> new SwiftLanguage(); + case JAVA -> new JavaLanguage(); + case JAVASCRIPT -> new JavaScriptLanguage(); case KOTLIN -> new KotlinLanguage(); + case PYTHON -> new PythonLanguage(); + case R -> new RLanguage(); case RUST -> new RustLanguage(); - case JAVASCRIPT -> new JavaScriptLanguage(); - case EMPTY, PHP, DART, HASKELL, ASSEMBLER, OCAML, C_SHARP, C_PLUS_PLUS, SQL, R, TYPESCRIPT, GO, MATLAB, BASH, VHDL, RUBY, POWERSHELL, ADA -> + case SWIFT -> new SwiftLanguage(); + case EMPTY, PHP, DART, HASKELL, ASSEMBLER, OCAML, C_SHARP, C_PLUS_PLUS, SQL, TYPESCRIPT, GO, MATLAB, BASH, VHDL, RUBY, POWERSHELL, ADA -> throw new BadRequestAlertException("Programming language " + programmingExercise.getProgrammingLanguage() + " not supported for plagiarism check.", "ProgrammingExercise", "notSupported"); }; diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/domain/AuthenticationMechanism.java b/src/main/java/de/tum/cit/aet/artemis/programming/domain/AuthenticationMechanism.java index 3caec801ed53..239ef3674d44 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/domain/AuthenticationMechanism.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/domain/AuthenticationMechanism.java @@ -20,5 +20,5 @@ public enum AuthenticationMechanism { /** * The user used the artemis client code editor to authenticate to the LocalVC */ - CODE_EDITOR + CODE_EDITOR, } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingLanguage.java b/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingLanguage.java index 4206bfe15dbc..781ad04f98c6 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingLanguage.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingLanguage.java @@ -38,18 +38,19 @@ public enum ProgrammingLanguage { PHP("php"); private static final Set ENABLED_LANGUAGES = Set.of( - EMPTY, - JAVA, - PYTHON, + ASSEMBLER, C, HASKELL, + JAVA, + JAVASCRIPT, KOTLIN, - VHDL, - ASSEMBLER, - SWIFT, OCAML, + PYTHON, + R, RUST, - JAVASCRIPT + SWIFT, + VHDL, + EMPTY ); // @formatter:on diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/repository/BuildJobRepository.java b/src/main/java/de/tum/cit/aet/artemis/programming/repository/BuildJobRepository.java index 7c341585f60f..d7a5e662c744 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/repository/BuildJobRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/repository/BuildJobRepository.java @@ -89,4 +89,19 @@ default BuildJob findByBuildJobIdElseThrow(String buildJobId) { return getValueElseThrow(findByBuildJobId(buildJobId)); } + /** + * Get the number of build jobs for a list of exercise ids. + * + * @param exerciseIds the list of exercise ids + * @return the number of build jobs + */ + @Query(""" + SELECT COUNT(b) + FROM BuildJob b + LEFT JOIN Result r ON b.result.id = r.id + LEFT JOIN Participation p ON r.participation.id = p.id + LEFT JOIN Exercise e ON p.exercise.id = e.id + WHERE e.id IN :exerciseIds + """) + long countBuildJobsByExerciseIds(@Param("exerciseIds") List exerciseIds); } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseRepository.java b/src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseRepository.java index ab64ca6e53a8..579d714b18a8 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseRepository.java @@ -116,6 +116,8 @@ Optional findWithTemplateAndSolutionParticipationTeamAssign List findAllByProjectKey(String projectKey); + List findAllByCourseId(Long courseId); + @EntityGraph(type = LOAD, attributePaths = "submissionPolicy") List findWithSubmissionPolicyByProjectKey(String projectKey); diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/TemplateUpgradePolicyService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/TemplateUpgradePolicyService.java index 16285e6a0695..4c73046b1ab0 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/TemplateUpgradePolicyService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/TemplateUpgradePolicyService.java @@ -32,8 +32,8 @@ public TemplateUpgradePolicyService(JavaTemplateUpgradeService javaRepositoryUpg public TemplateUpgradeService getUpgradeService(ProgrammingLanguage programmingLanguage) { return switch (programmingLanguage) { case JAVA -> javaRepositoryUpgradeService; - case KOTLIN, PYTHON, C, HASKELL, VHDL, ASSEMBLER, SWIFT, OCAML, EMPTY, RUST, JAVASCRIPT -> defaultRepositoryUpgradeService; - case C_SHARP, C_PLUS_PLUS, SQL, R, TYPESCRIPT, GO, MATLAB, BASH, RUBY, POWERSHELL, ADA, DART, PHP -> + case KOTLIN, PYTHON, C, HASKELL, VHDL, ASSEMBLER, SWIFT, OCAML, EMPTY, RUST, JAVASCRIPT, R -> defaultRepositoryUpgradeService; + case C_SHARP, C_PLUS_PLUS, SQL, TYPESCRIPT, GO, MATLAB, BASH, RUBY, POWERSHELL, ADA, DART, PHP -> throw new UnsupportedOperationException("Unsupported programming language: " + programmingLanguage); }; } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/ContinuousIntegrationService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/ContinuousIntegrationService.java index b4f67794c073..b9050501c67a 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/ContinuousIntegrationService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/ContinuousIntegrationService.java @@ -219,8 +219,8 @@ enum RepositoryCheckoutPath implements CustomizableCheckoutPath { @Override public String forProgrammingLanguage(ProgrammingLanguage language) { return switch (language) { - case JAVA, PYTHON, C, HASKELL, KOTLIN, VHDL, ASSEMBLER, SWIFT, OCAML, EMPTY, RUST, JAVASCRIPT -> "assignment"; - case C_SHARP, C_PLUS_PLUS, SQL, R, TYPESCRIPT, GO, MATLAB, BASH, RUBY, POWERSHELL, ADA, DART, PHP -> + case JAVA, PYTHON, C, HASKELL, KOTLIN, VHDL, ASSEMBLER, SWIFT, OCAML, EMPTY, RUST, JAVASCRIPT, R -> "assignment"; + case C_SHARP, C_PLUS_PLUS, SQL, TYPESCRIPT, GO, MATLAB, BASH, RUBY, POWERSHELL, ADA, DART, PHP -> throw new UnsupportedOperationException("Unsupported programming language: " + language); }; } @@ -230,9 +230,9 @@ public String forProgrammingLanguage(ProgrammingLanguage language) { @Override public String forProgrammingLanguage(ProgrammingLanguage language) { return switch (language) { - case JAVA, PYTHON, HASKELL, KOTLIN, SWIFT, EMPTY, RUST, JAVASCRIPT -> ""; + case JAVA, PYTHON, HASKELL, KOTLIN, SWIFT, EMPTY, RUST, JAVASCRIPT, R -> ""; case C, VHDL, ASSEMBLER, OCAML -> "tests"; - case C_SHARP, C_PLUS_PLUS, SQL, R, TYPESCRIPT, GO, MATLAB, BASH, RUBY, POWERSHELL, ADA, DART, PHP -> + case C_SHARP, C_PLUS_PLUS, SQL, TYPESCRIPT, GO, MATLAB, BASH, RUBY, POWERSHELL, ADA, DART, PHP -> throw new UnsupportedOperationException("Unsupported programming language: " + language); }; } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIProgrammingLanguageFeatureService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIProgrammingLanguageFeatureService.java index a92bcdd26cb5..0c71114e13bb 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIProgrammingLanguageFeatureService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIProgrammingLanguageFeatureService.java @@ -25,7 +25,7 @@ public class GitLabCIProgrammingLanguageFeatureService extends ProgrammingLangua public GitLabCIProgrammingLanguageFeatureService() { programmingLanguageFeatures.put(EMPTY, new ProgrammingLanguageFeature(EMPTY, false, false, false, false, false, List.of(), false, false)); programmingLanguageFeatures.put(JAVA, new ProgrammingLanguageFeature(JAVA, false, false, false, true, false, List.of(PLAIN_MAVEN, MAVEN_MAVEN), false, false)); - programmingLanguageFeatures.put(RUST, new ProgrammingLanguageFeature(RUST, false, false, true, false, false, List.of(), false, false)); programmingLanguageFeatures.put(JAVASCRIPT, new ProgrammingLanguageFeature(JAVASCRIPT, false, false, true, false, false, List.of(), false, false)); + programmingLanguageFeatures.put(RUST, new ProgrammingLanguageFeature(RUST, false, false, true, false, false, List.of(), false, false)); } } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/CodeHintService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/CodeHintService.java index ac73fae28454..f04de8a56db9 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/CodeHintService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/CodeHintService.java @@ -13,8 +13,6 @@ import org.springframework.stereotype.Service; import de.tum.cit.aet.artemis.core.exception.BadRequestAlertException; -import de.tum.cit.aet.artemis.iris.domain.session.IrisHestiaSession; -import de.tum.cit.aet.artemis.iris.service.session.IrisHestiaSessionService; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; import de.tum.cit.aet.artemis.programming.domain.hestia.CodeHint; import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseSolutionEntry; @@ -29,17 +27,14 @@ public class CodeHintService { private static final Logger log = LoggerFactory.getLogger(CodeHintService.class); - private final Optional irisHestiaSessionService; - private final CodeHintRepository codeHintRepository; private final ProgrammingExerciseTaskRepository taskRepository; private final ProgrammingExerciseSolutionEntryRepository solutionEntryRepository; - public CodeHintService(Optional irisHestiaSessionService, CodeHintRepository codeHintRepository, ProgrammingExerciseTaskRepository taskRepository, + public CodeHintService(CodeHintRepository codeHintRepository, ProgrammingExerciseTaskRepository taskRepository, ProgrammingExerciseSolutionEntryRepository solutionEntryRepository) { - this.irisHestiaSessionService = irisHestiaSessionService; this.codeHintRepository = codeHintRepository; this.taskRepository = taskRepository; this.solutionEntryRepository = solutionEntryRepository; @@ -189,17 +184,4 @@ public void updateSolutionEntriesForCodeHint(CodeHint hint) { codeHintRepository.save(hint); } - - /** - * Generates a description and content for a code hint using the Iris subsystem. - * See {@link IrisHestiaSessionService#executeRequest(IrisHestiaSession)} for more information. - * - * @param codeHint The code hint to be generated - * @return The code hint with description and content - */ - public CodeHint generateDescriptionWithIris(CodeHint codeHint) { - var irisService = irisHestiaSessionService.orElseThrow(); - var session = irisService.getOrCreateSession(codeHint); - return irisService.executeRequest(session); - } } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java index 38893ea41093..45a473da9148 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java @@ -7,6 +7,7 @@ import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.JAVASCRIPT; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.KOTLIN; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.PYTHON; +import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.R; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.RUST; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.SWIFT; import static de.tum.cit.aet.artemis.programming.domain.ProjectType.FACT; @@ -33,15 +34,16 @@ public class JenkinsProgrammingLanguageFeatureService extends ProgrammingLanguag public JenkinsProgrammingLanguageFeatureService() { // Must be extended once a new programming language is added programmingLanguageFeatures.put(EMPTY, new ProgrammingLanguageFeature(EMPTY, false, false, false, false, false, List.of(), false, false)); + programmingLanguageFeatures.put(C, new ProgrammingLanguageFeature(C, false, false, true, false, false, List.of(FACT, GCC), false, false)); + programmingLanguageFeatures.put(HASKELL, new ProgrammingLanguageFeature(HASKELL, false, false, false, false, true, List.of(), false, false)); programmingLanguageFeatures.put(JAVA, new ProgrammingLanguageFeature(JAVA, true, true, true, true, false, List.of(PLAIN_GRADLE, GRADLE_GRADLE, PLAIN_MAVEN, MAVEN_MAVEN, MAVEN_BLACKBOX), true, false)); + programmingLanguageFeatures.put(JAVASCRIPT, new ProgrammingLanguageFeature(JAVASCRIPT, false, false, true, false, false, List.of(), false, false)); programmingLanguageFeatures.put(KOTLIN, new ProgrammingLanguageFeature(KOTLIN, true, false, true, true, false, List.of(), true, false)); programmingLanguageFeatures.put(PYTHON, new ProgrammingLanguageFeature(PYTHON, false, false, true, false, false, List.of(), false, false)); + programmingLanguageFeatures.put(R, new ProgrammingLanguageFeature(R, false, false, true, false, false, List.of(), false, false)); + programmingLanguageFeatures.put(RUST, new ProgrammingLanguageFeature(RUST, false, false, true, false, false, List.of(), false, false)); // Jenkins is not supporting XCODE at the moment programmingLanguageFeatures.put(SWIFT, new ProgrammingLanguageFeature(SWIFT, false, true, true, true, false, List.of(PLAIN), false, false)); - programmingLanguageFeatures.put(C, new ProgrammingLanguageFeature(C, false, false, true, false, false, List.of(FACT, GCC), false, false)); - programmingLanguageFeatures.put(HASKELL, new ProgrammingLanguageFeature(HASKELL, false, false, false, false, true, List.of(), false, false)); - programmingLanguageFeatures.put(RUST, new ProgrammingLanguageFeature(RUST, false, false, true, false, false, List.of(), false, false)); - programmingLanguageFeatures.put(JAVASCRIPT, new ProgrammingLanguageFeature(JAVASCRIPT, false, false, true, false, false, List.of(), false, false)); } } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/build_plan/JenkinsBuildPlanService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/build_plan/JenkinsBuildPlanService.java index 6e904910ca57..f900cc0f6dd1 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/build_plan/JenkinsBuildPlanService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/build_plan/JenkinsBuildPlanService.java @@ -184,8 +184,8 @@ private JenkinsXmlConfigBuilder builderFor(ProgrammingLanguage programmingLangua throw new UnsupportedOperationException("Xcode templates are not available for Jenkins."); } return switch (programmingLanguage) { - case JAVA, KOTLIN, PYTHON, C, HASKELL, SWIFT, EMPTY, RUST, JAVASCRIPT -> jenkinsBuildPlanCreator; - case VHDL, ASSEMBLER, OCAML, C_SHARP, C_PLUS_PLUS, SQL, R, TYPESCRIPT, GO, MATLAB, BASH, RUBY, POWERSHELL, ADA, DART, PHP -> + case JAVA, KOTLIN, PYTHON, C, HASKELL, SWIFT, EMPTY, RUST, JAVASCRIPT, R -> jenkinsBuildPlanCreator; + case VHDL, ASSEMBLER, OCAML, C_SHARP, C_PLUS_PLUS, SQL, TYPESCRIPT, GO, MATLAB, BASH, RUBY, POWERSHELL, ADA, DART, PHP -> throw new UnsupportedOperationException(programmingLanguage + " templates are not available for Jenkins."); }; } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java index 525170cca334..bc8292d407bb 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java @@ -10,6 +10,7 @@ import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.KOTLIN; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.OCAML; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.PYTHON; +import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.R; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.RUST; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.SWIFT; import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.VHDL; @@ -39,17 +40,18 @@ public class LocalCIProgrammingLanguageFeatureService extends ProgrammingLanguag public LocalCIProgrammingLanguageFeatureService() { // Must be extended once a new programming language is added programmingLanguageFeatures.put(EMPTY, new ProgrammingLanguageFeature(EMPTY, false, false, false, false, false, List.of(), false, true)); + programmingLanguageFeatures.put(ASSEMBLER, new ProgrammingLanguageFeature(ASSEMBLER, false, false, false, false, false, List.of(), false, true)); + programmingLanguageFeatures.put(C, new ProgrammingLanguageFeature(C, false, true, true, false, false, List.of(FACT, GCC), false, true)); + programmingLanguageFeatures.put(HASKELL, new ProgrammingLanguageFeature(HASKELL, true, false, false, false, true, List.of(), false, true)); programmingLanguageFeatures.put(JAVA, new ProgrammingLanguageFeature(JAVA, true, true, true, true, false, List.of(PLAIN_GRADLE, GRADLE_GRADLE, PLAIN_MAVEN, MAVEN_MAVEN), false, true)); - programmingLanguageFeatures.put(PYTHON, new ProgrammingLanguageFeature(PYTHON, false, false, true, false, false, List.of(), false, true)); - programmingLanguageFeatures.put(C, new ProgrammingLanguageFeature(C, false, true, true, false, false, List.of(FACT, GCC), false, true)); - programmingLanguageFeatures.put(ASSEMBLER, new ProgrammingLanguageFeature(ASSEMBLER, false, false, false, false, false, List.of(), false, true)); + programmingLanguageFeatures.put(JAVASCRIPT, new ProgrammingLanguageFeature(JAVASCRIPT, false, false, true, false, false, List.of(), false, true)); programmingLanguageFeatures.put(KOTLIN, new ProgrammingLanguageFeature(KOTLIN, false, false, true, true, false, List.of(), false, true)); - programmingLanguageFeatures.put(VHDL, new ProgrammingLanguageFeature(VHDL, false, false, false, false, false, List.of(), false, true)); - programmingLanguageFeatures.put(HASKELL, new ProgrammingLanguageFeature(HASKELL, true, false, false, false, true, List.of(), false, true)); programmingLanguageFeatures.put(OCAML, new ProgrammingLanguageFeature(OCAML, false, false, false, false, true, List.of(), false, true)); - programmingLanguageFeatures.put(SWIFT, new ProgrammingLanguageFeature(SWIFT, false, false, true, true, false, List.of(PLAIN), false, true)); + programmingLanguageFeatures.put(PYTHON, new ProgrammingLanguageFeature(PYTHON, false, false, true, false, false, List.of(), false, true)); + programmingLanguageFeatures.put(R, new ProgrammingLanguageFeature(R, false, false, true, false, false, List.of(), false, true)); programmingLanguageFeatures.put(RUST, new ProgrammingLanguageFeature(RUST, false, false, true, false, false, List.of(), false, true)); - programmingLanguageFeatures.put(JAVASCRIPT, new ProgrammingLanguageFeature(JAVASCRIPT, false, false, true, false, false, List.of(), false, true)); + programmingLanguageFeatures.put(SWIFT, new ProgrammingLanguageFeature(SWIFT, false, false, true, true, false, List.of(PLAIN), false, true)); + programmingLanguageFeatures.put(VHDL, new ProgrammingLanguageFeature(VHDL, false, false, false, false, false, List.of(), false, true)); } } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseExportImportResource.java b/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseExportImportResource.java index 2222e4a5f3d9..a55c44871619 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseExportImportResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseExportImportResource.java @@ -86,6 +86,7 @@ * REST controller for managing ProgrammingExercise. */ @Profile(PROFILE_CORE) +@FeatureToggle(Feature.ProgrammingExercises) @RestController @RequestMapping("api/") public class ProgrammingExerciseExportImportResource { @@ -184,7 +185,6 @@ private void validateStaticCodeAnalysisSettings(ProgrammingExercise programmingE */ @PostMapping("programming-exercises/import/{sourceExerciseId}") @EnforceAtLeastEditor - @FeatureToggle(Feature.ProgrammingExercises) public ResponseEntity importProgrammingExercise(@PathVariable long sourceExerciseId, @RequestBody ProgrammingExercise newExercise, @RequestParam(defaultValue = "false") boolean recreateBuildPlans, @RequestParam(defaultValue = "false") boolean updateTemplate, @RequestParam(defaultValue = "false") boolean setTestCaseVisibilityToAfterDueDate) throws JsonProcessingException { @@ -291,7 +291,6 @@ public ResponseEntity importProgrammingExercise(@PathVariab */ @PostMapping("courses/{courseId}/programming-exercises/import-from-file") @EnforceAtLeastEditor - @FeatureToggle(Feature.ProgrammingExercises) public ResponseEntity importProgrammingExerciseFromFile(@PathVariable long courseId, @RequestPart("programmingExercise") ProgrammingExercise programmingExercise, @RequestPart("file") MultipartFile zipFile) { final var user = userRepository.getUserWithGroupsAndAuthorities(); @@ -318,7 +317,7 @@ public ResponseEntity importProgrammingExerciseFromFile(@Pa */ @GetMapping("programming-exercises/{exerciseId}/export-instructor-exercise") @EnforceAtLeastInstructor - @FeatureToggle({ Feature.ProgrammingExercises, Feature.Exports }) + @FeatureToggle(Feature.Exports) public ResponseEntity exportInstructorExercise(@PathVariable long exerciseId) throws IOException { var programmingExercise = programmingExerciseRepository.findByIdWithPlagiarismDetectionConfigTeamConfigAndBuildConfigElseThrow(exerciseId); authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.INSTRUCTOR, programmingExercise, null); @@ -352,7 +351,7 @@ public ResponseEntity exportInstructorExercise(@PathVariable long exer */ @GetMapping("programming-exercises/{exerciseId}/export-instructor-repository/{repositoryType}") @EnforceAtLeastTutor - @FeatureToggle({ Feature.ProgrammingExercises, Feature.Exports }) + @FeatureToggle(Feature.Exports) public ResponseEntity exportInstructorRepository(@PathVariable long exerciseId, @PathVariable RepositoryType repositoryType) throws IOException { var programmingExercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.TEACHING_ASSISTANT, programmingExercise, null); @@ -373,7 +372,7 @@ public ResponseEntity exportInstructorRepository(@PathVariable long ex */ @GetMapping("programming-exercises/{exerciseId}/export-instructor-auxiliary-repository/{repositoryId}") @EnforceAtLeastTutor - @FeatureToggle({ Feature.ProgrammingExercises, Feature.Exports }) + @FeatureToggle(Feature.Exports) public ResponseEntity exportInstructorAuxiliaryRepository(@PathVariable long exerciseId, @PathVariable long repositoryId) throws IOException { var programmingExercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.TEACHING_ASSISTANT, programmingExercise, null); @@ -419,7 +418,7 @@ private ResponseEntity returnZipFileForRepositoryExport(Optional */ @PostMapping("programming-exercises/{exerciseId}/export-repos-by-participant-identifiers/{participantIdentifiers}") @EnforceAtLeastTutor - @FeatureToggle({ Feature.ProgrammingExercises, Feature.Exports }) + @FeatureToggle(Feature.Exports) public ResponseEntity exportSubmissionsByStudentLogins(@PathVariable long exerciseId, @PathVariable String participantIdentifiers, @RequestBody RepositoryExportOptionsDTO repositoryExportOptions) throws IOException { var programmingExercise = programmingExerciseRepository.findByIdWithStudentParticipationsAndLegalSubmissionsElseThrow(exerciseId); @@ -464,7 +463,7 @@ public ResponseEntity exportSubmissionsByStudentLogins(@PathVariable l */ @PostMapping("programming-exercises/{exerciseId}/export-repos-by-participation-ids/{participationIds}") @EnforceAtLeastTutor - @FeatureToggle({ Feature.ProgrammingExercises, Feature.Exports }) + @FeatureToggle(Feature.Exports) public ResponseEntity exportSubmissionsByParticipationIds(@PathVariable long exerciseId, @PathVariable String participationIds, @RequestBody RepositoryExportOptionsDTO repositoryExportOptions) throws IOException { var programmingExercise = programmingExerciseRepository.findByIdWithStudentParticipationsAndLegalSubmissionsElseThrow(exerciseId); @@ -523,7 +522,7 @@ private ResponseEntity provideZipForParticipations(@NotNull List exportStudentRequestedRepository(@PathVariable long exerciseId, @RequestParam() boolean includeTests) throws IOException { var programmingExercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); if (programmingExercise.isExamExercise()) { @@ -550,7 +549,7 @@ public ResponseEntity exportStudentRequestedRepository(@PathVariable l */ @GetMapping("programming-exercises/{exerciseId}/export-student-repository/{participationId}") @EnforceAtLeastStudent - @FeatureToggle({ Feature.ProgrammingExercises, Feature.Exports }) + @FeatureToggle(Feature.Exports) public ResponseEntity exportStudentRepository(@PathVariable long exerciseId, @PathVariable long participationId) throws IOException { var programmingExercise = programmingExerciseRepository.findByIdWithStudentParticipationsAndLegalSubmissionsElseThrow(exerciseId); var studentParticipation = programmingExercise.getStudentParticipations().stream().filter(p -> p.getId().equals(participationId)) diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExercisePlagiarismResource.java b/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExercisePlagiarismResource.java index 4388e386ab39..95e60a0ec89c 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExercisePlagiarismResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExercisePlagiarismResource.java @@ -40,6 +40,7 @@ * REST controller for managing ProgrammingExercise. */ @Profile(PROFILE_CORE) +@FeatureToggle(Feature.ProgrammingExercises) @RestController @RequestMapping("api/") public class ProgrammingExercisePlagiarismResource { @@ -72,7 +73,6 @@ public ProgrammingExercisePlagiarismResource(ProgrammingExerciseRepository progr */ @GetMapping("programming-exercises/{exerciseId}/plagiarism-result") @EnforceAtLeastEditor - @FeatureToggle(Feature.ProgrammingExercises) public ResponseEntity> getPlagiarismResult(@PathVariable long exerciseId) { log.debug("REST request to get the latest plagiarism result for the programming exercise with id: {}", exerciseId); ProgrammingExercise programmingExercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); @@ -95,7 +95,7 @@ public ResponseEntity> getPlagiarismRe */ @GetMapping("programming-exercises/{exerciseId}/check-plagiarism") @EnforceAtLeastEditor - @FeatureToggle({ Feature.ProgrammingExercises, Feature.PlagiarismChecks }) + @FeatureToggle(Feature.PlagiarismChecks) public ResponseEntity> checkPlagiarism(@PathVariable long exerciseId, @RequestParam int similarityThreshold, @RequestParam int minimumScore, @RequestParam int minimumSize) throws ExitException, IOException { ProgrammingExercise programmingExercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); @@ -128,7 +128,6 @@ public ResponseEntity> checkPlagiarism */ @GetMapping(value = "programming-exercises/{exerciseId}/check-plagiarism-jplag-report") @EnforceAtLeastEditor - @FeatureToggle(Feature.ProgrammingExercises) public ResponseEntity checkPlagiarismWithJPlagReport(@PathVariable long exerciseId, @RequestParam int similarityThreshold, @RequestParam int minimumScore, @RequestParam int minimumSize) throws IOException { log.debug("REST request to check plagiarism for ProgrammingExercise with id: {}", exerciseId); diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/web/hestia/CodeHintResource.java b/src/main/java/de/tum/cit/aet/artemis/programming/web/hestia/CodeHintResource.java index 297e784f1e2b..122aa11b7a17 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/web/hestia/CodeHintResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/web/hestia/CodeHintResource.java @@ -1,11 +1,9 @@ package de.tum.cit.aet.artemis.programming.web.hestia; import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; -import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_IRIS; import java.util.List; import java.util.Objects; -import java.util.Optional; import java.util.Set; import org.slf4j.Logger; @@ -23,8 +21,6 @@ import de.tum.cit.aet.artemis.core.exception.AccessForbiddenException; import de.tum.cit.aet.artemis.core.exception.ConflictException; import de.tum.cit.aet.artemis.core.security.annotations.enforceRoleInExercise.EnforceAtLeastEditorInExercise; -import de.tum.cit.aet.artemis.iris.domain.settings.IrisSubSettingsType; -import de.tum.cit.aet.artemis.iris.service.settings.IrisSettingsService; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; import de.tum.cit.aet.artemis.programming.domain.hestia.CodeHint; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository; @@ -50,15 +46,12 @@ public class CodeHintResource { private final CodeHintService codeHintService; - private final Optional irisSettingsService; - public CodeHintResource(ProgrammingExerciseRepository programmingExerciseRepository, ProgrammingExerciseSolutionEntryRepository solutionEntryRepository, - CodeHintRepository codeHintRepository, CodeHintService codeHintService, Optional irisSettingsService) { + CodeHintRepository codeHintRepository, CodeHintService codeHintService) { this.programmingExerciseRepository = programmingExerciseRepository; this.solutionEntryRepository = solutionEntryRepository; this.codeHintRepository = codeHintRepository; this.codeHintService = codeHintService; - this.irisSettingsService = irisSettingsService; } /** @@ -98,41 +91,6 @@ public ResponseEntity> generateCodeHintsForExercise(@PathVariable return ResponseEntity.ok(codeHints); } - /** - * {@code POST programming-exercises/:exerciseId/code-hints/:codeHintId/generate-description} : Generate a description for a code hint using Iris. - * - * @param exerciseId The id of the exercise of the code hint - * @param codeHintId The id of the code hint - * @return the {@link ResponseEntity} with status {@code 200 (Ok)} and with body the updated code hint - */ - // TODO: move into some IrisResource - @Profile(PROFILE_IRIS) - @PostMapping("programming-exercises/{exerciseId}/code-hints/{codeHintId}/generate-description") - @EnforceAtLeastEditorInExercise - public ResponseEntity generateDescriptionForCodeHint(@PathVariable Long exerciseId, @PathVariable Long codeHintId) { - log.debug("REST request to generate description with Iris for CodeHint: {}", codeHintId); - - ProgrammingExercise exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); - irisSettingsService.orElseThrow().isEnabledForElseThrow(IrisSubSettingsType.HESTIA, exercise); - - // Hints for exam exercises are not supported at the moment - if (exercise.isExamExercise()) { - throw new AccessForbiddenException("Code hints for exams are currently not supported"); - } - - var codeHint = codeHintRepository.findByIdWithSolutionEntriesElseThrow(codeHintId); - if (!Objects.equals(codeHint.getExercise().getId(), exercise.getId())) { - throw new ConflictException("The code hint does not belong to the exercise", "CodeHint", "codeHintExerciseConflict"); - } - - if (codeHint.getSolutionEntries().isEmpty()) { - throw new ConflictException("The code hint does not have any solution entries", "CodeHint", "codeHintNoSolutionEntries"); - } - - codeHint = codeHintService.generateDescriptionWithIris(codeHint); - return ResponseEntity.ok(codeHint); - } - /** * {@code DELETE programming-exercises/:exerciseId/code-hints/:codeHintId/solution-entries/:solutionEntryId} : * Removes a solution entry from a code hint. diff --git a/src/main/java/de/tum/cit/aet/artemis/text/web/admin/AdminTextAssessmentEventResource.java b/src/main/java/de/tum/cit/aet/artemis/text/web/admin/AdminTextAssessmentEventResource.java index 78b6a9f8871b..acecfb3b1e56 100644 --- a/src/main/java/de/tum/cit/aet/artemis/text/web/admin/AdminTextAssessmentEventResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/text/web/admin/AdminTextAssessmentEventResource.java @@ -19,6 +19,7 @@ * REST controller for administrating TextAssessmentEventResource. */ @Profile(PROFILE_CORE) +@EnforceAdmin @RestController @RequestMapping("api/admin/") public class AdminTextAssessmentEventResource { @@ -36,7 +37,6 @@ public AdminTextAssessmentEventResource(TextAssessmentEventRepository textAssess * @return returns a List of TextAssessmentEvent's */ @GetMapping("event-insights/text-assessment/events/{courseId}") - @EnforceAdmin public ResponseEntity> getEventsByCourseId(@PathVariable Long courseId) { List events = textAssessmentEventRepository.findAllByCourseId(courseId); return ResponseEntity.ok().body(events); diff --git a/src/main/java/de/tum/cit/aet/artemis/tutorialgroup/web/TutorialGroupFreePeriodResource.java b/src/main/java/de/tum/cit/aet/artemis/tutorialgroup/web/TutorialGroupFreePeriodResource.java index d8a59414d71c..791eea50d82e 100644 --- a/src/main/java/de/tum/cit/aet/artemis/tutorialgroup/web/TutorialGroupFreePeriodResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/tutorialgroup/web/TutorialGroupFreePeriodResource.java @@ -37,6 +37,7 @@ import de.tum.cit.aet.artemis.tutorialgroup.service.TutorialGroupFreePeriodService; @Profile(PROFILE_CORE) +@FeatureToggle(Feature.TutorialGroups) @RestController @RequestMapping("api/") public class TutorialGroupFreePeriodResource { @@ -73,7 +74,6 @@ public TutorialGroupFreePeriodResource(TutorialGroupsConfigurationRepository tut */ @GetMapping("courses/{courseId}/tutorial-groups-configuration/{tutorialGroupsConfigurationId}/tutorial-free-periods/{tutorialGroupFreePeriodId}") @EnforceAtLeastInstructor - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity getOneOfConfiguration(@PathVariable Long courseId, @PathVariable Long tutorialGroupsConfigurationId, @PathVariable Long tutorialGroupFreePeriodId) { log.debug("REST request to get tutorial group free period: {} of tutorial group configuration {} of course: {}", tutorialGroupFreePeriodId, tutorialGroupsConfigurationId, @@ -96,7 +96,6 @@ public ResponseEntity getOneOfConfiguration(@PathVariab */ @PutMapping("courses/{courseId}/tutorial-groups-configuration/{tutorialGroupsConfigurationId}/tutorial-free-periods/{tutorialGroupFreePeriodId}") @EnforceAtLeastInstructor - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity update(@PathVariable Long courseId, @PathVariable Long tutorialGroupsConfigurationId, @PathVariable Long tutorialGroupFreePeriodId, @RequestBody @Valid TutorialGroupFreePeriodDTO tutorialGroupFreePeriod) throws URISyntaxException { log.debug("REST request to update TutorialGroupFreePeriod: {} for tutorial group configuration: {} of course: {}", tutorialGroupFreePeriodId, tutorialGroupsConfigurationId, @@ -144,7 +143,6 @@ public ResponseEntity update(@PathVariable Long courseI */ @PostMapping("courses/{courseId}/tutorial-groups-configuration/{tutorialGroupsConfigurationId}/tutorial-free-periods") @EnforceAtLeastInstructor - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity create(@PathVariable Long courseId, @PathVariable Long tutorialGroupsConfigurationId, @RequestBody @Valid TutorialGroupFreePeriodDTO tutorialGroupFreePeriod) throws URISyntaxException { log.debug("REST request to create TutorialGroupFreePeriod: {} for tutorial group configuration: {} of course: {}", tutorialGroupFreePeriod, tutorialGroupsConfigurationId, @@ -189,7 +187,6 @@ public ResponseEntity create(@PathVariable Long courseI */ @DeleteMapping("courses/{courseId}/tutorial-groups-configuration/{tutorialGroupsConfigurationId}/tutorial-free-periods/{tutorialGroupFreePeriodId}") @EnforceAtLeastInstructor - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity delete(@PathVariable Long courseId, @PathVariable Long tutorialGroupsConfigurationId, @PathVariable Long tutorialGroupFreePeriodId) throws URISyntaxException { log.debug("REST request to delete TutorialGroupFreePeriod: {} of tutorial group configuration {} of course: {}", tutorialGroupFreePeriodId, tutorialGroupsConfigurationId, diff --git a/src/main/java/de/tum/cit/aet/artemis/tutorialgroup/web/TutorialGroupResource.java b/src/main/java/de/tum/cit/aet/artemis/tutorialgroup/web/TutorialGroupResource.java index daa50435cf94..a82e0c9297ad 100644 --- a/src/main/java/de/tum/cit/aet/artemis/tutorialgroup/web/TutorialGroupResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/tutorialgroup/web/TutorialGroupResource.java @@ -71,6 +71,7 @@ import de.tum.cit.aet.artemis.tutorialgroup.service.TutorialGroupService; @Profile(PROFILE_CORE) +@FeatureToggle(Feature.TutorialGroups) @RestController @RequestMapping("api/") public class TutorialGroupResource { @@ -131,7 +132,6 @@ public TutorialGroupResource(AuthorizationCheckService authorizationCheckService */ @GetMapping("tutorial-groups/{tutorialGroupId}/title") @EnforceAtLeastStudent - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity getTitle(@PathVariable Long tutorialGroupId) { log.debug("REST request to get title of TutorialGroup : {}", tutorialGroupId); return tutorialGroupRepository.getTutorialGroupTitle(tutorialGroupId).map(ResponseEntity::ok) @@ -147,7 +147,6 @@ public ResponseEntity getTitle(@PathVariable Long tutorialGroupId) { */ @GetMapping("courses/{courseId}/tutorial-groups/campus-values") @EnforceAtLeastInstructor - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity> getUniqueCampusValues(@PathVariable Long courseId) { log.debug("REST request to get unique campus values used for tutorial groups in course : {}", courseId); var course = courseRepository.findByIdElseThrow(courseId); @@ -165,7 +164,6 @@ public ResponseEntity> getUniqueCampusValues(@PathVariable Long cour */ @GetMapping("courses/{courseId}/tutorial-groups/language-values") @EnforceAtLeastInstructor - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity> getUniqueLanguageValues(@PathVariable Long courseId) { log.debug("REST request to get unique language values used for tutorial groups in course : {}", courseId); var course = courseRepository.findByIdElseThrow(courseId); @@ -182,7 +180,6 @@ public ResponseEntity> getUniqueLanguageValues(@PathVariable Long co */ @GetMapping("courses/{courseId}/tutorial-groups") @EnforceAtLeastStudent - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity> getAllForCourse(@PathVariable Long courseId) { log.debug("REST request to get all tutorial groups of course with id: {}", courseId); var course = courseRepository.findByIdElseThrow(courseId); @@ -202,7 +199,6 @@ public ResponseEntity> getAllForCourse(@PathVariable Long co */ @GetMapping("courses/{courseId}/tutorial-groups/{tutorialGroupId}") @EnforceAtLeastStudent - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity getOneOfCourse(@PathVariable Long courseId, @PathVariable Long tutorialGroupId) { log.debug("REST request to get tutorial group: {} of course: {}", tutorialGroupId, courseId); var course = courseRepository.findByIdElseThrow(courseId); @@ -221,7 +217,6 @@ public ResponseEntity getOneOfCourse(@PathVariable Long courseId, */ @PostMapping("courses/{courseId}/tutorial-groups") @EnforceAtLeastInstructor - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity create(@PathVariable Long courseId, @RequestBody @Valid TutorialGroup tutorialGroup) throws URISyntaxException { log.debug("REST request to create TutorialGroup: {} in course: {}", tutorialGroup, courseId); if (tutorialGroup.getId() != null) { @@ -282,7 +277,6 @@ public ResponseEntity create(@PathVariable Long courseId, @Reques */ @DeleteMapping("courses/{courseId}/tutorial-groups/{tutorialGroupId}") @EnforceAtLeastInstructor - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity delete(@PathVariable Long courseId, @PathVariable Long tutorialGroupId) { log.info("REST request to delete a TutorialGroup: {} of course: {}", tutorialGroupId, courseId); var tutorialGroupFromDatabase = this.tutorialGroupRepository.findByIdWithTeachingAssistantAndRegistrationsElseThrow(tutorialGroupId); @@ -317,7 +311,6 @@ public record TutorialGroupUpdateDTO(@Valid @NotNull TutorialGroup tutorialGroup */ @PutMapping("courses/{courseId}/tutorial-groups/{tutorialGroupId}") @EnforceAtLeastInstructor - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity update(@PathVariable long courseId, @PathVariable long tutorialGroupId, @RequestBody @Valid TutorialGroupUpdateDTO tutorialGroupUpdateDTO) { TutorialGroup updatedTutorialGroup = tutorialGroupUpdateDTO.tutorialGroup(); @@ -405,7 +398,6 @@ public ResponseEntity update(@PathVariable long courseId, @PathVa */ @DeleteMapping("courses/{courseId}/tutorial-groups/{tutorialGroupId}/deregister/{studentLogin:" + Constants.LOGIN_REGEX + "}") @EnforceAtLeastTutor - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity deregisterStudent(@PathVariable Long courseId, @PathVariable Long tutorialGroupId, @PathVariable String studentLogin) { log.debug("REST request to deregister {} student from tutorial group : {}", studentLogin, tutorialGroupId); var tutorialGroupFromDatabase = this.tutorialGroupRepository.findByIdElseThrow(tutorialGroupId); @@ -427,7 +419,6 @@ public ResponseEntity deregisterStudent(@PathVariable Long courseId, @Path */ @PostMapping("courses/{courseId}/tutorial-groups/{tutorialGroupId}/register/{studentLogin:" + Constants.LOGIN_REGEX + "}") @EnforceAtLeastTutor - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity registerStudent(@PathVariable Long courseId, @PathVariable Long tutorialGroupId, @PathVariable String studentLogin) { log.debug("REST request to register {} student to tutorial group : {}", studentLogin, tutorialGroupId); var tutorialGroupFromDatabase = this.tutorialGroupRepository.findByIdElseThrow(tutorialGroupId); @@ -454,7 +445,6 @@ public ResponseEntity registerStudent(@PathVariable Long courseId, @PathVa */ @PostMapping("courses/{courseId}/tutorial-groups/{tutorialGroupId}/register-multiple") @EnforceAtLeastInstructor - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity> registerMultipleStudentsToTutorialGroup(@PathVariable long courseId, @PathVariable long tutorialGroupId, @RequestBody Set studentDtos) { log.debug("REST request to register {} to tutorial group {}", studentDtos, tutorialGroupId); @@ -476,7 +466,6 @@ public ResponseEntity> registerMultipleStudentsToTutorialGroup(@ */ @PostMapping("courses/{courseId}/tutorial-groups/import") @EnforceAtLeastInstructor - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity> importRegistrations(@PathVariable Long courseId, @RequestBody @Valid Set importDTOs) { log.debug("REST request to import registrations {} to course {}", importDTOs, courseId); @@ -549,7 +538,6 @@ private void checkEntityIdMatchesPathIds(TutorialGroup tutorialGroup, Optional exportTutorialGroupsToCSV(@PathVariable Long courseId, @RequestParam List fields) { log.debug("REST request to export TutorialGroups to CSV for course: {}", courseId); var course = courseRepository.findByIdElseThrow(courseId); @@ -580,7 +568,6 @@ public ResponseEntity exportTutorialGroupsToCSV(@PathVariable Long cours */ @GetMapping(value = "courses/{courseId}/tutorial-groups/export/json", produces = MediaType.APPLICATION_JSON_VALUE) @EnforceAtLeastInstructorInCourse - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity exportTutorialGroupsToJSON(@PathVariable Long courseId, @RequestParam List fields) { log.debug("REST request to export TutorialGroups to JSON for course: {}", courseId); try { diff --git a/src/main/java/de/tum/cit/aet/artemis/tutorialgroup/web/TutorialGroupSessionResource.java b/src/main/java/de/tum/cit/aet/artemis/tutorialgroup/web/TutorialGroupSessionResource.java index 54790e628ba9..4d9c9459f65c 100644 --- a/src/main/java/de/tum/cit/aet/artemis/tutorialgroup/web/TutorialGroupSessionResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/tutorialgroup/web/TutorialGroupSessionResource.java @@ -56,6 +56,7 @@ import de.tum.cit.aet.artemis.tutorialgroup.service.TutorialGroupService; @Profile(PROFILE_CORE) +@FeatureToggle(Feature.TutorialGroups) @RestController @RequestMapping("api/") public class TutorialGroupSessionResource { @@ -101,7 +102,6 @@ public TutorialGroupSessionResource(TutorialGroupSessionRepository tutorialGroup */ @GetMapping("courses/{courseId}/tutorial-groups/{tutorialGroupId}/sessions/{sessionId}") @EnforceAtLeastStudent - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity getOneOfTutorialGroup(@PathVariable Long courseId, @PathVariable Long tutorialGroupId, @PathVariable Long sessionId) { log.debug("REST request to get session: {} of tutorial group: {} of course {}", sessionId, tutorialGroupId, courseId); var session = tutorialGroupSessionRepository.findByIdElseThrow(sessionId); @@ -124,7 +124,6 @@ public ResponseEntity getOneOfTutorialGroup(@PathVariable */ @PutMapping("courses/{courseId}/tutorial-groups/{tutorialGroupId}/sessions/{sessionId}") @EnforceAtLeastTutor - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity update(@PathVariable Long courseId, @PathVariable Long tutorialGroupId, @PathVariable Long sessionId, @RequestBody @Valid TutorialGroupSessionDTO tutorialGroupSessionDTO) { log.debug("REST request to update session: {} of tutorial group: {} of course {}", sessionId, tutorialGroupId, courseId); @@ -170,7 +169,6 @@ public ResponseEntity update(@PathVariable Long courseId, */ @PatchMapping("courses/{courseId}/tutorial-groups/{tutorialGroupId}/sessions/{sessionId}/attendance-count") @EnforceAtLeastTutor - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity updateAttendanceCount(@PathVariable Long courseId, @PathVariable Long tutorialGroupId, @PathVariable Long sessionId, @RequestParam(required = false) @Min(0) @Max(3000) Integer attendanceCount) { log.debug("REST request to update attendance count of session: {} of tutorial group: {} of course {} to {}", sessionId, tutorialGroupId, courseId, attendanceCount); @@ -192,7 +190,6 @@ public ResponseEntity updateAttendanceCount(@PathVariable */ @DeleteMapping("courses/{courseId}/tutorial-groups/{tutorialGroupId}/sessions/{sessionId}") @EnforceAtLeastTutor - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity deleteSession(@PathVariable Long courseId, @PathVariable Long tutorialGroupId, @PathVariable Long sessionId) { log.debug("REST request to delete session: {} of tutorial group: {} of course {}", sessionId, tutorialGroupId, courseId); var sessionFromDatabase = this.tutorialGroupSessionRepository.findByIdElseThrow(sessionId); @@ -212,7 +209,6 @@ public ResponseEntity deleteSession(@PathVariable Long courseId, @PathVari */ @PostMapping("courses/{courseId}/tutorial-groups/{tutorialGroupId}/sessions") @EnforceAtLeastTutor - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity create(@PathVariable Long courseId, @PathVariable Long tutorialGroupId, @RequestBody @Valid TutorialGroupSessionDTO tutorialGroupSessionDTO) throws URISyntaxException { log.debug("REST request to create TutorialGroupSession: {} for tutorial group: {}", tutorialGroupSessionDTO, tutorialGroupId); @@ -255,7 +251,6 @@ private TutorialGroupsConfiguration validateTutorialGroupConfiguration(@PathVari */ @PostMapping("courses/{courseId}/tutorial-groups/{tutorialGroupId}/sessions/{sessionId}/cancel") @EnforceAtLeastTutor - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity cancel(@PathVariable Long courseId, @PathVariable Long tutorialGroupId, @PathVariable Long sessionId, @RequestBody TutorialGroupStatusDTO tutorialGroupStatusDTO) throws URISyntaxException { log.debug("REST request to cancel session: {} of tutorial group: {} of course {}", sessionId, tutorialGroupId, courseId); @@ -283,7 +278,6 @@ public ResponseEntity cancel(@PathVariable Long courseId, */ @PostMapping("courses/{courseId}/tutorial-groups/{tutorialGroupId}/sessions/{sessionId}/activate") @EnforceAtLeastTutor - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity activate(@PathVariable long courseId, @PathVariable long tutorialGroupId, @PathVariable long sessionId) throws URISyntaxException { log.debug("REST request to activate session: {} of tutorial group: {} of course {}", sessionId, tutorialGroupId, courseId); var sessionToActivate = tutorialGroupSessionRepository.findByIdElseThrow(sessionId); diff --git a/src/main/java/de/tum/cit/aet/artemis/tutorialgroup/web/TutorialGroupsConfigurationResource.java b/src/main/java/de/tum/cit/aet/artemis/tutorialgroup/web/TutorialGroupsConfigurationResource.java index 4e9c9212ee4b..dc26c5bd4e09 100644 --- a/src/main/java/de/tum/cit/aet/artemis/tutorialgroup/web/TutorialGroupsConfigurationResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/tutorialgroup/web/TutorialGroupsConfigurationResource.java @@ -36,6 +36,7 @@ import de.tum.cit.aet.artemis.tutorialgroup.service.TutorialGroupChannelManagementService; @Profile(PROFILE_CORE) +@FeatureToggle(Feature.TutorialGroups) @RestController @RequestMapping("api/") public class TutorialGroupsConfigurationResource { @@ -68,7 +69,6 @@ public TutorialGroupsConfigurationResource(TutorialGroupsConfigurationRepository */ @GetMapping("courses/{courseId}/tutorial-groups-configuration") @EnforceAtLeastStudent - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity getOneOfCourse(@PathVariable Long courseId) { log.debug("REST request to get tutorial groups configuration of course: {}", courseId); var course = courseRepository.findByIdElseThrow(courseId); @@ -85,7 +85,6 @@ public ResponseEntity getOneOfCourse(@PathVariable */ @PostMapping("courses/{courseId}/tutorial-groups-configuration") @EnforceAtLeastInstructor - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity create(@PathVariable Long courseId, @RequestBody @Valid TutorialGroupsConfiguration tutorialGroupsConfiguration) throws URISyntaxException { log.debug("REST request to create TutorialGroupsConfiguration: {} for course: {}", tutorialGroupsConfiguration, courseId); @@ -120,7 +119,6 @@ public ResponseEntity create(@PathVariable Long cou */ @PutMapping("courses/{courseId}/tutorial-groups-configuration/{tutorialGroupsConfigurationId}") @EnforceAtLeastInstructor - @FeatureToggle(Feature.TutorialGroups) public ResponseEntity update(@PathVariable Long courseId, @PathVariable Long tutorialGroupsConfigurationId, @RequestBody @Valid TutorialGroupsConfiguration updatedTutorialGroupConfiguration) { log.debug("REST request to update TutorialGroupsConfiguration: {} of course: {}", updatedTutorialGroupConfiguration, courseId); diff --git a/src/main/resources/config/application.yml b/src/main/resources/config/application.yml index 924d087ec8f2..3924e2d804f9 100644 --- a/src/main/resources/config/application.yml +++ b/src/main/resources/config/application.yml @@ -91,6 +91,8 @@ artemis: default: "ghcr.io/ls1intum/artemis-rust-docker:v0.9.70" javascript: default: "ghcr.io/ls1intum/artemis-javascript-docker:v1.0.0" + r: + default: "ghcr.io/ls1intum/artemis-r-docker:v1.0.0" management: endpoints: diff --git a/src/main/resources/config/liquibase/changelog/20240825191919_changelog.xml b/src/main/resources/config/liquibase/changelog/20240825191919_changelog.xml new file mode 100644 index 000000000000..3ae7fd7ea038 --- /dev/null +++ b/src/main/resources/config/liquibase/changelog/20240825191919_changelog.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + DELETE FROM iris_sub_settings WHERE discriminator = 'HESTIA'; + + + + + + + + + + + UPDATE iris_sub_settings + SET allowed_variants = 'default', selected_variant = 'default' + WHERE id IN ( + SELECT iris_chat_settings_id FROM iris_settings WHERE discriminator = 'GLOBAL' + UNION + SELECT iris_competency_generation_settings_id FROM iris_settings WHERE discriminator = 'GLOBAL' + UNION + SELECT iris_lecture_ingestion_settings_id FROM iris_settings WHERE discriminator = 'GLOBAL' + ); + + + + + + + + + + + + + + DELETE FROM iris_json_message_content WHERE id IN ( + SELECT iris_message_content.id FROM iris_message_content + JOIN iris_message ON iris_message_content.message_id = iris_message.id + JOIN iris_session ON iris_message.session_id = iris_session.id + WHERE iris_session.discriminator = 'HESTIA' + ); + DELETE FROM iris_text_message_content WHERE id IN ( + SELECT iris_message_content.id FROM iris_message_content + JOIN iris_message ON iris_message_content.message_id = iris_message.id + JOIN iris_session ON iris_message.session_id = iris_session.id + WHERE iris_session.discriminator = 'HESTIA' + ); + DELETE FROM iris_message_content WHERE message_id IN ( + SELECT iris_message.id FROM iris_message + JOIN iris_session ON iris_message.session_id = iris_session.id + WHERE iris_session.discriminator = 'HESTIA' + ); + DELETE FROM iris_message WHERE session_id IN ( + SELECT id FROM iris_session WHERE discriminator = 'HESTIA' + ); + DELETE FROM iris_session WHERE discriminator = 'HESTIA'; + + + + + + + diff --git a/src/main/resources/config/liquibase/master.xml b/src/main/resources/config/liquibase/master.xml index 49f3eeee4d63..b560fb1047fe 100644 --- a/src/main/resources/config/liquibase/master.xml +++ b/src/main/resources/config/liquibase/master.xml @@ -22,6 +22,7 @@ + diff --git a/src/main/resources/templates/aeolus/r/default.sh b/src/main/resources/templates/aeolus/r/default.sh new file mode 100644 index 000000000000..1d0b32e87105 --- /dev/null +++ b/src/main/resources/templates/aeolus/r/default.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -e +export AEOLUS_INITIAL_DIRECTORY=${PWD} +install () { + echo '⚙️ executing install' + R CMD INSTALL assignment +} + +run_all_tests () { + echo '⚙️ executing run_all_tests' + Rscript -e 'library("testthat"); options(testthat.output_file = "junit.xml"); test_local(".", reporter = "junit")' +} + +main () { + if [[ "${1}" == "aeolus_sourcing" ]]; then + return 0 # just source to use the methods in the subshell, no execution + fi + local _script_name + _script_name=${BASH_SOURCE[0]:-$0} + cd "${AEOLUS_INITIAL_DIRECTORY}" + bash -c "source ${_script_name} aeolus_sourcing; install" + cd "${AEOLUS_INITIAL_DIRECTORY}" + bash -c "source ${_script_name} aeolus_sourcing; run_all_tests" +} + +main "${@}" diff --git a/src/main/resources/templates/aeolus/r/default.yaml b/src/main/resources/templates/aeolus/r/default.yaml new file mode 100644 index 000000000000..a41d23c6f012 --- /dev/null +++ b/src/main/resources/templates/aeolus/r/default.yaml @@ -0,0 +1,14 @@ +api: v0.0.1 +metadata: + name: R + id: r + description: Test package using testthat +actions: + - name: install + script: R CMD INSTALL assignment + - name: run_all_tests + script: Rscript -e 'library("testthat"); options(testthat.output_file = "junit.xml"); test_local(".", reporter = "junit")' + results: + - name: junit + path: tests/testthat/junit.xml + type: junit diff --git a/src/main/resources/templates/jenkins/r/regularRuns/pipeline.groovy b/src/main/resources/templates/jenkins/r/regularRuns/pipeline.groovy new file mode 100644 index 000000000000..9a2ec97b5843 --- /dev/null +++ b/src/main/resources/templates/jenkins/r/regularRuns/pipeline.groovy @@ -0,0 +1,59 @@ +/* + * This file configures the actual build steps for the automatic grading. + * + * !!! + * For regular exercises, there is no need to make changes to this file. + * Only this base configuration is actively supported by the Artemis maintainers + * and/or your Artemis instance administrators. + * !!! + */ + +dockerImage = '#dockerImage' +dockerFlags = '#dockerArgs' + +/** + * Main function called by Jenkins. + */ +void testRunner() { + docker.image(dockerImage).inside(dockerFlags) { c -> + runTestSteps() + } +} + +private void runTestSteps() { + test() +} + +/** + * Run unit tests + */ +private void test() { + stage('Test') { + sh ''' + R CMD INSTALL assignment + Rscript -e 'library("testthat"); options(testthat.output_file = "junit.xml"); test_local(".", reporter = "junit")' + ''' + } +} + +/** + * Script of the post build tasks aggregating all JUnit files in $WORKSPACE/results. + * + * Called by Jenkins. + */ +void postBuildTasks() { + sh ''' + rm -rf results + mkdir results + if [ -e tests/testthat/junit.xml ] + then + sed -i 's/]*>//g ; s/<\\/testsuites>/<\\/testsuite>/g' tests/testthat/junit.xml + fi + cp tests/testthat/junit.xml $WORKSPACE/results/ || true + sed -i 's/[^[:print:]\t]/�/g' $WORKSPACE/results/*.xml || true + ''' +} + +// very important, do not remove +// required so that Jenkins finds the methods defined in this script +return this diff --git a/src/main/resources/templates/r/exercise/DESCRIPTION b/src/main/resources/templates/r/exercise/DESCRIPTION new file mode 100644 index 000000000000..2933cb767621 --- /dev/null +++ b/src/main/resources/templates/r/exercise/DESCRIPTION @@ -0,0 +1,7 @@ +Package: assignment +Title: Artemis R Student Assignment +Version: 0.0.0.9000 +Author: Artemis +Description: This is an assignment to be solved by students. +License: MIT +Encoding: UTF-8 diff --git a/src/main/resources/templates/r/exercise/NAMESPACE b/src/main/resources/templates/r/exercise/NAMESPACE new file mode 100644 index 000000000000..9c9f9ac2d917 --- /dev/null +++ b/src/main/resources/templates/r/exercise/NAMESPACE @@ -0,0 +1 @@ +exportPattern("^[^\\.]") diff --git a/src/main/resources/templates/r/exercise/R/convert.R b/src/main/resources/templates/r/exercise/R/convert.R new file mode 100644 index 000000000000..28e787cf2967 --- /dev/null +++ b/src/main/resources/templates/r/exercise/R/convert.R @@ -0,0 +1,3 @@ +matrix_to_column_list <- function(mat) { + # TODO: implement +} diff --git a/src/main/resources/templates/r/readme b/src/main/resources/templates/r/readme new file mode 100644 index 000000000000..73377139d293 --- /dev/null +++ b/src/main/resources/templates/r/readme @@ -0,0 +1,6 @@ +# Matrix Columns + +Write a function `matrix_to_column_list` in R that takes a matrix of any shape and converts it into a list of +column-vectors. Each element of the list should represent a column of the matrix. + +1. [task][Convert to column-vectors](converts_3x3_matrix_to_vectors,converts_4x2_matrix_to_vectors,converts_1x5_matrix_to_scalars,converts_5x1_matrix_to_vector) diff --git a/src/main/resources/templates/r/solution/DESCRIPTION b/src/main/resources/templates/r/solution/DESCRIPTION new file mode 100644 index 000000000000..2933cb767621 --- /dev/null +++ b/src/main/resources/templates/r/solution/DESCRIPTION @@ -0,0 +1,7 @@ +Package: assignment +Title: Artemis R Student Assignment +Version: 0.0.0.9000 +Author: Artemis +Description: This is an assignment to be solved by students. +License: MIT +Encoding: UTF-8 diff --git a/src/main/resources/templates/r/solution/NAMESPACE b/src/main/resources/templates/r/solution/NAMESPACE new file mode 100644 index 000000000000..9c9f9ac2d917 --- /dev/null +++ b/src/main/resources/templates/r/solution/NAMESPACE @@ -0,0 +1 @@ +exportPattern("^[^\\.]") diff --git a/src/main/resources/templates/r/solution/R/convert.R b/src/main/resources/templates/r/solution/R/convert.R new file mode 100644 index 000000000000..7d701772ab7b --- /dev/null +++ b/src/main/resources/templates/r/solution/R/convert.R @@ -0,0 +1,17 @@ +matrix_to_column_list <- function(mat) { + if (!is.matrix(mat)) { + stop("Input must be a matrix") + } + + n_cols <- ncol(mat) + + # Initialize an empty list to store column-vectors + column_list <- vector("list", length = n_cols) + + # Loop through each column and store it in the list + for (i in 1:n_cols) { + column_list[[i]] <- mat[, i] + } + + return(column_list) +} diff --git a/src/main/resources/templates/r/test/DESCRIPTION b/src/main/resources/templates/r/test/DESCRIPTION new file mode 100644 index 000000000000..e19a2b735419 --- /dev/null +++ b/src/main/resources/templates/r/test/DESCRIPTION @@ -0,0 +1,14 @@ +Package: test +Title: Artemis R Tests +Version: 0.0.0.9000 +Author: Artemis +Description: This package tests the student assignment. +License: MIT +Encoding: UTF-8 +Imports: + assignment +Remotes: + local::./assignment +Suggests: + testthat (>= 3.0.0) +Config/testthat/edition: 3 diff --git a/src/main/resources/templates/r/test/tests/testthat.R b/src/main/resources/templates/r/test/tests/testthat.R new file mode 100644 index 000000000000..388438828173 --- /dev/null +++ b/src/main/resources/templates/r/test/tests/testthat.R @@ -0,0 +1,12 @@ +# This file is part of the standard setup for testthat. +# It is recommended that you do not modify it. +# +# Where should you do additional test configuration? +# Learn more about the roles of various files in: +# * https://r-pkgs.org/testing-design.html#sec-tests-files-overview +# * https://testthat.r-lib.org/articles/special-files.html + +library(testthat) +library(tests) + +test_check("tests") diff --git a/src/main/resources/templates/r/test/tests/testthat/test-convert.R b/src/main/resources/templates/r/test/tests/testthat/test-convert.R new file mode 100644 index 000000000000..a84a0e879711 --- /dev/null +++ b/src/main/resources/templates/r/test/tests/testthat/test-convert.R @@ -0,0 +1,47 @@ +test_that("converts_3x3_matrix_to_vectors", { + mat <- matrix(c(5, 8, 11, 6, 9, 12, 7, 10, 13), nrow = 3, ncol = 3) + + result <- assignment::matrix_to_column_list(mat) + + # Make sure to only use exactly one "expect_" function per test + expect_equal(result, list( + c(5, 8, 11), + c(6, 9, 12), + c(7, 10, 13) + )) +}) + +test_that("converts_4x2_matrix_to_vectors", { + mat <- matrix(c(13, 13, 5, 18, 11, 4, 7, 10), nrow = 4, ncol = 2) + + result <- assignment::matrix_to_column_list(mat) + + expect_equal(result, list( + c(13, 13, 5, 18), + c(11, 4, 7, 10) + )) +}) + +test_that("converts_1x5_matrix_to_scalars", { + mat <- matrix(c(16, 10, 15, 8, 7), nrow = 1, ncol = 5) + + result <- assignment::matrix_to_column_list(mat) + + expect_equal(result, list( + 16, + 10, + 15, + 8, + 7 + )) +}) + +test_that("converts_5x1_matrix_to_vector", { + mat <- matrix(c(14, 9, 1, 3, 4), nrow = 5, ncol = 1) + + result <- assignment::matrix_to_column_list(mat) + + expect_equal(result, list( + c(14, 9, 1, 3, 4) + )) +}) diff --git a/src/main/webapp/app/core/user/user.model.ts b/src/main/webapp/app/core/user/user.model.ts index b55ff839fd54..f793581b21a3 100644 --- a/src/main/webapp/app/core/user/user.model.ts +++ b/src/main/webapp/app/core/user/user.model.ts @@ -16,6 +16,7 @@ export class User extends Account { public vcsAccessToken?: string; public vcsAccessTokenExpiryDate?: string; public sshPublicKey?: string; + public sshKeyHash?: string; public irisAccepted?: dayjs.Dayjs; constructor( diff --git a/src/main/webapp/app/course/manage/course-admin.service.ts b/src/main/webapp/app/course/manage/course-admin.service.ts index fe7ceef20670..1185c439a8d2 100644 --- a/src/main/webapp/app/course/manage/course-admin.service.ts +++ b/src/main/webapp/app/course/manage/course-admin.service.ts @@ -5,6 +5,7 @@ import { map } from 'rxjs/operators'; import { Course } from 'app/entities/course.model'; import { objectToJsonBlob } from 'app/utils/blob-util'; import { CourseManagementService } from 'app/course/manage/course-management.service'; +import { CourseDeletionSummaryDTO } from 'app/entities/course-deletion-summary.model'; export type EntityResponseType = HttpResponse; export type EntityArrayResponseType = HttpResponse; @@ -51,4 +52,12 @@ export class CourseAdminService { delete(courseId: number): Observable> { return this.http.delete(`${this.resourceUrl}/${courseId}`, { observe: 'response' }); } + + /** + * Returns a summary for the course providing information potentially relevant for the deletion. + * @param courseId - the id of the course to get the deletion summary for + */ + getDeletionSummary(courseId: number): Observable> { + return this.http.get(`${this.resourceUrl}/${courseId}/deletion-summary`, { observe: 'response' }); + } } diff --git a/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.html b/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.html index abbfdaf7c010..bc10fd753a7c 100644 --- a/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.html +++ b/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.html @@ -107,6 +107,8 @@ [buttonSize]="ButtonSize.MEDIUM" jhiDeleteButton [entityTitle]="course.title || ''" + entitySummaryTitle="artemisApp.course.delete.summary.title" + [fetchEntitySummary]="fetchCourseDeletionSummary()" deleteQuestion="artemisApp.course.delete.question" deleteConfirmationText="artemisApp.course.delete.typeNameToConfirm" (delete)="deleteCourse(course.id!)" diff --git a/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.ts b/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.ts index c25c182067e3..e4b3865d3dbc 100644 --- a/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.ts +++ b/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.ts @@ -1,7 +1,7 @@ import { AfterViewInit, Component, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { HttpErrorResponse } from '@angular/common/http'; -import { Subject, Subscription } from 'rxjs'; +import { Observable, Subject, Subscription, map, of } from 'rxjs'; import { Course, isCommunicationEnabled } from 'app/entities/course.model'; import { CourseManagementService } from 'app/course/manage/course-management.service'; import { ButtonSize } from 'app/shared/components/button.component'; @@ -34,6 +34,8 @@ import { ProfileService } from 'app/shared/layouts/profiles/profile.service'; import { PROFILE_IRIS, PROFILE_LOCALCI, PROFILE_LTI } from 'app/app.constants'; import { CourseAccessStorageService } from 'app/course/course-access-storage.service'; import { scrollToTopOfPage } from 'app/shared/util/utils'; +import { ExerciseType } from 'app/entities/exercise.model'; +import { EntitySummary } from 'app/shared/delete-dialog/delete-dialog.model'; @Component({ selector: 'jhi-course-management-tab-bar', @@ -200,4 +202,68 @@ export class CourseManagementTabBarComponent implements OnInit, OnDestroy, After const courseManagementRegex = /course-management\/[0-9]+(\/edit)?$/; return courseManagementRegex.test(this.router.url); } + + private getExistingSummaryEntries(): EntitySummary { + const numberRepositories = + this.course?.exercises + ?.filter((exercise) => exercise.type === 'programming') + .map((exercise) => exercise?.numberOfParticipations ?? 0) + .reduce((repositorySum, numberOfParticipationsForRepository) => repositorySum + numberOfParticipationsForRepository, 0) ?? 0; + + const numberOfExercisesPerType = new Map(); + this.course?.exercises?.forEach((exercise) => { + if (exercise.type === undefined) { + return; + } + const oldValue = numberOfExercisesPerType.get(exercise.type) ?? 0; + numberOfExercisesPerType.set(exercise.type, oldValue + 1); + }); + + const numberExams = this.course?.numberOfExams ?? 0; + const numberLectures = this.course?.lectures?.length ?? 0; + const numberStudents = this.course?.numberOfStudents ?? 0; + const numberTutors = this.course?.numberOfTeachingAssistants ?? 0; + const numberEditors = this.course?.numberOfEditors ?? 0; + const numberInstructors = this.course?.numberOfInstructors ?? 0; + const isTestCourse = this.course?.testCourse; + + return { + 'artemisApp.course.delete.summary.numberRepositories': numberRepositories, + 'artemisApp.course.delete.summary.numberProgrammingExercises': numberOfExercisesPerType.get(ExerciseType.PROGRAMMING) ?? 0, + 'artemisApp.course.delete.summary.numberModelingExercises': numberOfExercisesPerType.get(ExerciseType.MODELING) ?? 0, + 'artemisApp.course.delete.summary.numberTextExercises': numberOfExercisesPerType.get(ExerciseType.TEXT) ?? 0, + 'artemisApp.course.delete.summary.numberFileUploadExercises': numberOfExercisesPerType.get(ExerciseType.FILE_UPLOAD) ?? 0, + 'artemisApp.course.delete.summary.numberQuizExercises': numberOfExercisesPerType.get(ExerciseType.QUIZ) ?? 0, + 'artemisApp.course.delete.summary.numberExams': numberExams, + 'artemisApp.course.delete.summary.numberLectures': numberLectures, + 'artemisApp.course.delete.summary.numberStudents': numberStudents, + 'artemisApp.course.delete.summary.numberTutors': numberTutors, + 'artemisApp.course.delete.summary.numberEditors': numberEditors, + 'artemisApp.course.delete.summary.numberInstructors': numberInstructors, + 'artemisApp.course.delete.summary.isTestCourse': isTestCourse, + }; + } + + fetchCourseDeletionSummary(): Observable { + if (this.course?.id === undefined) { + return of({}); + } + + return this.courseAdminService.getDeletionSummary(this.course.id).pipe( + map((response) => { + const summary = response.body; + + if (summary === null) { + return {}; + } + + return { + ...this.getExistingSummaryEntries(), + 'artemisApp.course.delete.summary.numberBuilds': summary.numberOfBuilds, + 'artemisApp.course.delete.summary.numberCommunicationPosts': summary.numberOfCommunicationPosts, + 'artemisApp.course.delete.summary.numberAnswerPosts': summary.numberOfAnswerPosts, + }; + }), + ); + } } diff --git a/src/main/webapp/app/course/manage/detail/course-detail.component.ts b/src/main/webapp/app/course/manage/detail/course-detail.component.ts index 5954f3c0b7c0..29858e79b334 100644 --- a/src/main/webapp/app/course/manage/detail/course-detail.component.ts +++ b/src/main/webapp/app/course/manage/detail/course-detail.component.ts @@ -50,7 +50,6 @@ export class CourseDetailComponent implements OnInit, OnDestroy { communicationEnabled: boolean; irisEnabled = false; irisChatEnabled = false; - irisHestiaEnabled = false; ltiEnabled = false; isAthenaEnabled = false; tutorialEnabled = false; @@ -96,7 +95,6 @@ export class CourseDetailComponent implements OnInit, OnDestroy { if (this.irisEnabled) { const irisSettings = await firstValueFrom(this.irisSettingsService.getGlobalSettings()); this.irisChatEnabled = irisSettings?.irisChatSettings?.enabled ?? false; - this.irisHestiaEnabled = irisSettings?.irisHestiaSettings?.enabled ?? false; } this.route.data.subscribe(({ course }) => { if (course) { diff --git a/src/main/webapp/app/entities/course-deletion-summary.model.ts b/src/main/webapp/app/entities/course-deletion-summary.model.ts new file mode 100644 index 000000000000..606a9d440d49 --- /dev/null +++ b/src/main/webapp/app/entities/course-deletion-summary.model.ts @@ -0,0 +1,5 @@ +export interface CourseDeletionSummaryDTO { + numberOfBuilds: number; + numberOfCommunicationPosts: number; + numberOfAnswerPosts: number; +} diff --git a/src/main/webapp/app/entities/exam-deletion-summary.model.ts b/src/main/webapp/app/entities/exam-deletion-summary.model.ts new file mode 100644 index 000000000000..ed98a32d1628 --- /dev/null +++ b/src/main/webapp/app/entities/exam-deletion-summary.model.ts @@ -0,0 +1,9 @@ +export interface ExamDeletionSummaryDTO { + numberOfBuilds: number; + numberOfCommunicationPosts: number; + numberOfAnswerPosts: number; + numberRegisteredStudents: number; + numberNotStartedExams: number; + numberStartedExams: number; + numberSubmittedExams: number; +} diff --git a/src/main/webapp/app/entities/iris/settings/iris-settings.model.ts b/src/main/webapp/app/entities/iris/settings/iris-settings.model.ts index 017c3ea2cffa..ab7f3b475908 100644 --- a/src/main/webapp/app/entities/iris/settings/iris-settings.model.ts +++ b/src/main/webapp/app/entities/iris/settings/iris-settings.model.ts @@ -1,10 +1,5 @@ import { BaseEntity } from 'app/shared/model/base-entity'; -import { - IrisChatSubSettings, - IrisCompetencyGenerationSubSettings, - IrisHestiaSubSettings, - IrisLectureIngestionSubSettings, -} from 'app/entities/iris/settings/iris-sub-settings.model'; +import { IrisChatSubSettings, IrisCompetencyGenerationSubSettings, IrisLectureIngestionSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; export enum IrisSettingsType { GLOBAL = 'global', @@ -17,21 +12,14 @@ export abstract class IrisSettings implements BaseEntity { type: IrisSettingsType; irisChatSettings?: IrisChatSubSettings; irisLectureIngestionSettings?: IrisLectureIngestionSubSettings; - irisHestiaSettings?: IrisHestiaSubSettings; irisCompetencyGenerationSettings?: IrisCompetencyGenerationSubSettings; } export class IrisGlobalSettings implements IrisSettings { id?: number; type = IrisSettingsType.GLOBAL; - currentVersion?: number; - enableAutoUpdateChat?: boolean; - enableAutoUpdateLectureIngestion?: boolean; - enableAutoUpdateHestia?: boolean; - enableAutoUpdateCompetencyGeneration?: boolean; irisChatSettings?: IrisChatSubSettings; irisLectureIngestionSettings?: IrisLectureIngestionSubSettings; - irisHestiaSettings?: IrisHestiaSubSettings; irisCompetencyGenerationSettings?: IrisCompetencyGenerationSubSettings; } @@ -41,7 +29,6 @@ export class IrisCourseSettings implements IrisSettings { courseId?: number; irisChatSettings?: IrisChatSubSettings; irisLectureIngestionSettings?: IrisLectureIngestionSubSettings; - irisHestiaSettings?: IrisHestiaSubSettings; irisCompetencyGenerationSettings?: IrisCompetencyGenerationSubSettings; } diff --git a/src/main/webapp/app/entities/iris/settings/iris-sub-settings.model.ts b/src/main/webapp/app/entities/iris/settings/iris-sub-settings.model.ts index 8848394f1350..626155a43555 100644 --- a/src/main/webapp/app/entities/iris/settings/iris-sub-settings.model.ts +++ b/src/main/webapp/app/entities/iris/settings/iris-sub-settings.model.ts @@ -1,9 +1,7 @@ import { BaseEntity } from 'app/shared/model/base-entity'; -import { IrisTemplate } from 'app/entities/iris/settings/iris-template'; export enum IrisSubSettingsType { CHAT = 'chat', - HESTIA = 'hestia', COMPETENCY_GENERATION = 'competency-generation', LECTURE_INGESTION = 'lecture-ingestion', } @@ -12,13 +10,12 @@ export abstract class IrisSubSettings implements BaseEntity { id?: number; type: IrisSubSettingsType; enabled = false; - allowedModels?: string[]; - preferredModel?: string; + allowedVariants?: string[]; + selectedVariant?: string; } export class IrisChatSubSettings extends IrisSubSettings { type = IrisSubSettingsType.CHAT; - template?: IrisTemplate; rateLimit?: number; rateLimitTimeframeHours?: number; } @@ -28,12 +25,6 @@ export class IrisLectureIngestionSubSettings extends IrisSubSettings { autoIngestOnLectureAttachmentUpload: boolean; } -export class IrisHestiaSubSettings extends IrisSubSettings { - type = IrisSubSettingsType.HESTIA; - template?: IrisTemplate; -} - export class IrisCompetencyGenerationSubSettings extends IrisSubSettings { type = IrisSubSettingsType.COMPETENCY_GENERATION; - template?: IrisTemplate; } diff --git a/src/main/webapp/app/entities/iris/settings/iris-template.ts b/src/main/webapp/app/entities/iris/settings/iris-template.ts deleted file mode 100644 index eb0c8a90041c..000000000000 --- a/src/main/webapp/app/entities/iris/settings/iris-template.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { BaseEntity } from 'app/shared/model/base-entity'; - -export class IrisTemplate implements BaseEntity { - id?: number; - content = ''; -} diff --git a/src/main/webapp/app/entities/iris/settings/iris-model.ts b/src/main/webapp/app/entities/iris/settings/iris-variant.ts similarity index 69% rename from src/main/webapp/app/entities/iris/settings/iris-model.ts rename to src/main/webapp/app/entities/iris/settings/iris-variant.ts index 94a7f9202d92..3fbecfee8c49 100644 --- a/src/main/webapp/app/entities/iris/settings/iris-model.ts +++ b/src/main/webapp/app/entities/iris/settings/iris-variant.ts @@ -1,4 +1,4 @@ -export class IrisModel { +export class IrisVariant { id: string; name: string; description: string; diff --git a/src/main/webapp/app/entities/programming/programming-exercise.model.ts b/src/main/webapp/app/entities/programming/programming-exercise.model.ts index ef2d95985068..17d04e971160 100644 --- a/src/main/webapp/app/entities/programming/programming-exercise.model.ts +++ b/src/main/webapp/app/entities/programming/programming-exercise.model.ts @@ -13,18 +13,19 @@ import { SubmissionPolicy } from 'app/entities/submission-policy.model'; import dayjs from 'dayjs/esm'; export enum ProgrammingLanguage { - JAVA = 'JAVA', - PYTHON = 'PYTHON', + EMPTY = 'EMPTY', + ASSEMBLER = 'ASSEMBLER', C = 'C', HASKELL = 'HASKELL', + JAVA = 'JAVA', + JAVASCRIPT = 'JAVASCRIPT', KOTLIN = 'KOTLIN', - VHDL = 'VHDL', - ASSEMBLER = 'ASSEMBLER', - SWIFT = 'SWIFT', OCAML = 'OCAML', - EMPTY = 'EMPTY', + PYTHON = 'PYTHON', + R = 'R', RUST = 'RUST', - JAVASCRIPT = 'JAVASCRIPT', + SWIFT = 'SWIFT', + VHDL = 'VHDL', } export enum ProjectType { diff --git a/src/main/webapp/app/exam/manage/exam-management.service.ts b/src/main/webapp/app/exam/manage/exam-management.service.ts index ef0a74bd28fc..24425d3a530e 100644 --- a/src/main/webapp/app/exam/manage/exam-management.service.ts +++ b/src/main/webapp/app/exam/manage/exam-management.service.ts @@ -21,6 +21,7 @@ import { EntityTitleService, EntityType } from 'app/shared/layouts/navbar/entity import { ExamExerciseStartPreparationStatus } from 'app/exam/manage/student-exams/student-exams.component'; import { Exercise } from 'app/entities/exercise.model'; import { ExamWideAnnouncementEvent } from 'app/exam/participate/exam-participation-live-events.service'; +import { ExamDeletionSummaryDTO } from 'app/entities/exam-deletion-summary.model'; type EntityResponseType = HttpResponse; type EntityArrayResponseType = HttpResponse; @@ -208,6 +209,15 @@ export class ExamManagementService { ); } + /** + * Returns a summary for the exam providing information potentially relevant for the deletion. + * @param courseId The course id. + * @param examId The exam id. + */ + getDeletionSummary(courseId: number, examId: number): Observable> { + return this.http.get(`${this.resourceUrl}/${courseId}/exams/${examId}/deletion-summary`, { observe: 'response' }); + } + /** * Delete an exam on the server using a DELETE request. * @param courseId The course id. diff --git a/src/main/webapp/app/exam/manage/exams/exam-detail.component.html b/src/main/webapp/app/exam/manage/exams/exam-detail.component.html index 243126fc3758..18dfd2f261a6 100644 --- a/src/main/webapp/app/exam/manage/exams/exam-detail.component.html +++ b/src/main/webapp/app/exam/manage/exams/exam-detail.component.html @@ -87,6 +87,8 @@

    jhiDeleteButton [buttonSize]="buttonSize" [entityTitle]="exam.title || ''" + entitySummaryTitle="artemisApp.examManagement.delete.summary.title" + [fetchEntitySummary]="fetchExamDeletionSummary()" deleteQuestion="artemisApp.examManagement.delete.question" deleteConfirmationText="artemisApp.examManagement.delete.typeNameToConfirm" (delete)="deleteExam(exam.id!)" diff --git a/src/main/webapp/app/exam/manage/exams/exam-detail.component.ts b/src/main/webapp/app/exam/manage/exams/exam-detail.component.ts index 21638aa00238..c0f4130e83c1 100644 --- a/src/main/webapp/app/exam/manage/exams/exam-detail.component.ts +++ b/src/main/webapp/app/exam/manage/exams/exam-detail.component.ts @@ -2,9 +2,9 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { SafeHtml } from '@angular/platform-browser'; import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; -import { Subject } from 'rxjs'; +import { Observable, Subject, map } from 'rxjs'; import { Exam } from 'app/entities/exam/exam.model'; -import { ActionType } from 'app/shared/delete-dialog/delete-dialog.model'; +import { ActionType, EntitySummary } from 'app/shared/delete-dialog/delete-dialog.model'; import { ButtonSize } from 'app/shared/components/button.component'; import { ArtemisMarkdownService } from 'app/shared/markdown.service'; import { AccountService } from 'app/core/auth/account.service'; @@ -17,6 +17,7 @@ import { GradeType } from 'app/entities/grading-scale.model'; import { DetailOverviewSection, DetailType } from 'app/detail-overview-list/detail-overview-list.component'; import { ArtemisDurationFromSecondsPipe } from 'app/shared/pipes/artemis-duration-from-seconds.pipe'; import { scrollToTopOfPage } from 'app/shared/util/utils'; +import { ExerciseType } from 'app/entities/exercise.model'; @Component({ selector: 'jhi-exam-detail', @@ -160,4 +161,63 @@ export class ExamDetailComponent implements OnInit, OnDestroy { error: (error: HttpErrorResponse) => this.dialogErrorSource.next(error.message), }); } + + private getExistingSummaryEntries(): EntitySummary { + const numberOfProgrammingExerciseParticipations = + this.exam.exerciseGroups + ?.flatMap((exerciseGroup) => exerciseGroup.exercises) + .filter((exercise) => exercise?.type === ExerciseType.PROGRAMMING) + .map((exercise) => exercise?.numberOfParticipations ?? 0) + .reduce((repositorySum, numberOfParticipationsForRepository) => repositorySum + numberOfParticipationsForRepository, 0) ?? 0; + + const numberOfExercisesPerType = new Map(); + this.exam.exerciseGroups?.forEach((exerciseGroup) => { + exerciseGroup.exercises?.forEach((exercise) => { + if (exercise.type === undefined) { + return; + } + const oldValue = numberOfExercisesPerType.get(exercise.type) ?? 0; + numberOfExercisesPerType.set(exercise.type, oldValue + 1); + }); + }); + + const numberOfExerciseGroups = this.exam.exerciseGroups?.length ?? 0; + const isTestExam = this.exam.testExam ?? false; + const isTestCourse = this.exam.course?.testCourse ?? false; + + return { + 'artemisApp.examManagement.delete.summary.numberExerciseGroups': numberOfExerciseGroups, + 'artemisApp.examManagement.delete.summary.numberProgrammingExercises': numberOfExercisesPerType.get(ExerciseType.PROGRAMMING), + 'artemisApp.examManagement.delete.summary.numberModelingExercises': numberOfExercisesPerType.get(ExerciseType.MODELING), + 'artemisApp.examManagement.delete.summary.numberTextExercises': numberOfExercisesPerType.get(ExerciseType.TEXT), + 'artemisApp.examManagement.delete.summary.numberFileUploadExercises': numberOfExercisesPerType.get(ExerciseType.FILE_UPLOAD), + 'artemisApp.examManagement.delete.summary.numberQuizExercises': numberOfExercisesPerType.get(ExerciseType.QUIZ), + 'artemisApp.examManagement.delete.summary.numberRepositories': numberOfProgrammingExerciseParticipations, + 'artemisApp.examManagement.delete.summary.isTestExam': isTestExam, + 'artemisApp.examManagement.delete.summary.isTestCourse': isTestCourse, + }; + } + + fetchExamDeletionSummary(): Observable { + return this.examManagementService.getDeletionSummary(this.exam.course!.id!, this.exam.id!).pipe( + map((response) => { + const summary = response.body; + + if (summary === null) { + return {}; + } + + return { + ...this.getExistingSummaryEntries(), + 'artemisApp.examManagement.delete.summary.numberBuilds': summary.numberOfBuilds, + 'artemisApp.examManagement.delete.summary.numberRegisteredStudents': summary.numberRegisteredStudents, + 'artemisApp.examManagement.delete.summary.numberNotStartedExams': summary.numberNotStartedExams, + 'artemisApp.examManagement.delete.summary.numberStartedExams': summary.numberStartedExams, + 'artemisApp.examManagement.delete.summary.numberSubmittedExams': summary.numberSubmittedExams, + 'artemisApp.examManagement.delete.summary.numberCommunicationPosts': summary.numberOfCommunicationPosts, + 'artemisApp.examManagement.delete.summary.numberAnswerPosts': summary.numberOfAnswerPosts, + }; + }), + ); + } } diff --git a/src/main/webapp/app/exam/participate/summary/exam-result-summary.component.html b/src/main/webapp/app/exam/participate/summary/exam-result-summary.component.html index f1017919bfb7..44eea193c092 100644 --- a/src/main/webapp/app/exam/participate/summary/exam-result-summary.component.html +++ b/src/main/webapp/app/exam/participate/summary/exam-result-summary.component.html @@ -190,6 +190,7 @@

    [resultsPublished]="resultsArePublished" [isPrinting]="isPrinting" [isAfterResultsArePublished]="resultsArePublished" + [instructorView]="instructorView" /> } } diff --git a/src/main/webapp/app/exam/participate/summary/exercises/programming-exam-summary/programming-exam-summary.component.html b/src/main/webapp/app/exam/participate/summary/exercises/programming-exam-summary/programming-exam-summary.component.html index c483a168cd8a..2a0df39f1353 100644 --- a/src/main/webapp/app/exam/participate/summary/exercises/programming-exam-summary/programming-exam-summary.component.html +++ b/src/main/webapp/app/exam/participate/summary/exercises/programming-exam-summary/programming-exam-summary.component.html @@ -48,7 +48,7 @@
    @if (exercise.problemStatement) { - + }
    diff --git a/src/main/webapp/app/exam/participate/summary/exercises/programming-exam-summary/programming-exam-summary.component.ts b/src/main/webapp/app/exam/participate/summary/exercises/programming-exam-summary/programming-exam-summary.component.ts index 2243c413644a..5ffdffc38018 100644 --- a/src/main/webapp/app/exam/participate/summary/exercises/programming-exam-summary/programming-exam-summary.component.ts +++ b/src/main/webapp/app/exam/participate/summary/exercises/programming-exam-summary/programming-exam-summary.component.ts @@ -39,6 +39,8 @@ export class ProgrammingExamSummaryComponent implements OnInit { @Input() isAfterResultsArePublished?: boolean = false; + @Input() instructorView?: boolean = false; + readonly PROGRAMMING: ExerciseType = ExerciseType.PROGRAMMING; protected readonly AssessmentType = AssessmentType; diff --git a/src/main/webapp/app/exercises/programming/shared/code-editor/file-browser/supported-file-extensions.ts b/src/main/webapp/app/exercises/programming/shared/code-editor/file-browser/supported-file-extensions.ts index 73ec34bebb00..89664e7f5963 100644 --- a/src/main/webapp/app/exercises/programming/shared/code-editor/file-browser/supported-file-extensions.ts +++ b/src/main/webapp/app/exercises/programming/shared/code-editor/file-browser/supported-file-extensions.ts @@ -1,5 +1,6 @@ export const supportedTextFileExtensions = [ 'Makefile', + 'R', 'Rakefile', 'ada', 'adb', diff --git a/src/main/webapp/app/exercises/shared/exercise-hint/manage/exercise-hint-update.component.html b/src/main/webapp/app/exercises/shared/exercise-hint/manage/exercise-hint-update.component.html index 5c80f6e0c868..6bf170d0bac9 100644 --- a/src/main/webapp/app/exercises/shared/exercise-hint/manage/exercise-hint-update.component.html +++ b/src/main/webapp/app/exercises/shared/exercise-hint/manage/exercise-hint-update.component.html @@ -45,21 +45,6 @@

    - @if (exerciseHint.type === HintType.CODE && irisSettings?.irisHestiaSettings?.enabled) { -
    - Generate description - -
    - }
    diff --git a/src/main/webapp/app/faq/faq.component.html b/src/main/webapp/app/faq/faq.component.html index 94bdab39fb48..157c9a8a8708 100644 --- a/src/main/webapp/app/faq/faq.component.html +++ b/src/main/webapp/app/faq/faq.component.html @@ -5,7 +5,10 @@

    -
    +
    +
    + +
    -

    -: +

    +: @if (parentSubSettings) {
    }
    - @for (model of allIrisModels; track model) { + @for (variant of availableVariants; track variant) {
    -
    }
    +
    :
    +
    +
    + +
    + @if (parentSubSettings) { + + } + @for (model of allowedVariants; track model) { + + } +
    +
    + @if (!subSettings?.selectedVariant) { + {{ getSelectedVariantNameParent() }} + } +
    diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts index 723b8ddb21a9..ba5bd6573691 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.ts @@ -1,10 +1,11 @@ import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; import { IrisSubSettings, IrisSubSettingsType } from 'app/entities/iris/settings/iris-sub-settings.model'; -import { IrisModel } from 'app/entities/iris/settings/iris-model'; +import { IrisVariant } from 'app/entities/iris/settings/iris-variant'; import { AccountService } from 'app/core/auth/account.service'; import { ButtonType } from 'app/shared/components/button.component'; import { faTrash } from '@fortawesome/free-solid-svg-icons'; import { IrisSettingsType } from 'app/entities/iris/settings/iris-settings.model'; +import { IrisSettingsService } from 'app/iris/settings/shared/iris-settings.service'; @Component({ selector: 'jhi-iris-common-sub-settings-update', @@ -17,9 +18,6 @@ export class IrisCommonSubSettingsUpdateComponent implements OnInit, OnChanges { @Input() parentSubSettings?: IrisSubSettings; - @Input() - allIrisModels: IrisModel[]; - @Input() settingsType: IrisSettingsType; @@ -28,9 +26,11 @@ export class IrisCommonSubSettingsUpdateComponent implements OnInit, OnChanges { isAdmin: boolean; - inheritAllowedModels: boolean; + inheritAllowedVariants: boolean; + + availableVariants: IrisVariant[] = []; - allowedIrisModels: IrisModel[]; + allowedVariants: IrisVariant[] = []; enabled: boolean; @@ -42,49 +42,62 @@ export class IrisCommonSubSettingsUpdateComponent implements OnInit, OnChanges { // Icons faTrash = faTrash; - constructor(accountService: AccountService) { + constructor( + accountService: AccountService, + private irisSettingsService: IrisSettingsService, + ) { this.isAdmin = accountService.isAdmin(); } ngOnInit() { this.enabled = this.subSettings?.enabled ?? false; - this.allowedIrisModels = this.getAvailableModels(); - this.inheritAllowedModels = !!(!this.subSettings?.allowedModels && this.parentSubSettings); + this.loadVariants(); + this.inheritAllowedVariants = !!(!this.subSettings?.allowedVariants && this.parentSubSettings); } ngOnChanges(changes: SimpleChanges): void { - if (changes.allIrisModels) { - this.allowedIrisModels = this.getAvailableModels(); + if (changes.availableVariants) { + this.allowedVariants = this.getAllowedVariants(); } if (changes.subSettings) { this.enabled = this.subSettings?.enabled ?? false; } } - getAvailableModels(): IrisModel[] { - return this.allIrisModels.filter((model) => (this.subSettings?.allowedModels ?? this.parentSubSettings?.allowedModels ?? []).includes(model.id)); + loadVariants(): void { + if (!this.subSettings?.type) { + return; + } + this.irisSettingsService.getVariantsForFeature(this.subSettings?.type).subscribe((variants) => { + this.availableVariants = variants ?? this.availableVariants; + this.allowedVariants = this.getAllowedVariants(); + }); + } + + getAllowedVariants(): IrisVariant[] { + return this.availableVariants.filter((variant) => (this.subSettings?.allowedVariants ?? this.parentSubSettings?.allowedVariants ?? []).includes(variant.id)); } - getPreferredModelName(): string | undefined { - return this.allIrisModels.find((model) => model.id === this.subSettings?.preferredModel)?.name ?? this.subSettings?.preferredModel; + getSelectedVariantName(): string | undefined { + return this.availableVariants.find((variant) => variant.id === this.subSettings?.selectedVariant)?.name ?? this.subSettings?.selectedVariant; } - getPreferredModelNameParent(): string | undefined { - return this.allIrisModels.find((model) => model.id === this.parentSubSettings?.preferredModel)?.name ?? this.parentSubSettings?.preferredModel; + getSelectedVariantNameParent(): string | undefined { + return this.availableVariants.find((variant) => variant.id === this.parentSubSettings?.selectedVariant)?.name ?? this.parentSubSettings?.selectedVariant; } - onAllowedIrisModelsSelectionChange(model: IrisModel) { - this.inheritAllowedModels = false; - if (this.allowedIrisModels.includes(model)) { - this.allowedIrisModels = this.allowedIrisModels.filter((m) => m !== model); + onAllowedIrisVariantsSelectionChange(variant: IrisVariant) { + this.inheritAllowedVariants = false; + if (this.allowedVariants.map((variant) => variant.id).includes(variant.id)) { + this.allowedVariants = this.allowedVariants.filter((m) => m.id !== variant.id); } else { - this.allowedIrisModels.push(model); + this.allowedVariants.push(variant); } - this.subSettings!.allowedModels = this.allowedIrisModels.map((model) => model.id); + this.subSettings!.allowedVariants = this.allowedVariants.map((variant) => variant.id); } - setModel(model: IrisModel | undefined) { - this.subSettings!.preferredModel = model?.id; + setVariant(variant: IrisVariant | undefined) { + this.subSettings!.selectedVariant = variant?.id; } onEnabledChange() { @@ -101,12 +114,12 @@ export class IrisCommonSubSettingsUpdateComponent implements OnInit, OnChanges { this.onEnabledChange(); } - onInheritAllowedModelsChange() { - if (this.inheritAllowedModels) { - this.subSettings!.allowedModels = undefined; - this.allowedIrisModels = this.getAvailableModels(); + onInheritAllowedVariantsChange() { + if (this.inheritAllowedVariants) { + this.subSettings!.allowedVariants = undefined; + this.allowedVariants = this.getAllowedVariants(); } else { - this.subSettings!.allowedModels = this.allowedIrisModels.map((model) => model.id); + this.subSettings!.allowedVariants = this.allowedVariants.map((variant) => variant.id); } } diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component.html b/src/main/webapp/app/iris/settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component.html deleted file mode 100644 index efb780138e8c..000000000000 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component.html +++ /dev/null @@ -1,20 +0,0 @@ -@if (irisSettings) { -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -} diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component.ts deleted file mode 100644 index 404132633566..000000000000 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-global-autoupdate-settings-update/iris-global-autoupdate-settings-update.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { IrisGlobalSettings } from 'app/entities/iris/settings/iris-settings.model'; -import { IrisSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; - -@Component({ - selector: 'jhi-iris-global-autoupdate-settings-update', - templateUrl: './iris-global-autoupdate-settings-update.component.html', -}) -export class IrisGlobalAutoupdateSettingsUpdateComponent { - @Input() - irisSettings?: IrisGlobalSettings; - - @Output() - onChanges = new EventEmitter(); -} diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.html b/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.html index b28a3a4c690c..c1e15d609c1f 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.html +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.html @@ -11,18 +11,11 @@
    @if (irisSettings) {
    - @if (settingsType === GLOBAL) { -
    -

    - -
    - }

    @@ -34,33 +27,21 @@

    -

    - } - @if (settingsType === COURSE) { -
    - - -
    - } - @if (settingsType !== EXERCISE) { -
    -
    -

    - +
    + + @if (settingsType === COURSE) { +
    + + +
    + }
    } @if (settingsType !== EXERCISE) { @@ -70,7 +51,6 @@

    diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.ts b/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.ts index 5bde2f10f791..aa9adac6be64 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.ts +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-settings-update.component.ts @@ -6,15 +6,9 @@ import { Observable } from 'rxjs'; import { AlertService } from 'app/core/util/alert.service'; import { ButtonType } from 'app/shared/components/button.component'; import { faRotate, faSave } from '@fortawesome/free-solid-svg-icons'; -import { IrisModel } from 'app/entities/iris/settings/iris-model'; import { ComponentCanDeactivate } from 'app/shared/guard/can-deactivate.model'; import { cloneDeep, isEqual } from 'lodash-es'; -import { - IrisChatSubSettings, - IrisCompetencyGenerationSubSettings, - IrisHestiaSubSettings, - IrisLectureIngestionSubSettings, -} from 'app/entities/iris/settings/iris-sub-settings.model'; +import { IrisChatSubSettings, IrisCompetencyGenerationSubSettings, IrisLectureIngestionSubSettings } from 'app/entities/iris/settings/iris-sub-settings.model'; import { AccountService } from 'app/core/auth/account.service'; @Component({ @@ -30,7 +24,6 @@ export class IrisSettingsUpdateComponent implements OnInit, DoCheck, ComponentCa public exerciseId?: number; public irisSettings?: IrisSettings; public parentIrisSettings?: IrisSettings; - public allIrisModels?: IrisModel[]; originalIrisSettings?: IrisSettings; @@ -77,13 +70,6 @@ export class IrisSettingsUpdateComponent implements OnInit, DoCheck, ComponentCa return !this.isDirty; } - loadIrisModels(): void { - this.irisSettingsService.getIrisModels().subscribe((models) => { - this.allIrisModels = models; - this.isLoading = false; - }); - } - loadIrisSettings(): void { this.isLoading = true; this.loadIrisSettingsObservable().subscribe((settings) => { @@ -116,9 +102,6 @@ export class IrisSettingsUpdateComponent implements OnInit, DoCheck, ComponentCa if (!this.irisSettings.irisLectureIngestionSettings) { this.irisSettings.irisLectureIngestionSettings = new IrisLectureIngestionSubSettings(); } - if (!this.irisSettings.irisHestiaSettings) { - this.irisSettings.irisHestiaSettings = new IrisHestiaSubSettings(); - } if (!this.irisSettings.irisCompetencyGenerationSettings) { this.irisSettings.irisCompetencyGenerationSettings = new IrisCompetencyGenerationSubSettings(); } diff --git a/src/main/webapp/app/iris/settings/shared/iris-enabled.component.ts b/src/main/webapp/app/iris/settings/shared/iris-enabled.component.ts index ef8d44419841..be64bd5d856b 100644 --- a/src/main/webapp/app/iris/settings/shared/iris-enabled.component.ts +++ b/src/main/webapp/app/iris/settings/shared/iris-enabled.component.ts @@ -56,9 +56,6 @@ export class IrisEnabledComponent implements OnInit { case IrisSubSettingsType.CHAT: this.irisSubSettings = this.irisSettings?.irisChatSettings; break; - case IrisSubSettingsType.HESTIA: - this.irisSubSettings = this.irisSettings?.irisHestiaSettings; - break; case IrisSubSettingsType.COMPETENCY_GENERATION: this.irisSubSettings = this.irisSettings?.irisCompetencyGenerationSettings; break; diff --git a/src/main/webapp/app/iris/settings/shared/iris-settings.service.ts b/src/main/webapp/app/iris/settings/shared/iris-settings.service.ts index 475540bad156..7273e5958cf6 100644 --- a/src/main/webapp/app/iris/settings/shared/iris-settings.service.ts +++ b/src/main/webapp/app/iris/settings/shared/iris-settings.service.ts @@ -3,7 +3,8 @@ import { HttpClient, HttpResponse } from '@angular/common/http'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { IrisCourseSettings, IrisExerciseSettings, IrisGlobalSettings } from 'app/entities/iris/settings/iris-settings.model'; -import { IrisModel } from 'app/entities/iris/settings/iris-model'; +import { IrisVariant } from 'app/entities/iris/settings/iris-variant'; +import { IrisSubSettingsType } from 'app/entities/iris/settings/iris-sub-settings.model'; /** * Service for calling the Iris settings endpoints on the server @@ -90,9 +91,11 @@ export class IrisSettingsService { } /** - * Get the global Iris settings + * Get the available variants for a feature */ - getIrisModels(): Observable { - return this.http.get(`${this.resourceUrl}/iris/models`, { observe: 'response' }).pipe(map((res: HttpResponse) => res.body ?? [])); + getVariantsForFeature(feature: IrisSubSettingsType): Observable { + return this.http + .get(`${this.resourceUrl}/iris/variants/${feature}`, { observe: 'response' }) + .pipe(map((res: HttpResponse) => res.body ?? [])); } } diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachment-units/attachment-units.component.html b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachment-units/attachment-units.component.html index 38e8728489b5..41fe88dbb1b3 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachment-units/attachment-units.component.html +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachment-units/attachment-units.component.html @@ -99,7 +99,7 @@

    />

    - @if (units && units.length < 1) { + @if (units.length < 1) {
    {{ 'artemisApp.attachmentUnit.createAttachmentUnits.noUnitDetected' | artemisTranslate }} diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachment-units/attachment-units.component.ts b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachment-units/attachment-units.component.ts index b8d95ef66b6c..9a0ddf145f45 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachment-units/attachment-units.component.ts +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachment-units/attachment-units.component.ts @@ -117,7 +117,7 @@ export class AttachmentUnitsComponent implements OnInit { ) .subscribe({ next: (res) => { - this.units = res.body!.units; + this.units = res.body!.units || this.units; this.numberOfPages = res.body!.numberOfPages; this.isLoading = false; }, diff --git a/src/main/webapp/app/lti/lti13-exercise-launch.component.ts b/src/main/webapp/app/lti/lti13-exercise-launch.component.ts index 6c9af31bc0c4..bbb1757c77f5 100644 --- a/src/main/webapp/app/lti/lti13-exercise-launch.component.ts +++ b/src/main/webapp/app/lti/lti13-exercise-launch.component.ts @@ -4,6 +4,8 @@ import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; import { AccountService } from 'app/core/auth/account.service'; import { captureException } from '@sentry/angular'; import { SessionStorageService } from 'ngx-webstorage'; +import { LtiService } from 'app/shared/service/lti.service'; +import { Theme, ThemeService } from 'app/core/theme/theme.service'; type LtiLaunchResponse = { targetLinkUri: string; @@ -24,6 +26,8 @@ export class Lti13ExerciseLaunchComponent implements OnInit { private accountService: AccountService, private router: Router, private sessionStorageService: SessionStorageService, + private ltiService: LtiService, + private themeService: ThemeService, ) { this.isLaunching = true; } @@ -142,6 +146,10 @@ export class Lti13ExerciseLaunchComponent implements OnInit { } replaceWindowLocationWrapper(url: string): void { - window.location.replace(url); + this.ltiService.setLti(true); + this.themeService.applyThemeExplicitly(Theme.LIGHT); + const path = new URL(url).pathname; + + this.router.navigate([path], { replaceUrl: true }); } } diff --git a/src/main/webapp/app/overview/course-exercises/course-exercises.component.html b/src/main/webapp/app/overview/course-exercises/course-exercises.component.html index 830d3863c55e..4b32dedb50f4 100644 --- a/src/main/webapp/app/overview/course-exercises/course-exercises.component.html +++ b/src/main/webapp/app/overview/course-exercises/course-exercises.component.html @@ -1,11 +1,11 @@
    @if (course) { -
    +
    @if (exerciseSelected) { -
    +
    } @else { diff --git a/src/main/webapp/app/overview/course-exercises/course-exercises.component.ts b/src/main/webapp/app/overview/course-exercises/course-exercises.component.ts index 2238ede4702c..a8bfc240b99d 100644 --- a/src/main/webapp/app/overview/course-exercises/course-exercises.component.ts +++ b/src/main/webapp/app/overview/course-exercises/course-exercises.component.ts @@ -9,6 +9,7 @@ import { Exercise } from 'app/entities/exercise.model'; import { CourseStorageService } from 'app/course/manage/course-storage.service'; import { AccordionGroups, CollapseState, SidebarCardElement, SidebarData } from 'app/types/sidebar'; import { CourseOverviewService } from '../course-overview.service'; +import { LtiService } from 'app/shared/service/lti.service'; const DEFAULT_UNIT_GROUPS: AccordionGroups = { future: { entityData: [] }, @@ -34,6 +35,7 @@ const DEFAULT_COLLAPSE_STATE: CollapseState = { export class CourseExercisesComponent implements OnInit, OnDestroy { private parentParamSubscription: Subscription; private courseUpdatesSubscription: Subscription; + private ltiSubscription: Subscription; course?: Course; courseId: number; @@ -46,6 +48,7 @@ export class CourseExercisesComponent implements OnInit, OnDestroy { sidebarExercises: SidebarCardElement[] = []; isCollapsed: boolean = false; readonly DEFAULT_COLLAPSE_STATE = DEFAULT_COLLAPSE_STATE; + isLti: boolean = false; constructor( private courseStorageService: CourseStorageService, @@ -54,6 +57,7 @@ export class CourseExercisesComponent implements OnInit, OnDestroy { private programmingSubmissionService: ProgrammingSubmissionService, private router: Router, private courseOverviewService: CourseOverviewService, + private ltiService: LtiService, ) {} ngOnInit() { @@ -74,6 +78,10 @@ export class CourseExercisesComponent implements OnInit, OnDestroy { this.exerciseForGuidedTour = this.guidedTourService.enableTourForCourseExerciseComponent(this.course, courseExerciseOverviewTour, true); + this.ltiSubscription = this.ltiService.isLti$.subscribe((isLti) => { + this.isLti = isLti; + }); + // If no exercise is selected navigate to the lastSelected or upcoming exercise this.navigateToExercise(); } @@ -143,5 +151,6 @@ export class CourseExercisesComponent implements OnInit, OnDestroy { ngOnDestroy(): void { this.courseUpdatesSubscription?.unsubscribe(); this.parentParamSubscription?.unsubscribe(); + this.ltiSubscription?.unsubscribe(); } } diff --git a/src/main/webapp/app/overview/course-faq/course-faq.component.html b/src/main/webapp/app/overview/course-faq/course-faq.component.html index c94960161ee4..115efb80cc1c 100644 --- a/src/main/webapp/app/overview/course-faq/course-faq.component.html +++ b/src/main/webapp/app/overview/course-faq/course-faq.component.html @@ -1,6 +1,7 @@
    -
    -
    +
    + +
    - } -
    -
    - @if (!isNavbarCollapsed) { -
    {{ course?.title }}
    - } -
    - -
    -
    -
    + +
    +
    + +
    +
    + +
    + @if (courseActionItems?.length && !anyItemHidden) { + + } + +
    +
    + +
    -

+ - -
- @if (courseActionItems?.length && !anyItemHidden) { - - } - + + + @if (course) {
-
- - +
+ @if (hasSidebar) { + + } +
{{ 'artemisApp.courseOverview.menu.' + pageTitle | artemisTranslate }}
-
-
-
- - - @if (course) { -
-
- @if (hasSidebar) { - - } -
{{ 'artemisApp.courseOverview.menu.' + pageTitle | artemisTranslate }}
+ } + @if (showRefreshButton) { + + } +
-
- @if (isNotManagementView && course.isAtLeastTutor) { -
- -
- } - @if (showRefreshButton) { - +
+ @if (!hasSidebar) { + } + +
+ +
-
-
- @if (!hasSidebar) { - - } - -
- -
-
- } -
- + } + + + + +} @else { +
+ @if (!hasSidebar) { + + } + +
+ +
- +} diff --git a/src/main/webapp/app/overview/course-overview.component.ts b/src/main/webapp/app/overview/course-overview.component.ts index badecc84f502..86d8e31f26d3 100644 --- a/src/main/webapp/app/overview/course-overview.component.ts +++ b/src/main/webapp/app/overview/course-overview.component.ts @@ -66,6 +66,7 @@ import { ExamParticipationService } from 'app/exam/participate/exam-participatio import { CourseConversationsComponent } from 'app/overview/course-conversations/course-conversations.component'; import { sortCourses } from 'app/shared/util/course.util'; import { CourseUnenrollmentModalComponent } from './course-unenrollment-modal.component'; +import { LtiService } from 'app/shared/service/lti.service'; interface CourseActionItem { title: string; @@ -124,6 +125,8 @@ export class CourseOverviewComponent implements OnInit, OnDestroy, AfterViewInit isExamStarted = false; private examStartedSubscription: Subscription; readonly MIN_DISPLAYED_COURSES: number = 6; + isLti: boolean = false; + private ltiSubscription: Subscription; // Properties to track hidden items for dropdown menu dropdownOpen: boolean = false; @@ -199,6 +202,7 @@ export class CourseOverviewComponent implements OnInit, OnDestroy, AfterViewInit private profileService: ProfileService, private modalService: NgbModal, private examParticipationService: ExamParticipationService, + private ltiService: LtiService, ) {} async ngOnInit() { @@ -234,13 +238,18 @@ export class CourseOverviewComponent implements OnInit, OnDestroy, AfterViewInit this.updateVisibleNavbarItems(window.innerHeight); await this.updateRecentlyAccessedCourses(); this.isSidebarCollapsed = this.activatedComponentReference?.isCollapsed ?? false; + this.ltiSubscription = this.ltiService.isLti$.subscribe((isLti) => { + this.isLti = isLti; + }); } /** Listen window resize event by height */ @HostListener('window: resize', ['$event']) onResize() { - this.updateVisibleNavbarItems(window.innerHeight); - if (!this.anyItemHidden) this.itemsDrop.close(); + if (this.itemsDrop) { + this.updateVisibleNavbarItems(window.innerHeight); + if (!this.anyItemHidden) this.itemsDrop.close(); + } } /** Update sidebar item's hidden property based on the window height to display three-dots */ @@ -740,6 +749,7 @@ export class CourseOverviewComponent implements OnInit, OnDestroy, AfterViewInit this.dashboardSubscription?.unsubscribe(); this.ngUnsubscribe.next(); this.ngUnsubscribe.complete(); + this.ltiSubscription?.unsubscribe(); } subscribeForQuizChanges() { diff --git a/src/main/webapp/app/overview/courses.module.ts b/src/main/webapp/app/overview/courses.module.ts index 01e27a171163..5dbae0d30fde 100644 --- a/src/main/webapp/app/overview/courses.module.ts +++ b/src/main/webapp/app/overview/courses.module.ts @@ -19,6 +19,7 @@ import { ArtemisCourseExerciseRowModule } from 'app/overview/course-exercises/co import { NgxChartsModule, PieChartModule } from '@swimlane/ngx-charts'; import { CourseUnenrollmentModalComponent } from 'app/overview/course-unenrollment-modal.component'; import { ArtemisSidebarModule } from 'app/shared/sidebar/sidebar.module'; +import { SearchFilterComponent } from 'app/shared/search-filter/search-filter.component'; @NgModule({ imports: [ @@ -36,6 +37,7 @@ import { ArtemisSidebarModule } from 'app/shared/sidebar/sidebar.module'; NgxChartsModule, PieChartModule, ArtemisSidebarModule, + SearchFilterComponent, ], declarations: [ CoursesComponent, diff --git a/src/main/webapp/app/shared/components/documentation-button/documentation-button.component.ts b/src/main/webapp/app/shared/components/documentation-button/documentation-button.component.ts index f598865bb6a6..7724361d798e 100644 --- a/src/main/webapp/app/shared/components/documentation-button/documentation-button.component.ts +++ b/src/main/webapp/app/shared/components/documentation-button/documentation-button.component.ts @@ -12,7 +12,7 @@ const DocumentationLinks = { Quiz: 'exercises/quiz/', Model: 'exercises/modeling/', Programming: 'exercises/programming/', - SshSetup: 'exercises/programming.html#repository-access', + SshSetup: 'icl/ssh-intro', Text: 'exercises/textual/', FileUpload: 'exercises/file-upload/', Notifications: 'notifications/', diff --git a/src/main/webapp/app/shared/components/documentation-link/documentation-link.component.html b/src/main/webapp/app/shared/components/documentation-link/documentation-link.component.html new file mode 100644 index 000000000000..b3e990001b2f --- /dev/null +++ b/src/main/webapp/app/shared/components/documentation-link/documentation-link.component.html @@ -0,0 +1,5 @@ +@if (displayString() && documentationType()) { + + + +} diff --git a/src/main/webapp/app/shared/components/documentation-link/documentation-link.component.ts b/src/main/webapp/app/shared/components/documentation-link/documentation-link.component.ts new file mode 100644 index 000000000000..86f5d19aafb6 --- /dev/null +++ b/src/main/webapp/app/shared/components/documentation-link/documentation-link.component.ts @@ -0,0 +1,25 @@ +import { Component, input } from '@angular/core'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; + +// The routes here are used to build the link to the documentation. +// Therefore, it's important that they exactly match the url to the subpage of the documentation. +// Additionally, the case names must match the keys in documentationLinks.json for the tooltip. +const DocumentationLinks: { [key: string]: string } = { + SshSetup: 'icl/ssh-intro', +}; + +export type DocumentationType = keyof typeof DocumentationLinks; + +@Component({ + selector: 'jhi-documentation-link', + standalone: true, + templateUrl: './documentation-link.component.html', + imports: [TranslateDirective], +}) +export class DocumentationLinkComponent { + readonly BASE_URL = 'https://docs.artemis.cit.tum.de/user/'; + readonly DocumentationLinks = DocumentationLinks; + + documentationType = input(); + displayString = input(); +} diff --git a/src/main/webapp/app/shared/delete-dialog/delete-button.directive.ts b/src/main/webapp/app/shared/delete-dialog/delete-button.directive.ts index 4f1476ae508b..6fe508245dc8 100644 --- a/src/main/webapp/app/shared/delete-dialog/delete-button.directive.ts +++ b/src/main/webapp/app/shared/delete-dialog/delete-button.directive.ts @@ -1,7 +1,7 @@ import { DeleteDialogService } from 'app/shared/delete-dialog/delete-dialog.service'; import { Directive, ElementRef, EventEmitter, HostListener, Input, OnInit, Output, Renderer2 } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; -import { ActionType, DeleteDialogData } from 'app/shared/delete-dialog/delete-dialog.model'; +import { ActionType, DeleteDialogData, EntitySummary } from 'app/shared/delete-dialog/delete-dialog.model'; import { Observable } from 'rxjs'; import { ButtonSize, ButtonType } from 'app/shared/components/button.component'; @@ -9,6 +9,8 @@ import { ButtonSize, ButtonType } from 'app/shared/components/button.component'; export class DeleteButtonDirective implements OnInit { @Input() entityTitle?: string; @Input() deleteQuestion: string; + @Input() entitySummaryTitle?: string; + @Input() fetchEntitySummary?: Observable; @Input() translateValues: { [key: string]: unknown } = {}; @Input() deleteConfirmationText: string; @Input() buttonSize: ButtonSize = ButtonSize.SMALL; @@ -73,6 +75,8 @@ export class DeleteButtonDirective implements OnInit { translateValues: this.translateValues, deleteConfirmationText: this.deleteConfirmationText, additionalChecks: this.additionalChecks, + entitySummaryTitle: this.entitySummaryTitle, + fetchEntitySummary: this.fetchEntitySummary, actionType: this.actionType, buttonType: this.buttonType, delete: this.delete, diff --git a/src/main/webapp/app/shared/delete-dialog/delete-dialog.component.html b/src/main/webapp/app/shared/delete-dialog/delete-dialog.component.html index eaadd2bb1acd..47e8a7fd5b6c 100644 --- a/src/main/webapp/app/shared/delete-dialog/delete-dialog.component.html +++ b/src/main/webapp/app/shared/delete-dialog/delete-dialog.component.html @@ -29,6 +29,22 @@