diff --git a/.github/workflows/docker-fix.yml b/.github/workflows/docker-fix.yml index f1cab51..fe6cef5 100644 --- a/.github/workflows/docker-fix.yml +++ b/.github/workflows/docker-fix.yml @@ -16,11 +16,11 @@ jobs: - name: Checkout code uses: actions/checkout@v6 - - name: Set up JDK 21 + - name: Set up JDK 25 uses: actions/setup-java@v5 with: distribution: 'temurin' - java-version: '21' + java-version: '25' - name: Run production build env: diff --git a/.github/workflows/docker-preview.yml b/.github/workflows/docker-preview.yml index 247732c..b3fbe77 100644 --- a/.github/workflows/docker-preview.yml +++ b/.github/workflows/docker-preview.yml @@ -20,11 +20,11 @@ jobs: with: fetch-depth: 0 - - name: Set up JDK 21 + - name: Set up JDK 25 uses: actions/setup-java@v5 with: distribution: 'temurin' - java-version: '21' + java-version: '25' - name: Setup Gradle uses: gradle/actions/setup-gradle@v5 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a92956c..9675e7a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -75,11 +75,11 @@ jobs: with: name: modified-files - - name: Set up JDK 21 + - name: Set up JDK 25 uses: actions/setup-java@v5 with: distribution: 'temurin' - java-version: '21' + java-version: '25' - name: Setup Gradle uses: gradle/actions/setup-gradle@v5 @@ -162,11 +162,11 @@ jobs: with: name: modified-files - - name: Set up JDK 21 + - name: Set up JDK 25 uses: actions/setup-java@v5 with: distribution: 'temurin' - java-version: '21' + java-version: '25' - name: Setup Gradle uses: gradle/actions/setup-gradle@v5 diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml new file mode 100644 index 0000000..2044cb6 --- /dev/null +++ b/.github/workflows/sonar.yml @@ -0,0 +1,44 @@ +name: Sonar Analysis + +on: + push: + branches: + - main + pull_request: + types: [ opened, synchronize, reopened ] + +jobs: + sonar: + name: Sonar Analysis + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up JDK 25 + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: '25' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + + - name: Cache SonarCloud packages + uses: actions/cache@v4 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + + - name: Run tests and generate JaCoCo report + run: ./gradlew :app:test :app:jacocoTestReport + + - name: SonarCloud Scan + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: ./gradlew :app:sonar diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8014dc1..ab31d45 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -12,6 +12,8 @@ plugins { kotlin("plugin.jpa") id("com.google.devtools.ksp") application + jacoco + id("org.sonarqube") } application { @@ -33,15 +35,19 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-actuator") implementation("org.springframework.boot:spring-boot-starter-security") implementation("org.springframework.boot:spring-boot-starter-validation") - implementation("org.springframework.boot:spring-boot-starter-aop") + implementation("org.springframework.boot:spring-boot-starter-aspectj") + implementation("org.springframework.boot:spring-boot-starter-jackson") implementation("org.springframework.cloud:spring-cloud-starter") - implementation("jakarta.validation:jakarta.validation-api:3.1.0") + implementation("jakarta.validation:jakarta.validation-api:${rootProject.extra["jakartaValidationVersion"]}") // Kotlin extensions implementation(kotlin("reflect")) // Reactive - implementation("org.springframework.boot:spring-boot-starter-webflux") + implementation("org.springframework.boot:spring-boot-starter-webflux") { + exclude(group = "org.springframework.boot", module = "spring-boot-starter-reactor-netty") + } + implementation("org.springframework.boot:spring-boot-starter-jetty") implementation("io.projectreactor.kotlin:reactor-kotlin-extensions") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") @@ -49,17 +55,19 @@ dependencies { implementation("com.vaadin:vaadin-core") { exclude("com.vaadin:flow-react") } - implementation("com.vaadin:vaadin-spring-boot-starter") + implementation("com.vaadin:vaadin-spring-boot-starter") { + exclude(group = "org.springframework.boot", module = "spring-boot-starter-tomcat") + } + implementation("com.vaadin:hilla-spring-boot-starter") // Logging - implementation("io.github.oshai:kotlin-logging-jvm:7.0.3") + implementation("io.github.oshai:kotlin-logging-jvm:${rootProject.extra["kotlinLoggingVersion"]}") // Persistence & I/O implementation("org.springframework.boot:spring-boot-starter-data-jpa") - implementation("com.github.paulcwarren:spring-content-fs-boot-starter:3.0.17") - implementation("org.flywaydb:flyway-core") - implementation("commons-io:commons-io:2.18.0") - implementation("com.google.guava:guava:33.5.0-jre") + implementation("org.springframework.boot:spring-boot-starter-flyway") + implementation("commons-io:commons-io:${rootProject.extra["commonsIoVersion"]}") + implementation("com.google.guava:guava:${rootProject.extra["guavaVersion"]}") // SSO implementation("org.springframework.boot:spring-boot-starter-oauth2-client") @@ -68,18 +76,19 @@ dependencies { // Notifications implementation("org.springframework.boot:spring-boot-starter-mail") - implementation("ch.digitalfondue.mjml4j:mjml4j:1.1.4") + implementation("ch.digitalfondue.mjml4j:mjml4j:${rootProject.extra["mjml4jVersion"]}") // Plugins implementation(project(":plugin-api")) // Utils - implementation("org.apache.tika:tika-core:3.2.3") - implementation("me.xdrop:fuzzywuzzy:1.4.0") - implementation("com.vanniktech:blurhash:0.3.0") + implementation("org.apache.tika:tika-core:${rootProject.extra["tikaVersion"]}") + implementation("me.xdrop:fuzzywuzzy:${rootProject.extra["fuzzywuzzyVersion"]}") + implementation("com.vanniktech:blurhash:${rootProject.extra["blurhashVersion"]}") // Development developmentOnly("org.springframework.boot:spring-boot-devtools") + developmentOnly("com.vaadin:vaadin-dev") annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") runtimeOnly("com.h2database:h2") runtimeOnly("io.micrometer:micrometer-registry-prometheus") @@ -103,6 +112,30 @@ dependencyManagement { tasks.withType { useJUnitPlatform() + finalizedBy(tasks.jacocoTestReport) +} + +tasks.jacocoTestReport { + dependsOn(tasks.test) + reports { + xml.required = true + xml.outputLocation = layout.buildDirectory.file("reports/jacoco/test/jacocoTestReport.xml") + } +} + +tasks.named("sonar") { + dependsOn(tasks.jacocoTestReport) +} + +sonar { + properties { + property("sonar.organization", "gameyfin") + property("sonar.projectKey", "gameyfin_gameyfin") + property("sonar.projectName", "gameyfin") + property("sonar.host.url", "https://sonarcloud.io") + property("sonar.coverage.jacoco.xmlReportPaths", "build/reports/jacoco/test/jacocoTestReport.xml") + property("sonar.coverage.exclusions", "**/*Config.kt,**/org/gameyfin/db/h2/**") + } } tasks.named("processResources") { diff --git a/app/package-lock.json b/app/package-lock.json index 402ad7c..b01b630 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -1,60 +1,56 @@ { "name": "gameyfin", - "version": "2.3.3-preview", + "version": "2.3.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gameyfin", - "version": "2.3.3-preview", + "version": "2.3.3", "dependencies": { "@heroui/react": "^2.8.7", "@phosphor-icons/react": "^2.1.10", - "@polymer/polymer": "3.5.2", "@react-stately/data": "^3.12.2", "@react-types/shared": "^3.28.0", "@tailwindcss/vite": "4.1.13", - "@vaadin/bundles": "24.9.4", + "@vaadin/aura": "25.0.3", "@vaadin/common-frontend": "0.0.19", - "@vaadin/hilla-file-router": "24.9.4", - "@vaadin/hilla-frontend": "24.9.4", - "@vaadin/hilla-lit-form": "24.9.4", - "@vaadin/hilla-react-auth": "24.9.4", - "@vaadin/hilla-react-crud": "24.9.4", - "@vaadin/hilla-react-form": "24.9.4", - "@vaadin/hilla-react-i18n": "24.9.4", - "@vaadin/hilla-react-signals": "24.9.4", - "@vaadin/polymer-legacy-adapter": "24.9.4", - "@vaadin/react-components": "24.9.4", + "@vaadin/hilla-file-router": "25.0.4", + "@vaadin/hilla-frontend": "25.0.4", + "@vaadin/hilla-lit-form": "25.0.4", + "@vaadin/hilla-react-auth": "25.0.4", + "@vaadin/hilla-react-crud": "25.0.4", + "@vaadin/hilla-react-form": "25.0.4", + "@vaadin/hilla-react-i18n": "25.0.4", + "@vaadin/hilla-react-signals": "25.0.4", + "@vaadin/react-components": "25.0.3", "@vaadin/vaadin-development-mode-detector": "2.0.7", - "@vaadin/vaadin-lumo-styles": "24.9.4", - "@vaadin/vaadin-material-styles": "24.9.4", - "@vaadin/vaadin-themable-mixin": "24.9.4", + "@vaadin/vaadin-lumo-styles": "25.0.3", + "@vaadin/vaadin-themable-mixin": "25.0.3", "@vaadin/vaadin-usage-statistics": "2.1.3", "blurhash": "^2.0.5", "classnames": "^2.5.1", - "construct-style-sheets-polyfill": "3.1.0", - "date-fns": "2.29.3", + "date-fns": "4.1.0", "formik": "^2.4.6", "framer-motion": "^12.23.22", "fzf": "^0.5.2", "http-status-codes": "^2.3.0", - "lit": "3.3.1", + "lit": "3.3.2", "moment": "^2.30.1", "moment-timezone": "^0.5.47", "next-themes": "^0.4.6", "postcss": "^8.5.6", "postcss-import": "^16.1.1", "rand-seed": "^2.1.7", - "react": "19.1.1", + "react": "19.2.3", "react-accessible-treeview": "^2.11.1", "react-aria-components": "^1.7.1", "react-confetti-boom": "^1.0.0", - "react-dom": "19.1.1", + "react-dom": "19.2.3", "react-markdown": "^10.1.0", "react-player": "^2.16.0", "react-realtime-chart": "^0.8.1", - "react-router": "7.6.3", + "react-router": "7.12.0", "react-window": "^2.2.3", "remark-breaks": "^4.0.0", "swiper": "^11.2.6", @@ -62,41 +58,39 @@ "yup": "^1.6.1" }, "devDependencies": { - "@babel/preset-react": "7.27.1", + "@babel/preset-react": "7.28.5", "@lit-labs/react": "^2.1.3", "@preact/signals-react-transform": "0.6.0", - "@rollup/plugin-replace": "6.0.2", + "@rollup/plugin-replace": "6.0.3", "@rollup/pluginutils": "5.3.0", - "@types/node": "^22.4.0", - "@types/react": "19.1.17", - "@types/react-dom": "19.1.11", + "@types/node": "25.0.3", + "@types/react": "19.2.7", + "@types/react-dom": "19.2.3", "@types/react-window": "^1.8.8", - "@vaadin/hilla-generator-cli": "24.9.4", - "@vaadin/hilla-generator-core": "24.9.4", - "@vaadin/hilla-generator-plugin-backbone": "24.9.4", - "@vaadin/hilla-generator-plugin-barrel": "24.9.4", - "@vaadin/hilla-generator-plugin-client": "24.9.4", - "@vaadin/hilla-generator-plugin-model": "24.9.4", - "@vaadin/hilla-generator-plugin-push": "24.9.4", - "@vaadin/hilla-generator-plugin-signals": "24.9.4", - "@vaadin/hilla-generator-plugin-subtypes": "24.9.4", - "@vaadin/hilla-generator-plugin-transfertypes": "24.9.4", - "@vaadin/hilla-generator-utils": "24.9.4", - "@vitejs/plugin-react": "4.7.0", + "@vaadin/hilla-generator-cli": "25.0.4", + "@vaadin/hilla-generator-core": "25.0.4", + "@vaadin/hilla-generator-plugin-backbone": "25.0.4", + "@vaadin/hilla-generator-plugin-barrel": "25.0.4", + "@vaadin/hilla-generator-plugin-client": "25.0.4", + "@vaadin/hilla-generator-plugin-model": "25.0.4", + "@vaadin/hilla-generator-plugin-push": "25.0.4", + "@vaadin/hilla-generator-plugin-signals": "25.0.4", + "@vaadin/hilla-generator-plugin-subtypes": "25.0.4", + "@vaadin/hilla-generator-plugin-transfertypes": "25.0.4", + "@vaadin/hilla-generator-utils": "25.0.4", + "@vitejs/plugin-react": "5.1.2", "@vitejs/plugin-react-swc": "^3.7.0", - "glob": "11.0.3", - "magic-string": "0.30.19", + "baseline-browser-mapping": "^2.9.19", + "magic-string": "0.30.21", "rollup-plugin-brotli": "3.1.0", - "rollup-plugin-visualizer": "5.14.0", + "rollup-plugin-visualizer": "6.0.5", "strip-css-comments": "5.0.0", "tailwindcss": "4.1.13", "transform-ast": "2.4.4", - "typescript": "5.8.3", - "vite": "6.4.1", - "vite-plugin-checker": "0.10.3", - "workbox-build": "7.3.0", - "workbox-core": "7.3.0", - "workbox-precaching": "7.3.0" + "typescript": "5.9.3", + "vite": "7.3.1", + "vite-plugin-checker": "0.12.0", + "workbox-build": "7.4.0" } }, "node_modules/@apideck/better-ajv-errors": { @@ -172,13 +166,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -187,9 +181,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", - "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "dev": true, "license": "MIT", "engines": { @@ -197,21 +191,22 @@ } }, "node_modules/@babel/core": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", - "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.4", - "@babel/types": "^7.28.4", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -228,14 +223,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", - "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.0.tgz", + "integrity": "sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.3", - "@babel/types": "^7.28.2", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -258,13 +253,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", + "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -356,29 +351,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -471,9 +466,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -506,27 +501,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", - "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.4" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -1755,15 +1750,15 @@ } }, "node_modules/@babel/preset-react": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.27.1.tgz", - "integrity": "sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.28.5.tgz", + "integrity": "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-transform-react-display-name": "^7.27.1", + "@babel/plugin-transform-react-display-name": "^7.28.0", "@babel/plugin-transform-react-jsx": "^7.27.1", "@babel/plugin-transform-react-jsx-development": "^7.27.1", "@babel/plugin-transform-react-pure-annotations": "^7.27.1" @@ -1785,33 +1780,33 @@ } }, "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", - "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -1819,23 +1814,23 @@ } }, "node_modules/@babel/types": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", - "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", - "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", "cpu": [ "ppc64" ], @@ -1849,9 +1844,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", - "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", "cpu": [ "arm" ], @@ -1865,9 +1860,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", - "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", "cpu": [ "arm64" ], @@ -1881,9 +1876,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", - "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", "cpu": [ "x64" ], @@ -1897,9 +1892,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", - "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", "cpu": [ "arm64" ], @@ -1913,9 +1908,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", - "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", "cpu": [ "x64" ], @@ -1929,9 +1924,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", - "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", "cpu": [ "arm64" ], @@ -1945,9 +1940,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", - "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", "cpu": [ "x64" ], @@ -1961,9 +1956,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", - "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", "cpu": [ "arm" ], @@ -1977,9 +1972,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", - "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", "cpu": [ "arm64" ], @@ -1993,9 +1988,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", - "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", "cpu": [ "ia32" ], @@ -2009,9 +2004,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", - "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", "cpu": [ "loong64" ], @@ -2025,9 +2020,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", - "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", "cpu": [ "mips64el" ], @@ -2041,9 +2036,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", - "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", "cpu": [ "ppc64" ], @@ -2057,9 +2052,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", - "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", "cpu": [ "riscv64" ], @@ -2073,9 +2068,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", - "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", "cpu": [ "s390x" ], @@ -2089,9 +2084,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", - "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", "cpu": [ "x64" ], @@ -2105,9 +2100,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", - "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", "cpu": [ "arm64" ], @@ -2121,9 +2116,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", - "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", "cpu": [ "x64" ], @@ -2137,9 +2132,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", - "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", "cpu": [ "arm64" ], @@ -2153,9 +2148,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", - "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", "cpu": [ "x64" ], @@ -2169,9 +2164,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", - "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", "cpu": [ "arm64" ], @@ -2185,9 +2180,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", - "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", "cpu": [ "x64" ], @@ -2201,9 +2196,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", - "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", "cpu": [ "arm64" ], @@ -2217,9 +2212,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", - "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", "cpu": [ "ia32" ], @@ -2233,9 +2228,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", - "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", "cpu": [ "x64" ], @@ -5115,6 +5110,7 @@ "resolved": "https://registry.npmjs.org/@heroui/system/-/system-2.4.25.tgz", "integrity": "sha512-F6UUoGTQ+Qas5wYkCzLjXE7u74Z9ygO0u0+dkTW7zCaY7ds65CcmvZ/ahKz2ES3Tk6TNks1MJSyaQ9rFLs8AqA==", "license": "MIT", + "peer": true, "dependencies": { "@heroui/react-utils": "2.1.14", "@heroui/system-rsc": "2.3.21", @@ -5369,6 +5365,7 @@ "resolved": "https://registry.npmjs.org/@heroui/theme/-/theme-2.4.25.tgz", "integrity": "sha512-nTptYhO1V9rMoh9SJDnMfaSmFuoXvbem1UuwgHcraRtqy/TIVBPqv26JEGzSoUCL194TDGOJpqrpMuab/PdXcw==", "license": "MIT", + "peer": true, "dependencies": { "@heroui/shared-utils": "2.1.12", "color": "^4.2.3", @@ -6776,15 +6773,6 @@ "react-dom": ">= 16.8" } }, - "node_modules/@polymer/polymer": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@polymer/polymer/-/polymer-3.5.2.tgz", - "integrity": "sha512-fWwImY/UH4bb2534DVSaX+Azs2yKg8slkMBHOyGeU2kKx7Xmxp6Lee0jP8p6B3d7c1gFUPB2Z976dTUtX81pQA==", - "license": "BSD-3-Clause", - "dependencies": { - "@webcomponents/shadycss": "^1.9.1" - } - }, "node_modules/@preact/signals-core": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.12.1.tgz", @@ -9963,9 +9951,9 @@ } }, "node_modules/@rollup/plugin-replace": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.2.tgz", - "integrity": "sha512-7QaYCf8bqF04dOy7w/eHmJeNExxTYwvKAmlSAH/EaWWUzbT0h5sbF6bktFoX/0F/0qwng5/dWFMyf3gzaM8DsQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.3.tgz", + "integrity": "sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==", "dev": true, "license": "MIT", "dependencies": { @@ -10976,32 +10964,35 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.18.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.8.tgz", - "integrity": "sha512-pAZSHMiagDR7cARo/cch1f3rXy0AEXwsVsVH09FcyeJVAzCnGgmYis7P3JidtTUjyadhTeSo8TgRPswstghDaw==", + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", + "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.16.0" } }, "node_modules/@types/react": { - "version": "19.1.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.17.tgz", - "integrity": "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA==", + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "license": "MIT", + "peer": true, "dependencies": { - "csstype": "^3.0.2" + "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { - "version": "19.1.11", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.11.tgz", - "integrity": "sha512-3BKc/yGdNTYQVVw4idqHtSOcFsgGuBbMveKCOgF8wQ5QtrYOc3jDIlzg3jef04zcXFIHLelyGlj0T+BJ8+KN+w==", + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { - "@types/react": "^19.0.0" + "@types/react": "^19.2.0" } }, "node_modules/@types/react-window": { @@ -11045,508 +11036,11 @@ "integrity": "sha512-g7f0IkJdPW2xhY7H4iE72DAsIyfuwEFc6JWc2tYFwKDMWWAF699vGjrM348cwQuOXgHpe1gWFe+Eiyjx/ewvvw==", "license": "ISC" }, - "node_modules/@vaadin/a11y-base": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/a11y-base/-/a11y-base-24.9.4.tgz", - "integrity": "sha512-y8Rrq84MOyCYJ5rbzWtm7rqP3UNX5r5aspKFVYDNATLVcqFMFqUopz5Tn+YMRMb0MJ3K3GU3vy+xWVcF3WyzAg==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/component-base": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/accordion": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/accordion/-/accordion-24.9.4.tgz", - "integrity": "sha512-AkeNGWA7TOVM8hrh0JzMNdWrRScIS9HN4t4UJ8DFSQVsYwDkixZqDOCkoCGoNJ+iRbKzrTcA2a9rHIuk+qjh+A==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/details": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/app-layout": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/app-layout/-/app-layout-24.9.4.tgz", - "integrity": "sha512-VhFOyQSLV5ALm9C9YNmRw7TrsPZ3R/lwIYhowoYIDHE4o7NbW6EdbgdUYkEBa3EPI2oC5IYZzLkXz+kLYgvCIg==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/button": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/avatar": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/avatar/-/avatar-24.9.4.tgz", - "integrity": "sha512-/CzAHhwjGC8fXpsBlm6oUpj1PKZh7a0Nz451R1XGkNF4We2RK76yEwTkZSEDlJR+6Fx1mDH+nnXU1wplGUVynw==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/tooltip": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/avatar-group": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/avatar-group/-/avatar-group-24.9.4.tgz", - "integrity": "sha512-ao2J8wsubP/HOV18ftl5a9C5gDQ41NYAskQOc+B2Mjv9K+Gt/Nsp+I7vl/vli0gTWYVCbpTAm8oXVSqaOYyRWQ==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/avatar": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/item": "~24.9.4", - "@vaadin/list-box": "~24.9.4", - "@vaadin/overlay": "~24.9.4", - "@vaadin/tooltip": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/bundles": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/bundles/-/bundles-24.9.4.tgz", - "integrity": "sha512-f7NT5IJbBmRCm4HiaPj1ckD49eVPZKSOo5eyU6/c1pDZJBpx19xWcBj14SKqIDvJ0klVOxLSQmi9w/DpkqL9dw==", - "license": "(Apache-2.0 OR SEE LICENSE IN https://vaadin.com/license/cvdl-4.0)", - "peerDependencies": { - "@open-wc/dedupe-mixin": "1.4.0", - "@polymer/polymer": "3.5.2", - "@vaadin/a11y-base": "24.9.4", - "@vaadin/accordion": "24.9.4", - "@vaadin/app-layout": "24.9.4", - "@vaadin/avatar": "24.9.4", - "@vaadin/avatar-group": "24.9.4", - "@vaadin/board": "24.9.4", - "@vaadin/button": "24.9.4", - "@vaadin/card": "24.9.4", - "@vaadin/charts": "24.9.4", - "@vaadin/checkbox": "24.9.4", - "@vaadin/checkbox-group": "24.9.4", - "@vaadin/combo-box": "24.9.4", - "@vaadin/component-base": "24.9.4", - "@vaadin/confirm-dialog": "24.9.4", - "@vaadin/context-menu": "24.9.4", - "@vaadin/cookie-consent": "24.9.4", - "@vaadin/crud": "24.9.4", - "@vaadin/custom-field": "24.9.4", - "@vaadin/dashboard": "24.9.4", - "@vaadin/date-picker": "24.9.4", - "@vaadin/date-time-picker": "24.9.4", - "@vaadin/details": "24.9.4", - "@vaadin/dialog": "24.9.4", - "@vaadin/email-field": "24.9.4", - "@vaadin/field-base": "24.9.4", - "@vaadin/field-highlighter": "24.9.4", - "@vaadin/form-layout": "24.9.4", - "@vaadin/grid": "24.9.4", - "@vaadin/grid-pro": "24.9.4", - "@vaadin/horizontal-layout": "24.9.4", - "@vaadin/icon": "24.9.4", - "@vaadin/icons": "24.9.4", - "@vaadin/input-container": "24.9.4", - "@vaadin/integer-field": "24.9.4", - "@vaadin/item": "24.9.4", - "@vaadin/list-box": "24.9.4", - "@vaadin/lit-renderer": "24.9.4", - "@vaadin/login": "24.9.4", - "@vaadin/map": "24.9.4", - "@vaadin/markdown": "24.9.4", - "@vaadin/master-detail-layout": "24.9.4", - "@vaadin/menu-bar": "24.9.4", - "@vaadin/message-input": "24.9.4", - "@vaadin/message-list": "24.9.4", - "@vaadin/multi-select-combo-box": "24.9.4", - "@vaadin/notification": "24.9.4", - "@vaadin/number-field": "24.9.4", - "@vaadin/overlay": "24.9.4", - "@vaadin/password-field": "24.9.4", - "@vaadin/polymer-legacy-adapter": "24.9.4", - "@vaadin/popover": "24.9.4", - "@vaadin/progress-bar": "24.9.4", - "@vaadin/radio-group": "24.9.4", - "@vaadin/rich-text-editor": "24.9.4", - "@vaadin/scroller": "24.9.4", - "@vaadin/select": "24.9.4", - "@vaadin/side-nav": "24.9.4", - "@vaadin/split-layout": "24.9.4", - "@vaadin/tabs": "24.9.4", - "@vaadin/tabsheet": "24.9.4", - "@vaadin/text-area": "24.9.4", - "@vaadin/text-field": "24.9.4", - "@vaadin/time-picker": "24.9.4", - "@vaadin/tooltip": "24.9.4", - "@vaadin/upload": "24.9.4", - "@vaadin/vaadin-development-mode-detector": "2.0.7", - "@vaadin/vaadin-lumo-styles": "24.9.4", - "@vaadin/vaadin-themable-mixin": "24.9.4", - "@vaadin/vaadin-usage-statistics": "2.1.3", - "@vaadin/vertical-layout": "24.9.4", - "@vaadin/virtual-list": "24.9.4", - "cookieconsent": "3.1.1", - "dompurify": "3.2.7", - "highcharts": "9.2.2", - "lit": "3.3.1", - "marked": "15.0.12", - "ol": "6.13.0", - "quickselect": "2.0.0", - "rbush": "3.0.1" - }, - "peerDependenciesMeta": { - "@open-wc/dedupe-mixin": { - "optional": true - }, - "@polymer/polymer": { - "optional": true - }, - "@vaadin/a11y-base": { - "optional": true - }, - "@vaadin/accordion": { - "optional": true - }, - "@vaadin/app-layout": { - "optional": true - }, - "@vaadin/avatar": { - "optional": true - }, - "@vaadin/avatar-group": { - "optional": true - }, - "@vaadin/board": { - "optional": true - }, - "@vaadin/button": { - "optional": true - }, - "@vaadin/card": { - "optional": true - }, - "@vaadin/charts": { - "optional": true - }, - "@vaadin/checkbox": { - "optional": true - }, - "@vaadin/checkbox-group": { - "optional": true - }, - "@vaadin/combo-box": { - "optional": true - }, - "@vaadin/component-base": { - "optional": true - }, - "@vaadin/confirm-dialog": { - "optional": true - }, - "@vaadin/context-menu": { - "optional": true - }, - "@vaadin/cookie-consent": { - "optional": true - }, - "@vaadin/crud": { - "optional": true - }, - "@vaadin/custom-field": { - "optional": true - }, - "@vaadin/dashboard": { - "optional": true - }, - "@vaadin/date-picker": { - "optional": true - }, - "@vaadin/date-time-picker": { - "optional": true - }, - "@vaadin/details": { - "optional": true - }, - "@vaadin/dialog": { - "optional": true - }, - "@vaadin/email-field": { - "optional": true - }, - "@vaadin/field-base": { - "optional": true - }, - "@vaadin/field-highlighter": { - "optional": true - }, - "@vaadin/form-layout": { - "optional": true - }, - "@vaadin/grid": { - "optional": true - }, - "@vaadin/grid-pro": { - "optional": true - }, - "@vaadin/horizontal-layout": { - "optional": true - }, - "@vaadin/icon": { - "optional": true - }, - "@vaadin/icons": { - "optional": true - }, - "@vaadin/input-container": { - "optional": true - }, - "@vaadin/integer-field": { - "optional": true - }, - "@vaadin/item": { - "optional": true - }, - "@vaadin/list-box": { - "optional": true - }, - "@vaadin/lit-renderer": { - "optional": true - }, - "@vaadin/login": { - "optional": true - }, - "@vaadin/map": { - "optional": true - }, - "@vaadin/markdown": { - "optional": true - }, - "@vaadin/master-detail-layout": { - "optional": true - }, - "@vaadin/menu-bar": { - "optional": true - }, - "@vaadin/message-input": { - "optional": true - }, - "@vaadin/message-list": { - "optional": true - }, - "@vaadin/multi-select-combo-box": { - "optional": true - }, - "@vaadin/notification": { - "optional": true - }, - "@vaadin/number-field": { - "optional": true - }, - "@vaadin/overlay": { - "optional": true - }, - "@vaadin/password-field": { - "optional": true - }, - "@vaadin/polymer-legacy-adapter": { - "optional": true - }, - "@vaadin/popover": { - "optional": true - }, - "@vaadin/progress-bar": { - "optional": true - }, - "@vaadin/radio-group": { - "optional": true - }, - "@vaadin/rich-text-editor": { - "optional": true - }, - "@vaadin/scroller": { - "optional": true - }, - "@vaadin/select": { - "optional": true - }, - "@vaadin/side-nav": { - "optional": true - }, - "@vaadin/split-layout": { - "optional": true - }, - "@vaadin/tabs": { - "optional": true - }, - "@vaadin/tabsheet": { - "optional": true - }, - "@vaadin/text-area": { - "optional": true - }, - "@vaadin/text-field": { - "optional": true - }, - "@vaadin/time-picker": { - "optional": true - }, - "@vaadin/tooltip": { - "optional": true - }, - "@vaadin/upload": { - "optional": true - }, - "@vaadin/vaadin-development-mode-detector": { - "optional": true - }, - "@vaadin/vaadin-lumo-styles": { - "optional": true - }, - "@vaadin/vaadin-themable-mixin": { - "optional": true - }, - "@vaadin/vaadin-usage-statistics": { - "optional": true - }, - "@vaadin/vertical-layout": { - "optional": true - }, - "@vaadin/virtual-list": { - "optional": true - }, - "cookieconsent": { - "optional": true - }, - "dompurify": { - "optional": true - }, - "highcharts": { - "optional": true - }, - "lit": { - "optional": true - }, - "marked": { - "optional": true - }, - "ol": { - "optional": true - }, - "quickselect": { - "optional": true - }, - "rbush": { - "optional": true - } - } - }, - "node_modules/@vaadin/button": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/button/-/button-24.9.4.tgz", - "integrity": "sha512-Wg+gnrQ3LT9WpJGT91WCXFpX9HOpReyoq77K2jjsnzx2ZN6d3iHmPImVIdA658fLE6U3RCyRazQymoWQ01VEuA==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/card": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/card/-/card-24.9.4.tgz", - "integrity": "sha512-DzHGUjA9ESkAC6oJrAUx28OunwSs33lwNWQzq0Xh64mWafUWMjc0k6QvGc45rdXOY/MZXkG21SWqahAVi7qWVA==", - "license": "Apache-2.0", - "dependencies": { - "@vaadin/component-base": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/checkbox": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/checkbox/-/checkbox-24.9.4.tgz", - "integrity": "sha512-B381QSKkmi0bpnmw+BV/oMGrEAkLVHxrL75mrrl0jEzA9nzc+vTsb72cONnbp6XCO17hb+sVQ6gdAxBrSsr59A==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/field-base": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/checkbox-group": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/checkbox-group/-/checkbox-group-24.9.4.tgz", - "integrity": "sha512-LpqbUpzWLlC3lXGRNrbMPAb6rOA7sOQYPrfonw1aVM3u4IiS+T9ffKCQHeTrxeV+S0Ddmiu0rCA2q7j4EcMEPw==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/checkbox": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/field-base": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/combo-box": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/combo-box/-/combo-box-24.9.4.tgz", - "integrity": "sha512-DrAxKvu6rF/o2t1rP5zEWEj2A4R0OqkbkdaqbFOJDMdVSTHYOTHzmu/M38jRB9owSsTGJW9q9L2ATJcio2NECA==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/field-base": "~24.9.4", - "@vaadin/input-container": "~24.9.4", - "@vaadin/item": "~24.9.4", - "@vaadin/lit-renderer": "~24.9.4", - "@vaadin/overlay": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } + "node_modules/@vaadin/aura": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/aura/-/aura-25.0.3.tgz", + "integrity": "sha512-+YvapYD82RCjPfNBeBCQMpE1VD3I8ebognuJCysGjMncA38OfToVn92ONsYZfkUBSdNH7GZditk7vnkQhpeZkQ==", + "license": "Apache-2.0" }, "node_modules/@vaadin/common-frontend": { "version": "0.0.19", @@ -11560,239 +11054,18 @@ "lit": "^3.0.0" } }, - "node_modules/@vaadin/component-base": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/component-base/-/component-base-24.9.4.tgz", - "integrity": "sha512-bS28kpICJodSHTEJQwQexNGkhdXbPtPhmUJGWk6gx+9Dmuzeq5C/xlnkYpuRbGnvR4lGUL2SdK6ZlhcPzKXncQ==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/vaadin-development-mode-detector": "^2.0.0", - "@vaadin/vaadin-usage-statistics": "^2.1.0", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/confirm-dialog": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/confirm-dialog/-/confirm-dialog-24.9.4.tgz", - "integrity": "sha512-RTkr9k8HaYYnV4a/DoPFuDWSO3+LjubM8+FhOio8RgGyMWyyEXIlhdFqsCwV7EmyEZ3rje8Mk5zpUSabBN9fVQ==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/button": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/dialog": "~24.9.4", - "@vaadin/overlay": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/context-menu": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/context-menu/-/context-menu-24.9.4.tgz", - "integrity": "sha512-/ESoTcMP6EGpyVYoueEKwCSspbnOLlMFYChLiPreCTPJddvPAX1Wvip9VA9JHzEPqYlJ8f9sf0fCPCFYF+WNfQ==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/item": "~24.9.4", - "@vaadin/list-box": "~24.9.4", - "@vaadin/lit-renderer": "~24.9.4", - "@vaadin/overlay": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/custom-field": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/custom-field/-/custom-field-24.9.4.tgz", - "integrity": "sha512-hIcs5V7u6HnlL4t7/JIUTrCRTI0bcupH6Yo5gyO+8g9TojVLH9XJvg148P4pRD/aoSEaC7N2GjrB86051hdqhg==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/field-base": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/date-picker": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/date-picker/-/date-picker-24.9.4.tgz", - "integrity": "sha512-CiPt0KKPfmQvdijzP+J8nZmv/Gg1o2segUeeSovwX0y2zi9CDGPhBOEh7X+ZqoouRBc/XzaCaziZv5U1asExqg==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.2.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/button": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/field-base": "~24.9.4", - "@vaadin/input-container": "~24.9.4", - "@vaadin/overlay": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/date-time-picker": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/date-time-picker/-/date-time-picker-24.9.4.tgz", - "integrity": "sha512-GQsxm0h5vjam1KrM2g6AN7/fb9ZV3JiL8asW4t5FczvefjRUapSZCyqQem8z/AAqea/Nifyy/67tH8PwbEEISg==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/custom-field": "~24.9.4", - "@vaadin/date-picker": "~24.9.4", - "@vaadin/field-base": "~24.9.4", - "@vaadin/time-picker": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/details": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/details/-/details-24.9.4.tgz", - "integrity": "sha512-bEQsVGBCtTc49g9jpVUCHw/JfkZxHlHZ6N9y2CsYb3cCfpOoXPnhQsDPBmOlnCFSGOwwgQC2F2gi69P3rdZR0Q==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/button": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/dialog": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/dialog/-/dialog-24.9.4.tgz", - "integrity": "sha512-p+MXYfn1/OliqElQKy31/Wopo133HH5b/JoYt6dd2+Kn2YYXUR1LzDBxsMXumiEdkoowiUary7xFNvaSteStlw==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/component-base": "~24.9.4", - "@vaadin/lit-renderer": "~24.9.4", - "@vaadin/overlay": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/email-field": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/email-field/-/email-field-24.9.4.tgz", - "integrity": "sha512-GWf20H4j8Mad2+cm/2pViAKllW3kIweNjoIR64rqUvooSKrEHzoAmi+iKuCKTkaW3ljub2BVTQ3WCqYkAiA4jA==", - "license": "Apache-2.0", - "dependencies": { - "@polymer/polymer": "^3.0.0", - "@vaadin/component-base": "~24.9.4", - "@vaadin/text-field": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/field-base": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/field-base/-/field-base-24.9.4.tgz", - "integrity": "sha512-ANUMWe08i0BTv9DoMcStdtxy8gKnvt7ERCLSINrIPAI19tAhrooifHXYxr6ig7ebK4gmjIc1TKqLeelWjesBhA==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/field-highlighter": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/field-highlighter/-/field-highlighter-24.9.4.tgz", - "integrity": "sha512-8hFSAjXyKUR5Y+fSytJTp9wiKxDAjXHoUd1TxsSUvOMmG7p9tYQ25xT9tnFCR3O9bFUdb0TPF8Uo2+6oFUjYNA==", - "license": "Apache-2.0", - "dependencies": { - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/overlay": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/form-layout": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/form-layout/-/form-layout-24.9.4.tgz", - "integrity": "sha512-o0HO2hrOiXT0Pdqf/DxqsckRTtoIyUjDZX+RC+A4h9KxyUKtIDGHNmJEFfZq9p9iZeJNCz/Yejvq8tb8BtwfwA==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/grid": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/grid/-/grid-24.9.4.tgz", - "integrity": "sha512-Dxr+hB4cjr3AKGE7bhn7V5TTjIU2aAvsDHx2canxUj823hTrJ6OuVY7auuDyxEwZCduTUVK4dSlY6C0GLSGqwA==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/checkbox": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/lit-renderer": "~24.9.4", - "@vaadin/text-field": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, "node_modules/@vaadin/hilla-file-router": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/hilla-file-router/-/hilla-file-router-24.9.4.tgz", - "integrity": "sha512-lyq0Q/iqKhcCJF71JPWhyPzNGW57IpOA65hW3pcRYuXK5yNwSMXjeG3RMF6nOl8hWaNoriu3316pYSij9+U8QA==", + "version": "25.0.4", + "resolved": "https://registry.npmjs.org/@vaadin/hilla-file-router/-/hilla-file-router-25.0.4.tgz", + "integrity": "sha512-XTkBz0uCwH4qJNSUz2XmVcaEfZZu+1tU+S1RikeUgc1wpFTwIlmxyj1uF4EfJsALgkGmtKiyTLQZjVX2zeBIJg==", "license": "Apache-2.0", "dependencies": { "@ungap/with-resolvers": "0.1.0", - "@vaadin/hilla-generator-utils": "24.9.4", - "@vaadin/hilla-react-auth": "24.9.4", - "@vaadin/hilla-react-signals": "24.9.4", + "@vaadin/hilla-generator-utils": "25.0.4", + "@vaadin/hilla-react-auth": "25.0.4", + "@vaadin/hilla-react-signals": "25.0.4", "tsc-template": "0.2.3", - "typescript": "5.8.3" + "typescript": "5.9.3" }, "peerDependencies": { "react": "18 || 19", @@ -11801,9 +11074,9 @@ } }, "node_modules/@vaadin/hilla-frontend": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/hilla-frontend/-/hilla-frontend-24.9.4.tgz", - "integrity": "sha512-PPeY30djTdAtML1+fISgT39e/Vs7jns9laMarm/g/egJF25iQYC8m/mrbfZkvTMHUWgreISTLfPOqb6SjAO72w==", + "version": "25.0.4", + "resolved": "https://registry.npmjs.org/@vaadin/hilla-frontend/-/hilla-frontend-25.0.4.tgz", + "integrity": "sha512-/GCV/OLRessUZ+mtfQYrSLMkJF0XE5PcgDOYznsQ5C+3F53hBcK/GKr/wKmGnhUuzSZJUJyLrkwUPHanH8oYEg==", "license": "Apache-2.0", "dependencies": { "@vaadin/common-frontend": "0.0.19", @@ -11815,14 +11088,14 @@ } }, "node_modules/@vaadin/hilla-generator-cli": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-cli/-/hilla-generator-cli-24.9.4.tgz", - "integrity": "sha512-kNy7jtg7afCGpnPI4wcSma6VzZ9n+IxqorrxatV3gtMsJxwDcwAD87IRNjRnyZUx3nJBTyX2aVG+EyQ1/upHmQ==", + "version": "25.0.4", + "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-cli/-/hilla-generator-cli-25.0.4.tgz", + "integrity": "sha512-ksrGGMeF2CG50GAsFatIUMFQ5cV+cFHa9IaKyAJg6baH2Fe154W4dlm4ykqhUKuTJQ0iAyuBaIeL8WjWV+4hqA==", "dev": true, "license": "Apache 2.0", "dependencies": { - "@vaadin/hilla-generator-core": "24.9.4", - "@vaadin/hilla-generator-utils": "24.9.4" + "@vaadin/hilla-generator-core": "25.0.4", + "@vaadin/hilla-generator-utils": "25.0.4" }, "bin": { "tsgen": "bin/index.js" @@ -11832,199 +11105,199 @@ } }, "node_modules/@vaadin/hilla-generator-core": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-core/-/hilla-generator-core-24.9.4.tgz", - "integrity": "sha512-fTcknNOuiO4XWJBWPnnvWOyzQ2NOQM8kQ15YqegLhrxQNI3Q0nN3L/vaexRNf3E6bMk7fzlqhhAYG18SWrT0qA==", + "version": "25.0.4", + "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-core/-/hilla-generator-core-25.0.4.tgz", + "integrity": "sha512-nsfvoNV0EPR5zDWJOdTctXmmOJsGacIAjAkJZEhpjLhdTKGVZL90YUo9YaD0wHO3CRr2HbG8JqquGQmcR/14ag==", "dev": true, "license": "Apache 2.0", "dependencies": { "@apidevtools/swagger-parser": "10.1.1", - "@vaadin/hilla-generator-utils": "24.9.4", + "@vaadin/hilla-generator-utils": "25.0.4", "meow": "13.2.0", "openapi-types": "12.1.3", - "typescript": "5.8.3" + "typescript": "5.9.3" }, "engines": { "node": ">= 16.13" } }, "node_modules/@vaadin/hilla-generator-plugin-backbone": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-backbone/-/hilla-generator-plugin-backbone-24.9.4.tgz", - "integrity": "sha512-G00EI3A/JL3TptY9RmD9ChKEZods/fujaIzuhLSmjaHKJBYMrZ/98lqGz97ASCLsguZ7lBVsGqLoDKHCxVW0uQ==", + "version": "25.0.4", + "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-backbone/-/hilla-generator-plugin-backbone-25.0.4.tgz", + "integrity": "sha512-u2BZ2QpXXqc94Dd93Vgc9gW9ZUt8oJZJuGo2AA/nbVUAChnIaaPtTRmHoTbeGUbanCBVmE6z2t6WKnDhla2rOA==", "dev": true, "license": "Apache 2.0", "dependencies": { - "@vaadin/hilla-generator-core": "24.9.4", - "@vaadin/hilla-generator-plugin-client": "24.9.4", - "@vaadin/hilla-generator-utils": "24.9.4", + "@vaadin/hilla-generator-core": "25.0.4", + "@vaadin/hilla-generator-plugin-client": "25.0.4", + "@vaadin/hilla-generator-utils": "25.0.4", "fast-deep-equal": "3.1.3", "openapi-types": "12.1.3", - "typescript": "5.8.3" + "typescript": "5.9.3" }, "engines": { "node": ">= 16.13" } }, "node_modules/@vaadin/hilla-generator-plugin-barrel": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-barrel/-/hilla-generator-plugin-barrel-24.9.4.tgz", - "integrity": "sha512-RC9IXE0uxHA5kgoJOf2AO4FBM2aaeQ+ApMiQkfhfATJ5YmGwdXHoGSrgETl5TelnHxYheUdyK1Ot193Cpt2upg==", + "version": "25.0.4", + "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-barrel/-/hilla-generator-plugin-barrel-25.0.4.tgz", + "integrity": "sha512-jJEthR8pOIyKfnF/seWBb6ZaF+/ytBOkp2g+qDSybirhjsmn4yRetx9jKvwrVnpVRTprhqZ/2jxVHE09kqW5rw==", "dev": true, "license": "Apache 2.0", "dependencies": { - "@vaadin/hilla-generator-core": "24.9.4", - "@vaadin/hilla-generator-plugin-backbone": "24.9.4", - "@vaadin/hilla-generator-utils": "24.9.4", - "typescript": "5.8.3" + "@vaadin/hilla-generator-core": "25.0.4", + "@vaadin/hilla-generator-plugin-backbone": "25.0.4", + "@vaadin/hilla-generator-utils": "25.0.4", + "typescript": "5.9.3" }, "engines": { "node": ">= 16.13" } }, "node_modules/@vaadin/hilla-generator-plugin-client": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-client/-/hilla-generator-plugin-client-24.9.4.tgz", - "integrity": "sha512-yFPvIZ8yu99JFC5qACckD/qDE162pj6LQphN0sc8XoU/jyG9U11BzV1lpNqaJfB3lIHkpi7fRP4Rsp6m1Ta6iQ==", + "version": "25.0.4", + "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-client/-/hilla-generator-plugin-client-25.0.4.tgz", + "integrity": "sha512-kLHssF339KPE+FtOLOt9LqcvKzWDIfwL6Dkcb+sdy0hQ3rmamHsG89wUiFqAGt4WIRQmmKTjsDprL+hub9r7LA==", "dev": true, "license": "Apache 2.0", "dependencies": { - "@vaadin/hilla-generator-core": "24.9.4", - "@vaadin/hilla-generator-utils": "24.9.4", - "typescript": "5.8.3" + "@vaadin/hilla-generator-core": "25.0.4", + "@vaadin/hilla-generator-utils": "25.0.4", + "typescript": "5.9.3" }, "engines": { "node": ">= 16.13" } }, "node_modules/@vaadin/hilla-generator-plugin-model": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-model/-/hilla-generator-plugin-model-24.9.4.tgz", - "integrity": "sha512-YdE1c9M1HLLWI7/Aop1WTIHytvD5KntCrme0E5txCwiOjgcrkKqw1u7uYj0W3USujyAS+xmejnHqG8tK7HrDZw==", + "version": "25.0.4", + "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-model/-/hilla-generator-plugin-model-25.0.4.tgz", + "integrity": "sha512-hsu1LkOu/8Ceuij2T2dfgtGCwjWfCzD/eHvymeh2mtMf3hyrknvvFHbZVOMQB1ucHXtlnq5rcSZ370iDxWwqSw==", "dev": true, "license": "Apache 2.0", "dependencies": { - "@vaadin/hilla-generator-core": "24.9.4", - "@vaadin/hilla-generator-plugin-backbone": "24.9.4", - "@vaadin/hilla-generator-utils": "24.9.4", - "@vaadin/hilla-lit-form": "24.9.4", + "@vaadin/hilla-generator-core": "25.0.4", + "@vaadin/hilla-generator-plugin-backbone": "25.0.4", + "@vaadin/hilla-generator-utils": "25.0.4", + "@vaadin/hilla-lit-form": "25.0.4", "fast-deep-equal": "3.1.3", "openapi-types": "12.1.3", - "typescript": "5.8.3" + "typescript": "5.9.3" }, "engines": { "node": ">= 16.13" } }, "node_modules/@vaadin/hilla-generator-plugin-push": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-push/-/hilla-generator-plugin-push-24.9.4.tgz", - "integrity": "sha512-61ecykrdkse3y6XuXdceAjUUqIdsprOQGdx+PLWirtwIB59dj3zpR6WvaIhaXd0hKNjs/14zsI3kPhKO+ArVAA==", + "version": "25.0.4", + "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-push/-/hilla-generator-plugin-push-25.0.4.tgz", + "integrity": "sha512-6ivbRjEGbhaq8t8dbn8EfOwHa8sC2YblPxO/hUz6PWAXJfxuT67VtGtk+g5OMWvrQ7HUId3nTFvKYJj/F7eOBw==", "dev": true, "license": "Apache 2.0", "dependencies": { - "@vaadin/hilla-generator-core": "24.9.4", - "@vaadin/hilla-generator-plugin-client": "24.9.4", - "@vaadin/hilla-generator-utils": "24.9.4", + "@vaadin/hilla-generator-core": "25.0.4", + "@vaadin/hilla-generator-plugin-client": "25.0.4", + "@vaadin/hilla-generator-utils": "25.0.4", "fast-deep-equal": "3.1.3", "openapi-types": "12.1.3", - "typescript": "5.8.3" + "typescript": "5.9.3" }, "engines": { "node": ">= 16.13" } }, "node_modules/@vaadin/hilla-generator-plugin-signals": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-signals/-/hilla-generator-plugin-signals-24.9.4.tgz", - "integrity": "sha512-69EsE2+A83yZxC/+kmFKM8Csnplm8k39i9mll7kAOsNx20BK4eCqMabumB8tuB3B/Q4SjAhLjv7ARlGZ4YFLSA==", + "version": "25.0.4", + "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-signals/-/hilla-generator-plugin-signals-25.0.4.tgz", + "integrity": "sha512-N5qJeO9Fer5ll+n4BguPZ6hPwCPiDHPBavxU6REUWNAOGX6J9pxpsR3zag/NuqFU1L4ybxPgDuPpxF1roDJJwg==", "dev": true, "license": "Apache 2.0", "dependencies": { - "@vaadin/hilla-generator-core": "24.9.4", - "@vaadin/hilla-generator-utils": "24.9.4", + "@vaadin/hilla-generator-core": "25.0.4", + "@vaadin/hilla-generator-utils": "25.0.4", "fast-deep-equal": "3.1.3", "iterator-helpers-polyfill": "3.0.1", "openapi-types": "12.1.3", "tsc-template": "0.2.3", - "typescript": "5.8.3" + "typescript": "5.9.3" }, "engines": { "node": ">= 16.13" } }, "node_modules/@vaadin/hilla-generator-plugin-subtypes": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-subtypes/-/hilla-generator-plugin-subtypes-24.9.4.tgz", - "integrity": "sha512-psD5wXyG2nRjZAFHutfAkewp6MBdde2NrKWF3o4K7It8Zuf4hQtIN8wWeofFKQL8rY1NgVaRjLsxzrMgkXz5jg==", + "version": "25.0.4", + "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-subtypes/-/hilla-generator-plugin-subtypes-25.0.4.tgz", + "integrity": "sha512-NeRjxBeosxarsxLmclDt1z2Fc2WhZLgdmDYDWhi8DJMNo1IzH9DnqEuAkGRij9CmbcYqD5g2CxZPBYswY5e1zQ==", "dev": true, "license": "Apache 2.0", "dependencies": { - "@vaadin/hilla-generator-core": "24.9.4", - "@vaadin/hilla-generator-plugin-client": "24.9.4", - "@vaadin/hilla-generator-plugin-model": "24.9.4", - "@vaadin/hilla-generator-utils": "24.9.4", + "@vaadin/hilla-generator-core": "25.0.4", + "@vaadin/hilla-generator-plugin-client": "25.0.4", + "@vaadin/hilla-generator-plugin-model": "25.0.4", + "@vaadin/hilla-generator-utils": "25.0.4", "fast-deep-equal": "^3.1.3", "openapi-types": "^12.1.3", - "typescript": "5.8.3" + "typescript": "5.9.3" }, "engines": { "node": ">= 16.13" } }, "node_modules/@vaadin/hilla-generator-plugin-transfertypes": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-transfertypes/-/hilla-generator-plugin-transfertypes-24.9.4.tgz", - "integrity": "sha512-qL2Q1Qu5KNVFEW3wMe5docSH4JCURNL1/jIJbVj5P3JEpTUiII7eieLoa9qcvWW0VonQWkCgrkGNmth7J5S9Zw==", + "version": "25.0.4", + "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-transfertypes/-/hilla-generator-plugin-transfertypes-25.0.4.tgz", + "integrity": "sha512-eJ8EhmC/THSLNsl7FXzGWg0c6qKl9U9Bp9zLP1RI7BJX7s43sOFXH8TKSqnPShf6RReqIUV5UKoGLdvWO/xrIQ==", "dev": true, "license": "Apache 2.0", "dependencies": { - "@vaadin/hilla-generator-core": "24.9.4", - "@vaadin/hilla-generator-plugin-client": "24.9.4", - "@vaadin/hilla-generator-plugin-model": "24.9.4", - "@vaadin/hilla-generator-utils": "24.9.4", + "@vaadin/hilla-generator-core": "25.0.4", + "@vaadin/hilla-generator-plugin-client": "25.0.4", + "@vaadin/hilla-generator-plugin-model": "25.0.4", + "@vaadin/hilla-generator-utils": "25.0.4", "fast-deep-equal": "3.1.3", "openapi-types": "12.1.3", - "typescript": "5.8.3" + "typescript": "5.9.3" }, "engines": { "node": ">= 16.13" } }, "node_modules/@vaadin/hilla-generator-utils": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-utils/-/hilla-generator-utils-24.9.4.tgz", - "integrity": "sha512-qdVY964K3N5Jtsc12QS1TEfWulWhS/bz1QlnxfhcXM/auad3WC8gjve7U5GLUaR6jk4xxL4RaU0SLgXLI26WPg==", + "version": "25.0.4", + "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-utils/-/hilla-generator-utils-25.0.4.tgz", + "integrity": "sha512-YHVv87fzHfSzBSBoFTt8RpeOPopvEtU+zPwE3SwvD40iHlWr5JaElyAUpGAXu7BksV0E/h58Q6HZV/oM2ms07w==", "license": "Apache 2.0", "dependencies": { "pino": "9.6.0", "pino-pretty": "10.3.1", - "typescript": "5.8.3" + "typescript": "5.9.3" }, "engines": { "node": ">= 16.13" } }, "node_modules/@vaadin/hilla-lit-form": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/hilla-lit-form/-/hilla-lit-form-24.9.4.tgz", - "integrity": "sha512-2BcQqQVEdfsOqgq+Q2M0Po569xpHXZ65zzu3sO1+g9hGfiQ+seziTdthMtfpsscdHbNeD27OzzEu1CyEeYmRQA==", + "version": "25.0.4", + "resolved": "https://registry.npmjs.org/@vaadin/hilla-lit-form/-/hilla-lit-form-25.0.4.tgz", + "integrity": "sha512-+1GapkJdHew/Y9aqb6GWUIca1mD/fTm291PV/j89X0UbLbAAnqJpD3lZBraBpjwDoLLN0+Ed5ShSigE7KNcbLQ==", "license": "Apache-2.0", "dependencies": { - "@vaadin/hilla-frontend": "24.9.4", - "validator": "13.12.0" + "@vaadin/hilla-frontend": "25.0.4", + "validator": "13.15.22" }, "peerDependencies": { "lit": "^3.0.0" } }, "node_modules/@vaadin/hilla-react-auth": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-auth/-/hilla-react-auth-24.9.4.tgz", - "integrity": "sha512-bJTdmpnYY4OhkHEJFJD8hr8IeOwXXX37mdcZ5oxPCO/ua0Mj/ZJLIuIcmspd9hhynhKF5zeD6C54PNXf1F4I7A==", + "version": "25.0.4", + "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-auth/-/hilla-react-auth-25.0.4.tgz", + "integrity": "sha512-14wYCS/Ne9oLEEf9j8pcsS7qFGwzLfxglM5ryvN5M3XUi21Nvy558CA1FgVCenjZLa4763Tc0Ay6Z58nwPG1PA==", "license": "Apache-2.0", "dependencies": { - "@vaadin/hilla-frontend": "24.9.4" + "@vaadin/hilla-frontend": "25.0.4" }, "peerDependencies": { "react": "18 || 19", @@ -12033,16 +11306,16 @@ } }, "node_modules/@vaadin/hilla-react-crud": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-crud/-/hilla-react-crud-24.9.4.tgz", - "integrity": "sha512-eN2SXMAvz933ijz9TOxeBJOOIJTJLLOPbBpDjSZU7atiGuNcWunwJkTaZxE7rMGDqdL9aHIuqvHhnP4hcuBTQA==", + "version": "25.0.4", + "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-crud/-/hilla-react-crud-25.0.4.tgz", + "integrity": "sha512-V8H1kimuwikXYr9EqZEmzPLOHvQJCHznmPhMe1o4XAsx7fWO4QzX9RIu9zMWWHQpJeIkaz93rQfVy8Qsm7RNyg==", "license": "Apache-2.0", "dependencies": { - "@vaadin/hilla-frontend": "24.9.4", - "@vaadin/hilla-lit-form": "24.9.4", - "@vaadin/hilla-react-form": "24.9.4", - "@vaadin/react-components": "24.9.4", - "@vaadin/vaadin-lumo-styles": "24.9.4", + "@vaadin/hilla-frontend": "25.0.4", + "@vaadin/hilla-lit-form": "25.0.4", + "@vaadin/hilla-react-form": "25.0.4", + "@vaadin/react-components": "25.0.3", + "@vaadin/vaadin-lumo-styles": "25.0.3", "type-fest": "4.35.0" }, "peerDependencies": { @@ -12051,12 +11324,12 @@ } }, "node_modules/@vaadin/hilla-react-form": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-form/-/hilla-react-form-24.9.4.tgz", - "integrity": "sha512-s+qcp4UjaKbE2osEB+1ZSNLrLc2ssaV5JFB+3aYUtmuU9facqpNE3PsmlIH1pavb2MbrpQo52x7Xz/m766CYOg==", + "version": "25.0.4", + "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-form/-/hilla-react-form-25.0.4.tgz", + "integrity": "sha512-OAQN8q8EWgUqcklzSrYOnmp7+3OHzCHZggmn3I5Ix869R9PtvZ/p41aUiXYxXj1ILbVnNzzIHUZUEOIyrHBzyw==", "license": "Apache-2.0", "dependencies": { - "@vaadin/hilla-lit-form": "24.9.4" + "@vaadin/hilla-lit-form": "25.0.4" }, "peerDependencies": { "react": "18 || 19", @@ -12064,13 +11337,13 @@ } }, "node_modules/@vaadin/hilla-react-i18n": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-i18n/-/hilla-react-i18n-24.9.4.tgz", - "integrity": "sha512-XGSBK+Nz+kHHZyXQsrER6C2n4vSsPERA8uxPUiunAs4MoEDqo6k8ONNmp9eRoSUFGIWz/4w3iOgibmf2sPOy+A==", + "version": "25.0.4", + "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-i18n/-/hilla-react-i18n-25.0.4.tgz", + "integrity": "sha512-JI57/9IkluxKYSIBM0e1q/+H02ZUYWpMxsyGZSDZkPYHZstKEbwQboXB0NQPllBsFYPEcPEVKzq2Dl4u9sSOxQ==", "license": "Apache-2.0", "dependencies": { - "@vaadin/hilla-frontend": "24.9.4", - "@vaadin/hilla-react-signals": "24.9.4", + "@vaadin/hilla-frontend": "25.0.4", + "@vaadin/hilla-react-signals": "25.0.4", "intl-messageformat": "10.7.11" }, "peerDependencies": { @@ -12142,13 +11415,13 @@ } }, "node_modules/@vaadin/hilla-react-signals": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-signals/-/hilla-react-signals-24.9.4.tgz", - "integrity": "sha512-ZhEPO+RPjcQgMOfoJoaAa1Wlq2OjohMw7DJSFSxDL0B2DjjNZiMF7oTWUo3MLkYQfYeC0Hz3fSAi2IFKYoTuEA==", + "version": "25.0.4", + "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-signals/-/hilla-react-signals-25.0.4.tgz", + "integrity": "sha512-mWIZgZgOMN269garnXGaNG3xqgLs6imeiE5rHpcau+p/yeUpkaE2LFcdUq1Se8cOszfAXcIchK4aG/OVrpIn8g==", "license": "Apache-2.0", "dependencies": { "@preact/signals-react": "3.0.1", - "@vaadin/hilla-frontend": "24.9.4", + "@vaadin/hilla-frontend": "25.0.4", "nanoid": "5.0.9" }, "peerDependencies": { @@ -12173,444 +11446,79 @@ "react": "^16.14.0 || 17.x || 18.x || 19.x" } }, - "node_modules/@vaadin/horizontal-layout": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/horizontal-layout/-/horizontal-layout-24.9.4.tgz", - "integrity": "sha512-DHQZTcLnOCMMl9kJn2DXOTFhY2N3m6eVS8hx6ehnFE1ySNNxk7M2SgCAZRiAmAiQGUzaCWIlPdeo6OBOdRZiFg==", - "license": "Apache-2.0", - "dependencies": { - "@polymer/polymer": "^3.0.0", - "@vaadin/component-base": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/icon": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/icon/-/icon-24.9.4.tgz", - "integrity": "sha512-0JTSsi/U3z2I/gDZisLqzwJSHYeem3hQcMhNiul9gn1iWVnrfzehpZRAG9L3UhlAKssvh+ttYRjHyeiXR/Rqhg==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/component-base": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/icons": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/icons/-/icons-24.9.4.tgz", - "integrity": "sha512-aps89oGV+SrDwGDlr6/cO2kt+4gaz0oU4nnLBhJE9p7ZNDSjD6O91mLednKlK/RbpdMDLZ6S9gFgtLrTnrE7Lw==", - "license": "Apache-2.0", - "dependencies": { - "@polymer/polymer": "^3.0.0", - "@vaadin/icon": "~24.9.4" - } - }, - "node_modules/@vaadin/input-container": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/input-container/-/input-container-24.9.4.tgz", - "integrity": "sha512-uS+gVgc4NPAR3YcXvJZGbeN48G1tviQZ1n7ap3CAHxai5iCxBYUOwao3FLRkWtDG5KHrEKLFtAw6YCquM+P3NA==", - "license": "Apache-2.0", - "dependencies": { - "@polymer/polymer": "^3.0.0", - "@vaadin/component-base": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/integer-field": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/integer-field/-/integer-field-24.9.4.tgz", - "integrity": "sha512-a1iNJyIp6fGjIguHkZ6aV6e9PiEhZVCtSNItkoqmXCgmtNuhlpp0TwWniq5SoFSYFM/uExyv+JAXkxk7BbDuIw==", - "license": "Apache-2.0", - "dependencies": { - "@polymer/polymer": "^3.0.0", - "@vaadin/component-base": "~24.9.4", - "@vaadin/number-field": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4" - } - }, - "node_modules/@vaadin/item": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/item/-/item-24.9.4.tgz", - "integrity": "sha512-jSOA3SUrGVTYuPD/KqoL5lxVDoxwde5hzC+nDv6BtDvwNmnVMFUB7DKSKSuiPrult2DuUjqNGEJrTXTRhzodFg==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/list-box": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/list-box/-/list-box-24.9.4.tgz", - "integrity": "sha512-idi7Cag09/4snCvVdmrfnB5iPnpvDXzOT6I3SBkFY3mx7l8kceV5/Bk2/1ZqYgWHs52UGwnAeXpT5AGeseGdqQ==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/item": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/lit-renderer": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/lit-renderer/-/lit-renderer-24.9.4.tgz", - "integrity": "sha512-fXoM9XS0nWqUTO0GQVe5m2Oaa1Ae5Rhc1HwzIiX2nTiRuFlX3tfHPeaZJpcG+MuQLn3qtc6rAzHiGl1Y+Tovkw==", - "license": "Apache-2.0", - "dependencies": { - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/login": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/login/-/login-24.9.4.tgz", - "integrity": "sha512-IVNUZl7Gm50Jm2KNgV7EznL2blGroaYoZDj4vrnrd2mAPMgGfKqEEEMvQn1RNeFTguBblWMAABKXJ65q2dFzHg==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/button": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/overlay": "~24.9.4", - "@vaadin/password-field": "~24.9.4", - "@vaadin/text-field": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/markdown": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/markdown/-/markdown-24.9.4.tgz", - "integrity": "sha512-afhIEWc6RbbCoNy5WkP8U0GxwMrKpghHC/k0JEntJ6DrcT1zAhrhasLzHRL7GcXTDbTtbQUKGXlAhjebwPXTvA==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@vaadin/component-base": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "dompurify": "^3.2.5", - "lit": "^3.0.0", - "marked": "^15.0.11" - } - }, - "node_modules/@vaadin/master-detail-layout": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/master-detail-layout/-/master-detail-layout-24.9.4.tgz", - "integrity": "sha512-XKL9ucABTdJDvHgAJRs64Wzot8NgPj/RpnuISoozcvHK6NtQTje+TjJgVehCcJnyefbabed00UCT/YsCOTnA3Q==", - "license": "Apache-2.0", - "dependencies": { - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/menu-bar": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/menu-bar/-/menu-bar-24.9.4.tgz", - "integrity": "sha512-Fkto0kNLkuEfqaVTMm3VtRbUvawOVMsrSbyAe3zRSO72uXQ32Ir2OFHGWy3UD+xytAGz3yj5pqeESNAsoTIRsA==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/button": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/context-menu": "~24.9.4", - "@vaadin/item": "~24.9.4", - "@vaadin/list-box": "~24.9.4", - "@vaadin/overlay": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/message-input": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/message-input/-/message-input-24.9.4.tgz", - "integrity": "sha512-YZWmZgVBxZLasGL9ni0TTqY3UilcjrpKyJvBnziUq3W2NiXUvnQOGskXwpVkD5sQi2f/D5d18IfCaW2FSOR7Rg==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/button": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/text-area": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/message-list": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/message-list/-/message-list-24.9.4.tgz", - "integrity": "sha512-8uY6/Z7rBYzeLTe6ASB8Ub7dmIWiM6hzcwL1U2k/FXGZYyQ1MK3BGlqbH/QX0B0rR+3+F//ORZmh8efg5gTH9A==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/avatar": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/markdown": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/multi-select-combo-box": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/multi-select-combo-box/-/multi-select-combo-box-24.9.4.tgz", - "integrity": "sha512-fH0WKt93ucm6cSTwuIAVH4e/Qm1oLspuO0em3OfgrEoZN7b5gPAAaqI/gyUDs5+PTRyqJ7YyIu1m5VI8fLJkmg==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/combo-box": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/field-base": "~24.9.4", - "@vaadin/input-container": "~24.9.4", - "@vaadin/item": "~24.9.4", - "@vaadin/lit-renderer": "~24.9.4", - "@vaadin/overlay": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/notification": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/notification/-/notification-24.9.4.tgz", - "integrity": "sha512-ZRqPo0PZo/VqExs0DKQ3kGjUEvUgm2DPsHAPI0LzQXv1OS7Roet9T2tL7y9//CK3JGlzfTmhqlH7XxUR/DvbCw==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/component-base": "~24.9.4", - "@vaadin/lit-renderer": "~24.9.4", - "@vaadin/overlay": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/number-field": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/number-field/-/number-field-24.9.4.tgz", - "integrity": "sha512-Z9kmiJTZvoWPWt7TQJOLu8jcI4J0F+dHhjBoeTfMtLkrMTHiKiWj1oKbamtsS1+6DfOMkcrsY2C/cfsPFlA45g==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/field-base": "~24.9.4", - "@vaadin/input-container": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/overlay": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/overlay/-/overlay-24.9.4.tgz", - "integrity": "sha512-0oYDV//7rCxzt2tXPVyJYqUtFn4EiHXrC1lDmbUzWtbGIQZwP3KALiYSk3ylP+bqxv2KTA0m6FjObj8wTj5s8g==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/password-field": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/password-field/-/password-field-24.9.4.tgz", - "integrity": "sha512-LfVsS3lSGqVyNbTJ5dVp8qe5pHYF1//8wg7IXw/AZqnqGbcqkI8gezUHaNnW0yFY1Wx0ZVmee5akZisB4z0H2g==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/button": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/field-base": "~24.9.4", - "@vaadin/text-field": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/polymer-legacy-adapter": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/polymer-legacy-adapter/-/polymer-legacy-adapter-24.9.4.tgz", - "integrity": "sha512-5j38OaD+M44c6qBnrrt84ZiTTDbjHW5/8RsJBkqmvuZs5jQ9KXBqLqCV1WCZKAWr/aGcPS2NPBTpuFLNn0czRw==", - "license": "Apache-2.0", - "dependencies": { - "@polymer/polymer": "^3.0.0", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/popover": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/popover/-/popover-24.9.4.tgz", - "integrity": "sha512-v5iZAukyG4V1TV6OTJ4feo8yuLn3fyu7QYEvhIQFhTp5THrlmtpjlx+FXIU8JWm/uoJ0RjRUeJXUkp+Zg6sdlA==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/lit-renderer": "~24.9.4", - "@vaadin/overlay": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/progress-bar": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/progress-bar/-/progress-bar-24.9.4.tgz", - "integrity": "sha512-RBxOG5D0lsbUl0WreFDrKCVrtZSHEwrjfr7Wx+7QMI2c26TpElSF2yffF7Z+IRqqtF0Q71MbtQyEx23ej0AQ1A==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/component-base": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/radio-group": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/radio-group/-/radio-group-24.9.4.tgz", - "integrity": "sha512-qQ4VlIEsq4p5SNaSg8EglHAmmLnVpz53cMp91qb7fCLPD0zlaoC3wvQKiISpSDnYKlskUf7r6yyYjDi5yQL9vQ==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/field-base": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, "node_modules/@vaadin/react-components": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/react-components/-/react-components-24.9.4.tgz", - "integrity": "sha512-HXayZCXTSf6r8C9P37V/ip8ssev917PWzB8yTKigK0CM/vIRVJnivznNMazy6VDhn9RdrEreoFv6O9xG/IJFHQ==", + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/react-components/-/react-components-25.0.3.tgz", + "integrity": "sha512-7cA3/unDmyUFGvsvmGiLKVvEYQHGyQJoa9aL4pfYgkgK+A+7sw6AOcOEX8W+czOoR/d2XcFAy9qcq7kkWPMoSA==", "license": "Apache-2.0", "dependencies": { "@lit/react": "^1.0.7", - "@vaadin/a11y-base": "24.9.4", - "@vaadin/accordion": "24.9.4", - "@vaadin/app-layout": "24.9.4", - "@vaadin/avatar": "24.9.4", - "@vaadin/avatar-group": "24.9.4", - "@vaadin/button": "24.9.4", - "@vaadin/card": "24.9.4", - "@vaadin/checkbox": "24.9.4", - "@vaadin/checkbox-group": "24.9.4", - "@vaadin/combo-box": "24.9.4", - "@vaadin/component-base": "24.9.4", - "@vaadin/confirm-dialog": "24.9.4", - "@vaadin/context-menu": "24.9.4", - "@vaadin/custom-field": "24.9.4", - "@vaadin/date-picker": "24.9.4", - "@vaadin/date-time-picker": "24.9.4", - "@vaadin/details": "24.9.4", - "@vaadin/dialog": "24.9.4", - "@vaadin/email-field": "24.9.4", - "@vaadin/field-base": "24.9.4", - "@vaadin/field-highlighter": "24.9.4", - "@vaadin/form-layout": "24.9.4", - "@vaadin/grid": "24.9.4", - "@vaadin/horizontal-layout": "24.9.4", - "@vaadin/icon": "24.9.4", - "@vaadin/icons": "24.9.4", - "@vaadin/input-container": "24.9.4", - "@vaadin/integer-field": "24.9.4", - "@vaadin/item": "24.9.4", - "@vaadin/list-box": "24.9.4", - "@vaadin/lit-renderer": "24.9.4", - "@vaadin/login": "24.9.4", - "@vaadin/markdown": "24.9.4", - "@vaadin/master-detail-layout": "24.9.4", - "@vaadin/menu-bar": "24.9.4", - "@vaadin/message-input": "24.9.4", - "@vaadin/message-list": "24.9.4", - "@vaadin/multi-select-combo-box": "24.9.4", - "@vaadin/notification": "24.9.4", - "@vaadin/number-field": "24.9.4", - "@vaadin/overlay": "24.9.4", - "@vaadin/password-field": "24.9.4", - "@vaadin/popover": "24.9.4", - "@vaadin/progress-bar": "24.9.4", - "@vaadin/radio-group": "24.9.4", - "@vaadin/scroller": "24.9.4", - "@vaadin/select": "24.9.4", - "@vaadin/side-nav": "24.9.4", - "@vaadin/split-layout": "24.9.4", - "@vaadin/tabs": "24.9.4", - "@vaadin/tabsheet": "24.9.4", - "@vaadin/text-area": "24.9.4", - "@vaadin/text-field": "24.9.4", - "@vaadin/time-picker": "24.9.4", - "@vaadin/tooltip": "24.9.4", - "@vaadin/upload": "24.9.4", - "@vaadin/vaadin-lumo-styles": "24.9.4", - "@vaadin/vaadin-material-styles": "24.9.4", - "@vaadin/vaadin-themable-mixin": "24.9.4", - "@vaadin/vertical-layout": "24.9.4", - "@vaadin/virtual-list": "24.9.4" + "@vaadin/a11y-base": "25.0.3", + "@vaadin/accordion": "25.0.3", + "@vaadin/app-layout": "25.0.3", + "@vaadin/avatar": "25.0.3", + "@vaadin/avatar-group": "25.0.3", + "@vaadin/button": "25.0.3", + "@vaadin/card": "25.0.3", + "@vaadin/checkbox": "25.0.3", + "@vaadin/checkbox-group": "25.0.3", + "@vaadin/combo-box": "25.0.3", + "@vaadin/component-base": "25.0.3", + "@vaadin/confirm-dialog": "25.0.3", + "@vaadin/context-menu": "25.0.3", + "@vaadin/custom-field": "25.0.3", + "@vaadin/date-picker": "25.0.3", + "@vaadin/date-time-picker": "25.0.3", + "@vaadin/details": "25.0.3", + "@vaadin/dialog": "25.0.3", + "@vaadin/email-field": "25.0.3", + "@vaadin/field-base": "25.0.3", + "@vaadin/field-highlighter": "25.0.3", + "@vaadin/form-layout": "25.0.3", + "@vaadin/grid": "25.0.3", + "@vaadin/horizontal-layout": "25.0.3", + "@vaadin/icon": "25.0.3", + "@vaadin/icons": "25.0.3", + "@vaadin/input-container": "25.0.3", + "@vaadin/integer-field": "25.0.3", + "@vaadin/item": "25.0.3", + "@vaadin/list-box": "25.0.3", + "@vaadin/lit-renderer": "25.0.3", + "@vaadin/login": "25.0.3", + "@vaadin/markdown": "25.0.3", + "@vaadin/master-detail-layout": "25.0.3", + "@vaadin/menu-bar": "25.0.3", + "@vaadin/message-input": "25.0.3", + "@vaadin/message-list": "25.0.3", + "@vaadin/multi-select-combo-box": "25.0.3", + "@vaadin/notification": "25.0.3", + "@vaadin/number-field": "25.0.3", + "@vaadin/overlay": "25.0.3", + "@vaadin/password-field": "25.0.3", + "@vaadin/popover": "25.0.3", + "@vaadin/progress-bar": "25.0.3", + "@vaadin/radio-group": "25.0.3", + "@vaadin/scroller": "25.0.3", + "@vaadin/select": "25.0.3", + "@vaadin/side-nav": "25.0.3", + "@vaadin/split-layout": "25.0.3", + "@vaadin/tabs": "25.0.3", + "@vaadin/tabsheet": "25.0.3", + "@vaadin/text-area": "25.0.3", + "@vaadin/text-field": "25.0.3", + "@vaadin/time-picker": "25.0.3", + "@vaadin/tooltip": "25.0.3", + "@vaadin/upload": "25.0.3", + "@vaadin/vaadin-lumo-styles": "25.0.3", + "@vaadin/vaadin-themable-mixin": "25.0.3", + "@vaadin/vertical-layout": "25.0.3", + "@vaadin/virtual-list": "25.0.3" }, "peerDependencies": { - "@types/react": "^18.2.37 || ^19", - "@types/react-dom": "^18.2.15 || ^19", - "react": "^18.2.0 || ^19", - "react-dom": "^18.2.0 || ^19" + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -12621,201 +11529,830 @@ } } }, - "node_modules/@vaadin/scroller": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/scroller/-/scroller-24.9.4.tgz", - "integrity": "sha512-yOfKlwbItnzD9XR51/WThvjyFUIBVeIKu75FHpmlmUnjjAnwj4kyw0AjnUu9itGX+R5D83ZidondbcWtqhFexQ==", + "node_modules/@vaadin/react-components/node_modules/@vaadin/a11y-base": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/a11y-base/-/a11y-base-25.0.3.tgz", + "integrity": "sha512-p/GQKPHSvaoG02kaQfUyzQkN2+zlskKPXo/5pFRO7zTYhCGcfFkDDBIq4fjr7kFd9RH1FqRKhB6X8Fw2QOLH5w==", "license": "Apache-2.0", "dependencies": { "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", + "@vaadin/component-base": "~25.0.3", "lit": "^3.0.0" } }, - "node_modules/@vaadin/select": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/select/-/select-24.9.4.tgz", - "integrity": "sha512-Ii70lQGcys1alONgIKo2ufithPzFI4s0K5uDFFSH3e12NHFAZu8VxA8tYbWwLh1H5q209ib1MnfUuUevHghVUA==", + "node_modules/@vaadin/react-components/node_modules/@vaadin/accordion": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/accordion/-/accordion-25.0.3.tgz", + "integrity": "sha512-8DsWWkLVC3igYnfepb1yd9Z/xZYp5Lvr0s75bMQyK0zqqjIhwLVpTVfHzaaMcmidFRwfOmFIyumZaBAOBS6HVQ==", "license": "Apache-2.0", "dependencies": { "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.2.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/button": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/field-base": "~24.9.4", - "@vaadin/input-container": "~24.9.4", - "@vaadin/item": "~24.9.4", - "@vaadin/list-box": "~24.9.4", - "@vaadin/lit-renderer": "~24.9.4", - "@vaadin/overlay": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/details": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", "lit": "^3.0.0" } }, - "node_modules/@vaadin/side-nav": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/side-nav/-/side-nav-24.9.4.tgz", - "integrity": "sha512-pLAcoZzzMA+vQHuqo6jy8N4G0bz6qFtIM+BpJ9kcHYiulUg/Y7NPa4K2YsHF5gChZwE9cbxGMc0lPoN6p8QeKg==", + "node_modules/@vaadin/react-components/node_modules/@vaadin/app-layout": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/app-layout/-/app-layout-25.0.3.tgz", + "integrity": "sha512-3EgIsveq/gKpxr6hChJL6ZOY+wHB/UbcQkF6PjHg/LBFIPG3CIeOgRyHPJgnB0FCra6E8zOjo9j4sy/HyoXdCg==", "license": "Apache-2.0", "dependencies": { "@open-wc/dedupe-mixin": "^1.3.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/button": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", "lit": "^3.0.0" } }, - "node_modules/@vaadin/split-layout": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/split-layout/-/split-layout-24.9.4.tgz", - "integrity": "sha512-oTL0IT7Cm6flCOtMg3odZo+qEczSmL2OS61Mf67BIAYvOG08OMNZElKJ57tVe50NL5DIGMmMtcfyC5Mni/mWuw==", + "node_modules/@vaadin/react-components/node_modules/@vaadin/avatar": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/avatar/-/avatar-25.0.3.tgz", + "integrity": "sha512-LGKXpeGKnO1cQcYOXwak66mXPw9271MLQk0DN9SoRbRASKLAfsEvSan83dIYxozz+HFmgKkL0DTYvilLWvsd6g==", "license": "Apache-2.0", "dependencies": { "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/component-base": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/tooltip": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", "lit": "^3.0.0" } }, - "node_modules/@vaadin/tabs": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/tabs/-/tabs-24.9.4.tgz", - "integrity": "sha512-YfFZ+SV+2HtHIvdhHf0jTFc0spUjt3gQK8ckdw12DrOR2OPnShx1BFEDkor0NQqVLmLgvksbPxYJPhVf8wvj1w==", + "node_modules/@vaadin/react-components/node_modules/@vaadin/avatar-group": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/avatar-group/-/avatar-group-25.0.3.tgz", + "integrity": "sha512-B9fFJSckCvn+SOfX7dYXw+NcXPAjNchMrrqU3h987OcyYPVsySt0kSdxS61SAn6KqHQk/NVzLAdKD+RiWvoDNQ==", "license": "Apache-2.0", "dependencies": { "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/item": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/avatar": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/item": "~25.0.3", + "@vaadin/list-box": "~25.0.3", + "@vaadin/overlay": "~25.0.3", + "@vaadin/tooltip": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", "lit": "^3.0.0" } }, - "node_modules/@vaadin/tabsheet": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/tabsheet/-/tabsheet-24.9.4.tgz", - "integrity": "sha512-Yy37yU2p2zazxqVx2rj2ljjrrZVq/jqu01HhgwCrnA7kpi21p9uVV8EvuAg17qU9uKFc8LQseNRKMtis9K7TcA==", + "node_modules/@vaadin/react-components/node_modules/@vaadin/button": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/button/-/button-25.0.3.tgz", + "integrity": "sha512-hupuX+6IfvBCGq10OLlNyfeoKLoJ+/y7yZHdCKmFI64dSHdR66uN++WREyys/x9q2Es1fjbmbZWCF4y5PYXnfA==", "license": "Apache-2.0", "dependencies": { "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/component-base": "~24.9.4", - "@vaadin/scroller": "~24.9.4", - "@vaadin/tabs": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", "lit": "^3.0.0" } }, - "node_modules/@vaadin/text-area": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/text-area/-/text-area-24.9.4.tgz", - "integrity": "sha512-pihekoVk4wIeoqykvNyb8S81PS8NFHLKRZqcnsDgtMyIEg7UlRPSIgUZEbFAGzrU9WnY+NJehzhVnPFKBuePxw==", + "node_modules/@vaadin/react-components/node_modules/@vaadin/card": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/card/-/card-25.0.3.tgz", + "integrity": "sha512-shaCD9TaMZxtL3GMaE8wrpgJ1RGGthe/DpvyL1iyUmE+6YweGrEml1CBo1WalqfJalYHjnYwMav4TOiGicPDow==", "license": "Apache-2.0", "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/field-base": "~24.9.4", - "@vaadin/input-container": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", + "@vaadin/component-base": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", "lit": "^3.0.0" } }, - "node_modules/@vaadin/text-field": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/text-field/-/text-field-24.9.4.tgz", - "integrity": "sha512-1Yr8Hn5o4r3mBA0jXgRiMiAlfIOe2H/3tkWHj8KBdxH2PfwDGjzXvf5995g6kO4/b2E2sWNtMTQYufEr1HLlWw==", + "node_modules/@vaadin/react-components/node_modules/@vaadin/checkbox": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/checkbox/-/checkbox-25.0.3.tgz", + "integrity": "sha512-U4kgpckPAbufK1eLoVl3glh8m7NqaMLSlukVKfq6Mn3kvTrIf7i3LWbcqMZFuUrK+KphD91ZQMjF3z/qWMzQFw==", "license": "Apache-2.0", "dependencies": { "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/field-base": "~24.9.4", - "@vaadin/input-container": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/field-base": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", "lit": "^3.0.0" } }, - "node_modules/@vaadin/time-picker": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/time-picker/-/time-picker-24.9.4.tgz", - "integrity": "sha512-4agGlkzbEfCXPtwiTNrG22r8ayA6R1U/jq0HeaXGFb6toLDZZyPsxKlZfA0FEvOF3EmyGHIKuEG6sR5X+GyCTg==", + "node_modules/@vaadin/react-components/node_modules/@vaadin/checkbox-group": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/checkbox-group/-/checkbox-group-25.0.3.tgz", + "integrity": "sha512-v58pFRcwiM3j0YO44Wc8jlD3LSyTDKoeM3km0LTT1wPn9OnPPigubSbgvy0IRsnKEA/pigyaQ5BPro71wtbKTA==", "license": "Apache-2.0", "dependencies": { "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/combo-box": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/field-base": "~24.9.4", - "@vaadin/input-container": "~24.9.4", - "@vaadin/item": "~24.9.4", - "@vaadin/overlay": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/checkbox": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/field-base": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", "lit": "^3.0.0" } }, - "node_modules/@vaadin/tooltip": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/tooltip/-/tooltip-24.9.4.tgz", - "integrity": "sha512-p+zGTb5Q0HsCjbF8rbNU5SoadH9vC1Yz1qrvsgji61cDDcQ8VRQYbYjq85YHVQ9l0GHIhIJTIkC8MvqjPeGCtg==", + "node_modules/@vaadin/react-components/node_modules/@vaadin/combo-box": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/combo-box/-/combo-box-25.0.3.tgz", + "integrity": "sha512-akcRfefGhc7jG46ySp677oJOaE3BeiaSCMNoaRiOAdu6uja+hytWvAcu3f63GB71RU87+W2nkQtPf5Sk7MAa8g==", "license": "Apache-2.0", "dependencies": { "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/overlay": "~24.9.4", - "@vaadin/popover": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/field-base": "~25.0.3", + "@vaadin/input-container": "~25.0.3", + "@vaadin/item": "~25.0.3", + "@vaadin/lit-renderer": "~25.0.3", + "@vaadin/overlay": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", "lit": "^3.0.0" } }, - "node_modules/@vaadin/upload": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/upload/-/upload-24.9.4.tgz", - "integrity": "sha512-71yoqOL3p5mbuBZvMh9qzZLpEbbjXRUd+Q7kQsNLHaZHC+ca2usFvXRk9XK8GmFE9in9/+0fsMw9a33D1OF3Vw==", + "node_modules/@vaadin/react-components/node_modules/@vaadin/component-base": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/component-base/-/component-base-25.0.3.tgz", + "integrity": "sha512-/q4bPX1dVtHqxNqFIMHDepLrrMkYO/0K1LNyXePVYZFE2gqyfC4WCLvTGNnKEeiCH9nfQSyZn81Zh03SvKrmJA==", "license": "Apache-2.0", "dependencies": { "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.9.4", - "@vaadin/button": "~24.9.4", - "@vaadin/component-base": "~24.9.4", - "@vaadin/progress-bar": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", + "@vaadin/vaadin-development-mode-detector": "^2.0.0", + "@vaadin/vaadin-usage-statistics": "^2.1.0", "lit": "^3.0.0" } }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/confirm-dialog": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/confirm-dialog/-/confirm-dialog-25.0.3.tgz", + "integrity": "sha512-9r2OKhW2mt9LcJuFBlR48J2aD5OlRsBKt+CdKdaz3AQ1iwRhR9CxZw/LQubHEQuBPZ7WUz/cYoZ2fwhaYwj/6Q==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/button": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/dialog": "~25.0.3", + "@vaadin/overlay": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/context-menu": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/context-menu/-/context-menu-25.0.3.tgz", + "integrity": "sha512-wjRg2PqAcSvoyqL9IE57KGT2QL8xjZ0P11RPDymB+hUChlTbxiGBnG+7P60a7RNaQGsVzGOamdlV+++ElR0tSw==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/item": "~25.0.3", + "@vaadin/list-box": "~25.0.3", + "@vaadin/lit-renderer": "~25.0.3", + "@vaadin/overlay": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/custom-field": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/custom-field/-/custom-field-25.0.3.tgz", + "integrity": "sha512-AEUPMmPiF1h/urTj9PKHqJMlzs/ZnoymxS7bm3bWti/ii1fSohfcgsziRh/od7eZe2HGoDyRx+149q44YYVE0g==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/field-base": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/date-picker": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/date-picker/-/date-picker-25.0.3.tgz", + "integrity": "sha512-HkUdjQh3gf0P+fX675Zketm/updDjvkUEYZqlOLpJreUP3BwltCLX4eX1ZGXzF26ZM42FpnlDyvwP9uOWmGj5g==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/button": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/field-base": "~25.0.3", + "@vaadin/input-container": "~25.0.3", + "@vaadin/overlay": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/date-time-picker": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/date-time-picker/-/date-time-picker-25.0.3.tgz", + "integrity": "sha512-TAS5jB/2xcPdmA1FZhk7O/nb95O1QF3H0YwMERhxRO/uRicqLsURlnfOCXUjg5oee2kpggMJuaFaJJjPWeEIdw==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/custom-field": "~25.0.3", + "@vaadin/date-picker": "~25.0.3", + "@vaadin/field-base": "~25.0.3", + "@vaadin/time-picker": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/details": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/details/-/details-25.0.3.tgz", + "integrity": "sha512-3gG13dlXPsf3vGZam7HL5zTsYWcY2FU3QBAx20NNjj66uhP20BoFUXuBS3nnkmKISRofXxWR+tpupBDRJ8fzfw==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/button": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/dialog": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/dialog/-/dialog-25.0.3.tgz", + "integrity": "sha512-yxL7dhLMOv/V3d6bFl4X+h56bP75ntQJc7+eD9tSBr9qGa2WntLDzeliL8GUk7LYwH9AIcyCbSJjChsVDGoxtg==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/component-base": "~25.0.3", + "@vaadin/lit-renderer": "~25.0.3", + "@vaadin/overlay": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/email-field": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/email-field/-/email-field-25.0.3.tgz", + "integrity": "sha512-6l3QW7Trg4vRIZ+zK/II9C/NFLdtPyblnTBavkrP2eLJN2qz8X2jKNc/Mc2nwIY8rffRKknzBfG6QtuhIKfLNg==", + "license": "Apache-2.0", + "dependencies": { + "@vaadin/component-base": "~25.0.3", + "@vaadin/text-field": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/field-base": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/field-base/-/field-base-25.0.3.tgz", + "integrity": "sha512-jzcsSAQNOHTKuvwru80p8EJWlPdnIb/vdgKGUfQUw5eCmyRUTyzYjpOABdJLSrXeNgXKLzb/gp1Svkx/zOUyTw==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/field-highlighter": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/field-highlighter/-/field-highlighter-25.0.3.tgz", + "integrity": "sha512-Leqx6dHh/o4Jye3LLOn1KP96dwBN1MIWADszX9HX2DNifmZcteqogRtZUOW68tMlYpIpfWWvZjNdXFFrrYrLrw==", + "license": "Apache-2.0", + "dependencies": { + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/overlay": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/form-layout": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/form-layout/-/form-layout-25.0.3.tgz", + "integrity": "sha512-CA82pf96UHLJ8Pdr4iAjYot4DDoJXdnmRfJoEllT4dCeEljTddNLLVIDHVwW9RfCkNoV8QWmdFWE+YI4MQlDjQ==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/grid": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/grid/-/grid-25.0.3.tgz", + "integrity": "sha512-sI+ZHAz4vTuzedCDRt96H7HCXrkeyz7uEr8K+YcxPxK9WAAm0rPeInX+EXVx0sbyzi2/rJaxfrEcsHyy3lHxeA==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/checkbox": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/lit-renderer": "~25.0.3", + "@vaadin/text-field": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/horizontal-layout": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/horizontal-layout/-/horizontal-layout-25.0.3.tgz", + "integrity": "sha512-i3j2P0hL34LOf4kWK40f7gFTTG5t5iC45+595xIlaZ6uRET3peRJbSZBAvbgmGioIPN5vtouUUyxo0UrhUXkFw==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/component-base": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/icon": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/icon/-/icon-25.0.3.tgz", + "integrity": "sha512-BaLoZYUPAMCh9rSA46tb6FCLlRAkWgWj4wRF0tiaAXyQXZXgAU6oTumxRgZLFuJu091lJ17Sg8Fpnes0fuDSdg==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/component-base": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/icons": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/icons/-/icons-25.0.3.tgz", + "integrity": "sha512-Nlla0Tuhe5kD1JbqgTbu2HoAcxGmCNV47jUS/uEK+xleXC3IkNC50oHR9o7uWB8FvzOhutkIkwTIIJ0rhWFd5A==", + "license": "Apache-2.0", + "dependencies": { + "@vaadin/icon": "~25.0.3" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/input-container": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/input-container/-/input-container-25.0.3.tgz", + "integrity": "sha512-9JLqoZObZQDXFi3ZCdSnwYi/LAN5RwiSE707NtoILW9I9scVW3/s1VLcXHuD/+F96ILqz4P7Q1ebxGzFBj1QRA==", + "license": "Apache-2.0", + "dependencies": { + "@vaadin/component-base": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/integer-field": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/integer-field/-/integer-field-25.0.3.tgz", + "integrity": "sha512-0yhd3hqudXbQS6wmyuja76ijrhU+iJnx5ncY14CkTvgjVzkSQY0Sa6zHMHScrjW/gj5I/pMRyKFS/iER4Mj/pQ==", + "license": "Apache-2.0", + "dependencies": { + "@vaadin/component-base": "~25.0.3", + "@vaadin/number-field": "~25.0.3" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/item": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/item/-/item-25.0.3.tgz", + "integrity": "sha512-Wqa4FLmCZQ+keo6MOpEH2HP8S8r+4oUGNRFzpmBE3JHKjajEupEBigMST9uzKrqx0L7Wrs9ffKl7WT9ka1WQkA==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/list-box": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/list-box/-/list-box-25.0.3.tgz", + "integrity": "sha512-JSix38Yd6Wips4RmVOI4Ga9G6Hy9Eg+0XJ37F2QuqLQlBS0ToS4P2/PXrPsAq9/BYgDvadsSiUKB2vXEjGtLcg==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/item": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/lit-renderer": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/lit-renderer/-/lit-renderer-25.0.3.tgz", + "integrity": "sha512-hWhQ/+b9AspNvZHTPsJYgCNH1/1fXwtDRvDEuyuu3pkSgS9S5bylq5yOTEsSW7bq2kJJ2tJnzBgsn97avJ7tNA==", + "license": "Apache-2.0", + "dependencies": { + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/login": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/login/-/login-25.0.3.tgz", + "integrity": "sha512-Pg+SWsg+7Bghz18VFmvpK+jeZIE4wUfP+dKnZ+1o/etZVH9J99qKF422d7mMeTmUTTof+TF0l8HKlW77k3Qqxw==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/button": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/overlay": "~25.0.3", + "@vaadin/password-field": "~25.0.3", + "@vaadin/text-field": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/markdown": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/markdown/-/markdown-25.0.3.tgz", + "integrity": "sha512-YXIlVDGoboY4d/41vflQTJlrnRFlmEbTpvC9MgGuwoQdNTqO5Vw8v7k2X88SfdYcZ9mQaNJg8LUW3R+OfbFdYw==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/component-base": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "dompurify": "^3.2.5", + "lit": "^3.0.0", + "marked": "^16.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/master-detail-layout": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/master-detail-layout/-/master-detail-layout-25.0.3.tgz", + "integrity": "sha512-49zQkWjvptpQgPVa7BqC6i8yReVjw6U9jqEajwWz5xWaxwyZKKFFRDf6jbqBs55NOKLZzmLTypp99R0Qm+KVEg==", + "license": "Apache-2.0", + "dependencies": { + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/menu-bar": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/menu-bar/-/menu-bar-25.0.3.tgz", + "integrity": "sha512-He4p4IGk9XF8TgOpAz8jsszfz0b/PmkUDh2WQnEALgDGlzn+CmclZ8QRs3KLMmDnFOeAZ+85TEgPFCjnfXBbeA==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/button": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/context-menu": "~25.0.3", + "@vaadin/item": "~25.0.3", + "@vaadin/list-box": "~25.0.3", + "@vaadin/overlay": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/message-input": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/message-input/-/message-input-25.0.3.tgz", + "integrity": "sha512-C2CG2RbEVPaX9TNthDGyVGZLVX0/fVBLwz4Hodjzt55Q9UMbZvEvxE3o7dZU0iwbsvWxGLyqcDiGaVeps4cWzw==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/button": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/text-area": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/message-list": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/message-list/-/message-list-25.0.3.tgz", + "integrity": "sha512-07gRb2HSZMNiwUnW8lyPCYPxmIKi18NGk8S0lstdH2wAzrZ4TXaRfbGTo9pKPKQVS1/G3rn5LCztFHuPwSScBQ==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/avatar": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/markdown": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/multi-select-combo-box": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/multi-select-combo-box/-/multi-select-combo-box-25.0.3.tgz", + "integrity": "sha512-2+Jw4+2k24oBT2CcGCvfHI2ZAIV0agfDluwK4tsOfIw3cTXP1yF/M+s7xbW6yP84aPkwIsEPp9R4zqgy6j2JYQ==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/combo-box": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/field-base": "~25.0.3", + "@vaadin/input-container": "~25.0.3", + "@vaadin/item": "~25.0.3", + "@vaadin/lit-renderer": "~25.0.3", + "@vaadin/overlay": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/notification": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/notification/-/notification-25.0.3.tgz", + "integrity": "sha512-hcRhd2gMARWu6NGIB2hSmBpnil83sobNsALTsnjPN+nUS7KCqcSTw7rHLJh1DaUAA/5ZdG7lB8ebKa4sXVEyCg==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/component-base": "~25.0.3", + "@vaadin/lit-renderer": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/number-field": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/number-field/-/number-field-25.0.3.tgz", + "integrity": "sha512-hJgajOStyKORW/VSXJnrQjp8x9nmbjjFnDlEJuf5enKHix1nIG2Ory7BN7cdRiFLH51VZxPcQ3NMUi3C43YvRg==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/field-base": "~25.0.3", + "@vaadin/input-container": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/overlay": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/overlay/-/overlay-25.0.3.tgz", + "integrity": "sha512-O0+MyQ5YHnz1QVzjnwzm3WCOMEEcXVaFJm1VnmIW+7o113fRtFglVOPrKpUPR8yOCiJ6wvLfLOzXf1SFjxNgmw==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/password-field": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/password-field/-/password-field-25.0.3.tgz", + "integrity": "sha512-JPsyfmbp4EpkpN6VhrwaZMJVT2Z8GkPcnRe7tY6STXfcqTm7Ioc8BbtMsaIzo9zv8JCz8RB5zgUTN5iLp8hWYg==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/button": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/field-base": "~25.0.3", + "@vaadin/text-field": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/popover": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/popover/-/popover-25.0.3.tgz", + "integrity": "sha512-SWrwb2A0481IPK/qffYUVBiLPILJgHM63tho9fAZuOt0SulCssyLBxXbnuUP7Ds0RCc25vLR62F8C/nu3BTrpg==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/lit-renderer": "~25.0.3", + "@vaadin/overlay": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/progress-bar": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/progress-bar/-/progress-bar-25.0.3.tgz", + "integrity": "sha512-3P2MhwWaiLAsDC6ZHBGnF7lovsAQv/FV2wyUvXEirqW/bKsLEsLW6zo3WEsxoG2AMd+tJ2+CUkUmqI5zNRmuTw==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/component-base": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/radio-group": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/radio-group/-/radio-group-25.0.3.tgz", + "integrity": "sha512-JB/Gr8IPPVDoOQNOj7yXmKbubRBW7NYDRipC5nvAIJr1bcZf2caRBHFWvmyH5Laib9oYtZ6nTsoIl2G3Y195Pw==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/field-base": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/scroller": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/scroller/-/scroller-25.0.3.tgz", + "integrity": "sha512-fWXMZKgV+K8bwqespzeKXZUZ7oUPSWjEn7riq1nAYE/vEkO+fgWE2XsF7rQwJH/ZT6wP2bh7d6Q+AQx1WFL+gg==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/select": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/select/-/select-25.0.3.tgz", + "integrity": "sha512-CJz+kYJXb2q0oxPwDN/G9PdLqUk2fdFUc8A144202u4jUkPwGTsKonZoYZrB1i33peLFpWSNGsq60e0gLPxapw==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/button": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/field-base": "~25.0.3", + "@vaadin/input-container": "~25.0.3", + "@vaadin/item": "~25.0.3", + "@vaadin/list-box": "~25.0.3", + "@vaadin/lit-renderer": "~25.0.3", + "@vaadin/overlay": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/side-nav": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/side-nav/-/side-nav-25.0.3.tgz", + "integrity": "sha512-1Dx1x039tn3BxDpNYrt8nfnbq5Y99I7Ytybupa0XcsIAOl5ofnLsE0jCxdcv2tT2LS4RDBITPch4f5eLgQrfLw==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/split-layout": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/split-layout/-/split-layout-25.0.3.tgz", + "integrity": "sha512-m/Ax9ydn3Aogk1h2f9FXrB+/vV7VCM/kIDDV5fa70koXJLMiA62MGIkwxwIuGYcXtTcPDlJ5x+YyCjlN6Sx4VQ==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/component-base": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/tabs": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/tabs/-/tabs-25.0.3.tgz", + "integrity": "sha512-ZazOrjPWkUtsYD64vzHRmbK2kxA1OYZUiLG7pDE4TDoObhtnaXz6fk89yfcKiXjDAcG5yL7aDvbKnGrNMCoOfg==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/item": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/tabsheet": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/tabsheet/-/tabsheet-25.0.3.tgz", + "integrity": "sha512-SCRmQpa2ZoXIJXPcPSQpo/J//QUqIgMxwkKiJFCG308UVPgu/sq8AIY1LZiI4p+JFGj4br3cm8pdc6huTD3aAw==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/component-base": "~25.0.3", + "@vaadin/scroller": "~25.0.3", + "@vaadin/tabs": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/text-area": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/text-area/-/text-area-25.0.3.tgz", + "integrity": "sha512-zecn1Gx6mQ3xbhmn8NIC2U/gh+4FdVM04r+YGdaC+KE2HZLziIpnIDUdkFaiVoB4KqPMl+ApTv3wOFNdWfuukw==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/field-base": "~25.0.3", + "@vaadin/input-container": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/text-field": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/text-field/-/text-field-25.0.3.tgz", + "integrity": "sha512-51AhGhXyN/RiGDgGQf509WQ89h1Lw2RpKiwQmIRwfoypGKga9gS3W69Wghyd9mBNlc2RvVGpK47qDyofM2ppjg==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/field-base": "~25.0.3", + "@vaadin/input-container": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/time-picker": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/time-picker/-/time-picker-25.0.3.tgz", + "integrity": "sha512-BjHWIq4ZYT/WChxT/Bwd8QAiBY0+EjLo9hdrOg6o5l9hiKzTV7UDUpXpWHF3K7y+vWDSttDU7mxDcwnRlCSd9w==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/combo-box": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/field-base": "~25.0.3", + "@vaadin/input-container": "~25.0.3", + "@vaadin/item": "~25.0.3", + "@vaadin/overlay": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/tooltip": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/tooltip/-/tooltip-25.0.3.tgz", + "integrity": "sha512-ZsiojadHzj0uMFXFaSm84yLJginguC1A2KQh9OSN/EelWzL+6rBbQS4pG/VXzASu4adU5ANSZpLvg5+AJcm9/A==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/markdown": "~25.0.3", + "@vaadin/overlay": "~25.0.3", + "@vaadin/popover": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/upload": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/upload/-/upload-25.0.3.tgz", + "integrity": "sha512-mBkHSJxGhrfa0+svpPTsh7rEuJ4CpHNV43MhagKSGykTtqcPk6Z09ttKsH2Hb0IJMbwVuFchSu7W3R7URy3nLQ==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/a11y-base": "~25.0.3", + "@vaadin/button": "~25.0.3", + "@vaadin/component-base": "~25.0.3", + "@vaadin/progress-bar": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/vertical-layout": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/vertical-layout/-/vertical-layout-25.0.3.tgz", + "integrity": "sha512-aMjyjZX5gvj9Z/DTWIHaWmKkcbdv6bsbyBB9A4U974OmWkcr427T0otko8xjASj90pmgJvUOGhrUkiJ+UhocYQ==", + "license": "Apache-2.0", + "dependencies": { + "@vaadin/component-base": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/@vaadin/virtual-list": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/virtual-list/-/virtual-list-25.0.3.tgz", + "integrity": "sha512-1ZyFQelh1ZLdF1LtF3D/+HkSf7bD81KV1udyr2Q0VPIPExLs+MSlao43W678qvg46udhi5bvARS4TvqA+8CIFg==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/component-base": "~25.0.3", + "@vaadin/lit-renderer": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/react-components/node_modules/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/@vaadin/vaadin-development-mode-detector": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@vaadin/vaadin-development-mode-detector/-/vaadin-development-mode-detector-2.0.7.tgz", @@ -12823,37 +12360,61 @@ "license": "Apache-2.0" }, "node_modules/@vaadin/vaadin-lumo-styles": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/vaadin-lumo-styles/-/vaadin-lumo-styles-24.9.4.tgz", - "integrity": "sha512-g6qx0Fp9VOvjKUnYyxgE42Jl5c+LdiA9T6KxPCzOF8EiaHKsCLLKecnvwmV6SelQ45AbVLg4YiROFJ27k38d/w==", + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/vaadin-lumo-styles/-/vaadin-lumo-styles-25.0.3.tgz", + "integrity": "sha512-XOJHw1amOmDp0ZLy1klbgoCNz0oHwebr/bEbA51wTgNTZwa0oH3MbUUkdnBOvqxO0G/rrmg0sVrf2WmM8BEBKQ==", "license": "Apache-2.0", "dependencies": { - "@polymer/polymer": "^3.0.0", - "@vaadin/component-base": "~24.9.4", - "@vaadin/icon": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4" + "@vaadin/component-base": "~25.0.3", + "@vaadin/icon": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3" } }, - "node_modules/@vaadin/vaadin-material-styles": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/vaadin-material-styles/-/vaadin-material-styles-24.9.4.tgz", - "integrity": "sha512-edNJBBTJJ/e4jWCfQ/HvoCAkWfe5zKtNVUUmsuCzRd0CwItKnb8cToLA2rQNODMuOYZrz4TDEI+slsrcLXs/+A==", - "license": "Apache-2.0", - "dependencies": { - "@polymer/polymer": "^3.0.0", - "@vaadin/component-base": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4" - } - }, - "node_modules/@vaadin/vaadin-themable-mixin": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/vaadin-themable-mixin/-/vaadin-themable-mixin-24.9.4.tgz", - "integrity": "sha512-XfxSq/pgVDAbGXKLJuDqR5uFT0zRac6UfY8Q+aNzv2R53PAm3vrt/S6XQ8VHDMCV/+8mVT2dZSnl22pafSEDcw==", + "node_modules/@vaadin/vaadin-lumo-styles/node_modules/@vaadin/component-base": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/component-base/-/component-base-25.0.3.tgz", + "integrity": "sha512-/q4bPX1dVtHqxNqFIMHDepLrrMkYO/0K1LNyXePVYZFE2gqyfC4WCLvTGNnKEeiCH9nfQSyZn81Zh03SvKrmJA==", "license": "Apache-2.0", "dependencies": { "@open-wc/dedupe-mixin": "^1.3.0", - "lit": "^3.0.0", - "style-observer": "^0.0.8" + "@vaadin/vaadin-development-mode-detector": "^2.0.0", + "@vaadin/vaadin-usage-statistics": "^2.1.0", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/vaadin-lumo-styles/node_modules/@vaadin/icon": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/icon/-/icon-25.0.3.tgz", + "integrity": "sha512-BaLoZYUPAMCh9rSA46tb6FCLlRAkWgWj4wRF0tiaAXyQXZXgAU6oTumxRgZLFuJu091lJ17Sg8Fpnes0fuDSdg==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/component-base": "~25.0.3", + "@vaadin/vaadin-themable-mixin": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/vaadin-themable-mixin": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/vaadin-themable-mixin/-/vaadin-themable-mixin-25.0.3.tgz", + "integrity": "sha512-oLut3CB4MuMopjHtRvcSvtTYHpA5qHeK7HNz5FMeWSUqnxtI1/AuyfqPFrpkHi682DXPrlrNcqplCy5Vp54StA==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/component-base": "~25.0.3", + "lit": "^3.0.0" + } + }, + "node_modules/@vaadin/vaadin-themable-mixin/node_modules/@vaadin/component-base": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@vaadin/component-base/-/component-base-25.0.3.tgz", + "integrity": "sha512-/q4bPX1dVtHqxNqFIMHDepLrrMkYO/0K1LNyXePVYZFE2gqyfC4WCLvTGNnKEeiCH9nfQSyZn81Zh03SvKrmJA==", + "license": "Apache-2.0", + "dependencies": { + "@open-wc/dedupe-mixin": "^1.3.0", + "@vaadin/vaadin-development-mode-detector": "^2.0.0", + "@vaadin/vaadin-usage-statistics": "^2.1.0", + "lit": "^3.0.0" } }, "node_modules/@vaadin/vaadin-usage-statistics": { @@ -12869,52 +12430,22 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/@vaadin/vertical-layout": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/vertical-layout/-/vertical-layout-24.9.4.tgz", - "integrity": "sha512-kiVhwIGUXSA+fi6klIzLXauVF4TfYu07ME2BkwnC0I4ymQqN4SZiAemUP+0/Gp5XRJ23H4q5cSTYws5u8C4cwA==", - "license": "Apache-2.0", - "dependencies": { - "@polymer/polymer": "^3.0.0", - "@vaadin/component-base": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/virtual-list": { - "version": "24.9.4", - "resolved": "https://registry.npmjs.org/@vaadin/virtual-list/-/virtual-list-24.9.4.tgz", - "integrity": "sha512-Jc7puX8ln83CrVUQ2t3pK9B1crq+MJATEkKTwYZNZ1u4VfpwDWE3srN8ZTidIjUSOWWNXPHbBlnlAJSaOmyk/Q==", - "license": "Apache-2.0", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/component-base": "~24.9.4", - "@vaadin/lit-renderer": "~24.9.4", - "@vaadin/vaadin-lumo-styles": "~24.9.4", - "@vaadin/vaadin-material-styles": "~24.9.4", - "@vaadin/vaadin-themable-mixin": "~24.9.4", - "lit": "^3.0.0" - } - }, "node_modules/@vitejs/plugin-react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", - "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz", + "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.28.0", + "@babel/core": "^7.28.5", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.27", + "@rolldown/pluginutils": "1.0.0-beta.53", "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" + "react-refresh": "^0.18.0" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" @@ -12934,11 +12465,12 @@ "vite": "^4 || ^5 || ^6 || ^7" } }, - "node_modules/@webcomponents/shadycss": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/@webcomponents/shadycss/-/shadycss-1.11.2.tgz", - "integrity": "sha512-vRq+GniJAYSBmTRnhCYPAPq6THYqovJ/gzGThWbgEZUQaBccndGTi1hdiUP15HzEco0I6t4RCtXyX0rsSmwgPw==", - "license": "BSD-3-Clause" + "node_modules/@vitejs/plugin-react/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", + "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", + "dev": true, + "license": "MIT" }, "node_modules/abort-controller": { "version": "3.0.0", @@ -12993,6 +12525,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -13237,9 +12770,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.13", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.13.tgz", - "integrity": "sha512-7s16KR8io8nIBWQyCYhmFhd+ebIzb9VKTzki+wOJXHTxTnV6+mFGH3+Jwn1zoKaY9/H9T/0BcKCZnzXljPnpSQ==", + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -13289,6 +12822,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -13541,7 +13075,8 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/client-only": { "version": "0.0.1", @@ -13738,19 +13273,6 @@ "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", "license": "MIT" }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/construct-style-sheets-polyfill": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/construct-style-sheets-polyfill/-/construct-style-sheets-polyfill-3.1.0.tgz", - "integrity": "sha512-HBLKP0chz8BAY6rBdzda11c3wAZeCZ+kIG4weVC2NM3AXzxx09nhe8t0SQNdloAvg5GLuHwq/0SPOOSPvtCcKw==", - "license": "MIT" - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -13759,12 +13281,16 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", - "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", "license": "MIT", "engines": { "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/core-js-compat": { @@ -13807,9 +13333,9 @@ } }, "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, "node_modules/d3": { @@ -14138,6 +13664,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -14284,16 +13811,13 @@ } }, "node_modules/date-fns": { - "version": "2.29.3", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", - "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", "license": "MIT", - "engines": { - "node": ">=0.11" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" } }, "node_modules/dateformat": { @@ -14656,9 +14180,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", - "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -14668,32 +14192,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.10", - "@esbuild/android-arm": "0.25.10", - "@esbuild/android-arm64": "0.25.10", - "@esbuild/android-x64": "0.25.10", - "@esbuild/darwin-arm64": "0.25.10", - "@esbuild/darwin-x64": "0.25.10", - "@esbuild/freebsd-arm64": "0.25.10", - "@esbuild/freebsd-x64": "0.25.10", - "@esbuild/linux-arm": "0.25.10", - "@esbuild/linux-arm64": "0.25.10", - "@esbuild/linux-ia32": "0.25.10", - "@esbuild/linux-loong64": "0.25.10", - "@esbuild/linux-mips64el": "0.25.10", - "@esbuild/linux-ppc64": "0.25.10", - "@esbuild/linux-riscv64": "0.25.10", - "@esbuild/linux-s390x": "0.25.10", - "@esbuild/linux-x64": "0.25.10", - "@esbuild/netbsd-arm64": "0.25.10", - "@esbuild/netbsd-x64": "0.25.10", - "@esbuild/openbsd-arm64": "0.25.10", - "@esbuild/openbsd-x64": "0.25.10", - "@esbuild/openharmony-arm64": "0.25.10", - "@esbuild/sunos-x64": "0.25.10", - "@esbuild/win32-arm64": "0.25.10", - "@esbuild/win32-ia32": "0.25.10", - "@esbuild/win32-x64": "0.25.10" + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" } }, "node_modules/escalade": { @@ -14951,6 +14475,7 @@ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.22.tgz", "integrity": "sha512-ZgGvdxXCw55ZYvhoZChTlG6pUuehecgvEAJz0BHoC5pQKW1EC5xf1Mul1ej5+ai+pVY0pylyFfdl45qnM1/GsA==", "license": "MIT", + "peer": true, "dependencies": { "motion-dom": "^12.23.21", "motion-utils": "^12.23.6", @@ -14989,13 +14514,6 @@ "node": ">=10" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -15426,25 +14944,6 @@ ], "license": "BSD-3-Clause" }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, "node_modules/inline-style-parser": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", @@ -16432,10 +15931,11 @@ } }, "node_modules/lit": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.1.tgz", - "integrity": "sha512-Ksr/8L3PTapbdXJCk+EJVB78jDodUMaP54gD24W186zGRARvwrsPfS60wae/SSCTCNZVPd1chXqio1qHQmu4NA==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.2.tgz", + "integrity": "sha512-NF9zbsP79l4ao2SNrH3NkfmFgN/hBYSQo90saIVI1o5GpjAdCPVstVzO1MrLOakHoEhYkrtRjPK6Ob521aoYWQ==", "license": "BSD-3-Clause", + "peer": true, "dependencies": { "@lit/reactive-element": "^2.1.0", "lit-element": "^4.2.0", @@ -16527,26 +16027,14 @@ } }, "node_modules/magic-string": { - "version": "0.30.19", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", - "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/marked": { - "version": "15.0.12", - "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", - "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -17495,7 +16983,8 @@ "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/own-keys": { "version": "1.0.1", @@ -17547,16 +17036,6 @@ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "license": "MIT" }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -17703,9 +17182,9 @@ } }, "node_modules/pino-std-serializers": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", - "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", "license": "MIT" }, "node_modules/possible-typed-array-names": { @@ -17737,6 +17216,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -17840,6 +17320,7 @@ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -17918,10 +17399,11 @@ } }, "node_modules/react": { - "version": "19.1.1", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", - "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -18047,15 +17529,16 @@ } }, "node_modules/react-dom": { - "version": "19.1.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", - "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", + "peer": true, "dependencies": { - "scheduler": "^0.26.0" + "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.1.1" + "react": "^19.2.3" } }, "node_modules/react-fast-compare": { @@ -18133,9 +17616,9 @@ } }, "node_modules/react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", "dev": true, "license": "MIT", "engines": { @@ -18143,10 +17626,11 @@ } }, "node_modules/react-router": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.3.tgz", - "integrity": "sha512-zf45LZp5skDC6I3jDLXQUu0u26jtuP4lEGbc7BbdyxenBN1vJSTA18czM2D+h5qyMBuMrD+9uB+mU37HIoKGRA==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.12.0.tgz", + "integrity": "sha512-kTPDYPFzDVGIIGNLS5VJykK0HfHLY5MF3b+xj0/tTyNYL1gF1qs7u67Z9jEhQk2sQ98SUaHxlG31g1JtF7IfVw==", "license": "MIT", + "peer": true, "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" @@ -18477,6 +17961,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz", "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -18524,13 +18009,13 @@ } }, "node_modules/rollup-plugin-visualizer": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.14.0.tgz", - "integrity": "sha512-VlDXneTDaKsHIw8yzJAFWtrzguoJ/LnQ+lMpoVfYJ3jJF4Ihe5oYLAqLklIK/35lgUY+1yEzCkHyZ1j4A5w5fA==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-6.0.5.tgz", + "integrity": "sha512-9+HlNgKCVbJDs8tVtjQ43US12eqaiHyyiLMdBwQ7vSZPiHMysGNo2E88TAp1si5wx8NAoYriI2A5kuKfIakmJg==", "dev": true, "license": "MIT", "dependencies": { - "open": "^8.4.0", + "open": "^8.0.0", "picomatch": "^4.0.2", "source-map": "^0.7.4", "yargs": "^17.5.1" @@ -18542,7 +18027,7 @@ "node": ">=18" }, "peerDependencies": { - "rolldown": "1.x", + "rolldown": "1.x || ^1.0.0-beta", "rollup": "2.x || 3.x || 4.x" }, "peerDependenciesMeta": { @@ -18651,9 +18136,9 @@ "license": "MIT" }, "node_modules/scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, "node_modules/scroll-into-view-if-needed": { @@ -18692,9 +18177,9 @@ } }, "node_modules/set-cookie-parser": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", - "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", "license": "MIT" }, "node_modules/set-function-length": { @@ -19241,22 +18726,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/style-observer": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/style-observer/-/style-observer-0.0.8.tgz", - "integrity": "sha512-UaIPn33Sx4BJ+goia51Q++VFWoplWK1995VdxQYzwwbFa+FUNLKlG+aiIdG2Vw7VyzIUBi8tqu8mTyg0Ppu6Yg==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/LeaVerou" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/leaverou" - } - ], - "license": "MIT" - }, "node_modules/style-to-js": { "version": "1.1.17", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.17.tgz", @@ -19321,6 +18790,7 @@ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/dcastil" @@ -19437,6 +18907,7 @@ "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", "devOptional": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -19682,10 +19153,11 @@ } }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -19714,9 +19186,9 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "devOptional": true, "license": "MIT" }, @@ -19984,9 +19456,9 @@ } }, "node_modules/validator": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", - "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "version": "13.15.22", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.22.tgz", + "integrity": "sha512-uT/YQjiyLJP7HSrv/dPZqK9L28xf8hsNca01HSz1dfmI0DgMfjopp1rO/z13NeGF1tVystF0Ejx3y4rUKPw+bQ==", "license": "MIT", "engines": { "node": ">= 0.10" @@ -20045,23 +19517,24 @@ } }, "node_modules/vite": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", - "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "license": "MIT", + "peer": true, "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -20070,14 +19543,14 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", - "less": "*", + "less": "^4.0.0", "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" @@ -20119,9 +19592,9 @@ } }, "node_modules/vite-plugin-checker": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.10.3.tgz", - "integrity": "sha512-f4sekUcDPF+T+GdbbE8idb1i2YplBAoH+SfRS0e/WRBWb2rYb1Jf5Pimll0Rj+3JgIYWwG2K5LtBPCXxoibkLg==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.12.0.tgz", + "integrity": "sha512-CmdZdDOGss7kdQwv73UyVgLPv0FVYe5czAgnmRX2oKljgEvSrODGuClaV3PDR2+3ou7N/OKGauDDBjy2MB07Rg==", "dev": true, "license": "MIT", "dependencies": { @@ -20130,22 +19603,22 @@ "npm-run-path": "^6.0.0", "picocolors": "^1.1.1", "picomatch": "^4.0.3", - "strip-ansi": "^7.1.0", "tiny-invariant": "^1.3.3", - "tinyglobby": "^0.2.14", + "tinyglobby": "^0.2.15", "vscode-uri": "^3.1.0" }, "engines": { - "node": ">=14.16" + "node": ">=16.11" }, "peerDependencies": { "@biomejs/biome": ">=1.7", - "eslint": ">=7", + "eslint": ">=9.39.1", "meow": "^13.2.0", "optionator": "^0.9.4", + "oxlint": ">=1", "stylelint": ">=16", "typescript": "*", - "vite": ">=2.0.0", + "vite": ">=5.4.21", "vls": "*", "vti": "*", "vue-tsc": "~2.2.10 || ^3.0.0" @@ -20163,6 +19636,9 @@ "optionator": { "optional": true }, + "oxlint": { + "optional": true + }, "stylelint": { "optional": true }, @@ -20312,30 +19788,30 @@ } }, "node_modules/workbox-background-sync": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.3.0.tgz", - "integrity": "sha512-PCSk3eK7Mxeuyatb22pcSx9dlgWNv3+M8PqPaYDokks8Y5/FX4soaOqj3yhAZr5k6Q5JWTOMYgaJBpbw11G9Eg==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.4.0.tgz", + "integrity": "sha512-8CB9OxKAgKZKyNMwfGZ1XESx89GryWTfI+V5yEj8sHjFH8MFelUwYXEyldEK6M6oKMmn807GoJFUEA1sC4XS9w==", "dev": true, "license": "MIT", "dependencies": { "idb": "^7.0.1", - "workbox-core": "7.3.0" + "workbox-core": "7.4.0" } }, "node_modules/workbox-broadcast-update": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-7.3.0.tgz", - "integrity": "sha512-T9/F5VEdJVhwmrIAE+E/kq5at2OY6+OXXgOWQevnubal6sO92Gjo24v6dCVwQiclAF5NS3hlmsifRrpQzZCdUA==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-7.4.0.tgz", + "integrity": "sha512-+eZQwoktlvo62cI0b+QBr40v5XjighxPq3Fzo9AWMiAosmpG5gxRHgTbGGhaJv/q/MFVxwFNGh/UwHZ/8K88lA==", "dev": true, "license": "MIT", "dependencies": { - "workbox-core": "7.3.0" + "workbox-core": "7.4.0" } }, "node_modules/workbox-build": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-7.3.0.tgz", - "integrity": "sha512-JGL6vZTPlxnlqZRhR/K/msqg3wKP+m0wfEUVosK7gsYzSgeIxvZLi1ViJJzVL7CEeI8r7rGFV973RiEqkP3lWQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-7.4.0.tgz", + "integrity": "sha512-Ntk1pWb0caOFIvwz/hfgrov/OJ45wPEhI5PbTywQcYjyZiVhT3UrwwUPl6TRYbTm4moaFYithYnl1lvZ8UjxcA==", "dev": true, "license": "MIT", "dependencies": { @@ -20352,33 +19828,33 @@ "common-tags": "^1.8.0", "fast-json-stable-stringify": "^2.1.0", "fs-extra": "^9.0.1", - "glob": "^7.1.6", + "glob": "^11.0.1", "lodash": "^4.17.20", "pretty-bytes": "^5.3.0", - "rollup": "^2.43.1", + "rollup": "^2.79.2", "source-map": "^0.8.0-beta.0", "stringify-object": "^3.3.0", "strip-comments": "^2.0.1", "tempy": "^0.6.0", "upath": "^1.2.0", - "workbox-background-sync": "7.3.0", - "workbox-broadcast-update": "7.3.0", - "workbox-cacheable-response": "7.3.0", - "workbox-core": "7.3.0", - "workbox-expiration": "7.3.0", - "workbox-google-analytics": "7.3.0", - "workbox-navigation-preload": "7.3.0", - "workbox-precaching": "7.3.0", - "workbox-range-requests": "7.3.0", - "workbox-recipes": "7.3.0", - "workbox-routing": "7.3.0", - "workbox-strategies": "7.3.0", - "workbox-streams": "7.3.0", - "workbox-sw": "7.3.0", - "workbox-window": "7.3.0" + "workbox-background-sync": "7.4.0", + "workbox-broadcast-update": "7.4.0", + "workbox-cacheable-response": "7.4.0", + "workbox-core": "7.4.0", + "workbox-expiration": "7.4.0", + "workbox-google-analytics": "7.4.0", + "workbox-navigation-preload": "7.4.0", + "workbox-precaching": "7.4.0", + "workbox-range-requests": "7.4.0", + "workbox-recipes": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0", + "workbox-streams": "7.4.0", + "workbox-sw": "7.4.0", + "workbox-window": "7.4.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=20.0.0" } }, "node_modules/workbox-build/node_modules/@rollup/plugin-babel": { @@ -20444,17 +19920,6 @@ "dev": true, "license": "MIT" }, - "node_modules/workbox-build/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/workbox-build/node_modules/estree-walker": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", @@ -20462,28 +19927,6 @@ "dev": true, "license": "MIT" }, - "node_modules/workbox-build/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/workbox-build/node_modules/magic-string": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", @@ -20494,19 +19937,6 @@ "sourcemap-codec": "^1.4.8" } }, - "node_modules/workbox-build/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/workbox-build/node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -20526,6 +19956,7 @@ "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -20551,140 +19982,140 @@ } }, "node_modules/workbox-cacheable-response": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.3.0.tgz", - "integrity": "sha512-eAFERIg6J2LuyELhLlmeRcJFa5e16Mj8kL2yCDbhWE+HUun9skRQrGIFVUagqWj4DMaaPSMWfAolM7XZZxNmxA==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.4.0.tgz", + "integrity": "sha512-0Fb8795zg/x23ISFkAc7lbWes6vbw34DGFIMw31cwuHPgDEC/5EYm6m/ZkylLX0EnEbbOyOCLjKgFS/Z5g0HeQ==", "dev": true, "license": "MIT", "dependencies": { - "workbox-core": "7.3.0" + "workbox-core": "7.4.0" } }, "node_modules/workbox-core": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-7.3.0.tgz", - "integrity": "sha512-Z+mYrErfh4t3zi7NVTvOuACB0A/jA3bgxUN3PwtAVHvfEsZxV9Iju580VEETug3zYJRc0Dmii/aixI/Uxj8fmw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-7.4.0.tgz", + "integrity": "sha512-6BMfd8tYEnN4baG4emG9U0hdXM4gGuDU3ectXuVHnj71vwxTFI7WOpQJC4siTOlVtGqCUtj0ZQNsrvi6kZZTAQ==", "dev": true, "license": "MIT" }, "node_modules/workbox-expiration": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-7.3.0.tgz", - "integrity": "sha512-lpnSSLp2BM+K6bgFCWc5bS1LR5pAwDWbcKt1iL87/eTSJRdLdAwGQznZE+1czLgn/X05YChsrEegTNxjM067vQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-7.4.0.tgz", + "integrity": "sha512-V50p4BxYhtA80eOvulu8xVfPBgZbkxJ1Jr8UUn0rvqjGhLDqKNtfrDfjJKnLz2U8fO2xGQJTx/SKXNTzHOjnHw==", "dev": true, "license": "MIT", "dependencies": { "idb": "^7.0.1", - "workbox-core": "7.3.0" + "workbox-core": "7.4.0" } }, "node_modules/workbox-google-analytics": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-7.3.0.tgz", - "integrity": "sha512-ii/tSfFdhjLHZ2BrYgFNTrb/yk04pw2hasgbM70jpZfLk0vdJAXgaiMAWsoE+wfJDNWoZmBYY0hMVI0v5wWDbg==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-7.4.0.tgz", + "integrity": "sha512-MVPXQslRF6YHkzGoFw1A4GIB8GrKym/A5+jYDUSL+AeJw4ytQGrozYdiZqUW1TPQHW8isBCBtyFJergUXyNoWQ==", "dev": true, "license": "MIT", "dependencies": { - "workbox-background-sync": "7.3.0", - "workbox-core": "7.3.0", - "workbox-routing": "7.3.0", - "workbox-strategies": "7.3.0" + "workbox-background-sync": "7.4.0", + "workbox-core": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0" } }, "node_modules/workbox-navigation-preload": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-7.3.0.tgz", - "integrity": "sha512-fTJzogmFaTv4bShZ6aA7Bfj4Cewaq5rp30qcxl2iYM45YD79rKIhvzNHiFj1P+u5ZZldroqhASXwwoyusnr2cg==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-7.4.0.tgz", + "integrity": "sha512-etzftSgdQfjMcfPgbfaZCfM2QuR1P+4o8uCA2s4rf3chtKTq/Om7g/qvEOcZkG6v7JZOSOxVYQiOu6PbAZgU6w==", "dev": true, "license": "MIT", "dependencies": { - "workbox-core": "7.3.0" + "workbox-core": "7.4.0" } }, "node_modules/workbox-precaching": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.3.0.tgz", - "integrity": "sha512-ckp/3t0msgXclVAYaNndAGeAoWQUv7Rwc4fdhWL69CCAb2UHo3Cef0KIUctqfQj1p8h6aGyz3w8Cy3Ihq9OmIw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.4.0.tgz", + "integrity": "sha512-VQs37T6jDqf1rTxUJZXRl3yjZMf5JX/vDPhmx2CPgDDKXATzEoqyRqhYnRoxl6Kr0rqaQlp32i9rtG5zTzIlNg==", "dev": true, "license": "MIT", "dependencies": { - "workbox-core": "7.3.0", - "workbox-routing": "7.3.0", - "workbox-strategies": "7.3.0" + "workbox-core": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0" } }, "node_modules/workbox-range-requests": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-7.3.0.tgz", - "integrity": "sha512-EyFmM1KpDzzAouNF3+EWa15yDEenwxoeXu9bgxOEYnFfCxns7eAxA9WSSaVd8kujFFt3eIbShNqa4hLQNFvmVQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-7.4.0.tgz", + "integrity": "sha512-3Vq854ZNuP6Y0KZOQWLaLC9FfM7ZaE+iuQl4VhADXybwzr4z/sMmnLgTeUZLq5PaDlcJBxYXQ3U91V7dwAIfvw==", "dev": true, "license": "MIT", "dependencies": { - "workbox-core": "7.3.0" + "workbox-core": "7.4.0" } }, "node_modules/workbox-recipes": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-7.3.0.tgz", - "integrity": "sha512-BJro/MpuW35I/zjZQBcoxsctgeB+kyb2JAP5EB3EYzePg8wDGoQuUdyYQS+CheTb+GhqJeWmVs3QxLI8EBP1sg==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-7.4.0.tgz", + "integrity": "sha512-kOkWvsAn4H8GvAkwfJTbwINdv4voFoiE9hbezgB1sb/0NLyTG4rE7l6LvS8lLk5QIRIto+DjXLuAuG3Vmt3cxQ==", "dev": true, "license": "MIT", "dependencies": { - "workbox-cacheable-response": "7.3.0", - "workbox-core": "7.3.0", - "workbox-expiration": "7.3.0", - "workbox-precaching": "7.3.0", - "workbox-routing": "7.3.0", - "workbox-strategies": "7.3.0" + "workbox-cacheable-response": "7.4.0", + "workbox-core": "7.4.0", + "workbox-expiration": "7.4.0", + "workbox-precaching": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0" } }, "node_modules/workbox-routing": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-7.3.0.tgz", - "integrity": "sha512-ZUlysUVn5ZUzMOmQN3bqu+gK98vNfgX/gSTZ127izJg/pMMy4LryAthnYtjuqcjkN4HEAx1mdgxNiKJMZQM76A==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-7.4.0.tgz", + "integrity": "sha512-C/ooj5uBWYAhAqwmU8HYQJdOjjDKBp9MzTQ+otpMmd+q0eF59K+NuXUek34wbL0RFrIXe/KKT+tUWcZcBqxbHQ==", "dev": true, "license": "MIT", "dependencies": { - "workbox-core": "7.3.0" + "workbox-core": "7.4.0" } }, "node_modules/workbox-strategies": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-7.3.0.tgz", - "integrity": "sha512-tmZydug+qzDFATwX7QiEL5Hdf7FrkhjaF9db1CbB39sDmEZJg3l9ayDvPxy8Y18C3Y66Nrr9kkN1f/RlkDgllg==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-7.4.0.tgz", + "integrity": "sha512-T4hVqIi5A4mHi92+5EppMX3cLaVywDp8nsyUgJhOZxcfSV/eQofcOA6/EMo5rnTNmNTpw0rUgjAI6LaVullPpg==", "dev": true, "license": "MIT", "dependencies": { - "workbox-core": "7.3.0" + "workbox-core": "7.4.0" } }, "node_modules/workbox-streams": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-7.3.0.tgz", - "integrity": "sha512-SZnXucyg8x2Y61VGtDjKPO5EgPUG5NDn/v86WYHX+9ZqvAsGOytP0Jxp1bl663YUuMoXSAtsGLL+byHzEuMRpw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-7.4.0.tgz", + "integrity": "sha512-QHPBQrey7hQbnTs5GrEVoWz7RhHJXnPT+12qqWM378orDMo5VMJLCkCM1cnCk+8Eq92lccx/VgRZ7WAzZWbSLg==", "dev": true, "license": "MIT", "dependencies": { - "workbox-core": "7.3.0", - "workbox-routing": "7.3.0" + "workbox-core": "7.4.0", + "workbox-routing": "7.4.0" } }, "node_modules/workbox-sw": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.3.0.tgz", - "integrity": "sha512-aCUyoAZU9IZtH05mn0ACUpyHzPs0lMeJimAYkQkBsOWiqaJLgusfDCR+yllkPkFRxWpZKF8vSvgHYeG7LwhlmA==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.4.0.tgz", + "integrity": "sha512-ltU+Kr3qWR6BtbdlMnCjobZKzeV1hN+S6UvDywBrwM19TTyqA03X66dzw1tEIdJvQ4lYKkBFox6IAEhoSEZ8Xw==", "dev": true, "license": "MIT" }, "node_modules/workbox-window": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.3.0.tgz", - "integrity": "sha512-qW8PDy16OV1UBaUNGlTVcepzrlzyzNW/ZJvFQQs2j2TzGsg6IKjcpZC1RSquqQnTOafl5pCj5bGfAHlCjOOjdA==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.4.0.tgz", + "integrity": "sha512-/bIYdBLAVsNR3v7gYGaV4pQW3M3kEPx5E8vDxGvxo6khTrGtSSCS7QiFKv9ogzBgZiy0OXLP9zO28U/1nF1mfw==", "dev": true, "license": "MIT", "dependencies": { "@types/trusted-types": "^2.0.2", - "workbox-core": "7.3.0" + "workbox-core": "7.4.0" } }, "node_modules/wrap-ansi": { diff --git a/app/package.json b/app/package.json index 9c35cc9..fc55707 100644 --- a/app/package.json +++ b/app/package.json @@ -5,51 +5,47 @@ "dependencies": { "@heroui/react": "^2.8.7", "@phosphor-icons/react": "^2.1.10", - "@polymer/polymer": "3.5.2", "@react-stately/data": "^3.12.2", "@react-types/shared": "^3.28.0", "@tailwindcss/vite": "4.1.13", - "@vaadin/bundles": "24.9.4", + "@vaadin/aura": "25.0.3", "@vaadin/common-frontend": "0.0.19", - "@vaadin/hilla-file-router": "24.9.4", - "@vaadin/hilla-frontend": "24.9.4", - "@vaadin/hilla-lit-form": "24.9.4", - "@vaadin/hilla-react-auth": "24.9.4", - "@vaadin/hilla-react-crud": "24.9.4", - "@vaadin/hilla-react-form": "24.9.4", - "@vaadin/hilla-react-i18n": "24.9.4", - "@vaadin/hilla-react-signals": "24.9.4", - "@vaadin/polymer-legacy-adapter": "24.9.4", - "@vaadin/react-components": "24.9.4", + "@vaadin/hilla-file-router": "25.0.4", + "@vaadin/hilla-frontend": "25.0.4", + "@vaadin/hilla-lit-form": "25.0.4", + "@vaadin/hilla-react-auth": "25.0.4", + "@vaadin/hilla-react-crud": "25.0.4", + "@vaadin/hilla-react-form": "25.0.4", + "@vaadin/hilla-react-i18n": "25.0.4", + "@vaadin/hilla-react-signals": "25.0.4", + "@vaadin/react-components": "25.0.3", "@vaadin/vaadin-development-mode-detector": "2.0.7", - "@vaadin/vaadin-lumo-styles": "24.9.4", - "@vaadin/vaadin-material-styles": "24.9.4", - "@vaadin/vaadin-themable-mixin": "24.9.4", + "@vaadin/vaadin-lumo-styles": "25.0.3", + "@vaadin/vaadin-themable-mixin": "25.0.3", "@vaadin/vaadin-usage-statistics": "2.1.3", "blurhash": "^2.0.5", "classnames": "^2.5.1", - "construct-style-sheets-polyfill": "3.1.0", - "date-fns": "2.29.3", + "date-fns": "4.1.0", "formik": "^2.4.6", "framer-motion": "^12.23.22", "fzf": "^0.5.2", "http-status-codes": "^2.3.0", - "lit": "3.3.1", + "lit": "3.3.2", "moment": "^2.30.1", "moment-timezone": "^0.5.47", "next-themes": "^0.4.6", "postcss": "^8.5.6", "postcss-import": "^16.1.1", "rand-seed": "^2.1.7", - "react": "19.1.1", + "react": "19.2.3", "react-accessible-treeview": "^2.11.1", "react-aria-components": "^1.7.1", "react-confetti-boom": "^1.0.0", - "react-dom": "19.1.1", + "react-dom": "19.2.3", "react-markdown": "^10.1.0", "react-player": "^2.16.0", "react-realtime-chart": "^0.8.1", - "react-router": "7.6.3", + "react-router": "7.12.0", "react-window": "^2.2.3", "remark-breaks": "^4.0.0", "swiper": "^11.2.6", @@ -57,59 +53,53 @@ "yup": "^1.6.1" }, "devDependencies": { - "@babel/preset-react": "7.27.1", + "@babel/preset-react": "7.28.5", "@lit-labs/react": "^2.1.3", "@preact/signals-react-transform": "0.6.0", - "@rollup/plugin-replace": "6.0.2", + "@rollup/plugin-replace": "6.0.3", "@rollup/pluginutils": "5.3.0", - "@types/node": "^22.4.0", - "@types/react": "19.1.17", - "@types/react-dom": "19.1.11", + "@types/node": "25.0.3", + "@types/react": "19.2.7", + "@types/react-dom": "19.2.3", "@types/react-window": "^1.8.8", - "@vaadin/hilla-generator-cli": "24.9.4", - "@vaadin/hilla-generator-core": "24.9.4", - "@vaadin/hilla-generator-plugin-backbone": "24.9.4", - "@vaadin/hilla-generator-plugin-barrel": "24.9.4", - "@vaadin/hilla-generator-plugin-client": "24.9.4", - "@vaadin/hilla-generator-plugin-model": "24.9.4", - "@vaadin/hilla-generator-plugin-push": "24.9.4", - "@vaadin/hilla-generator-plugin-signals": "24.9.4", - "@vaadin/hilla-generator-plugin-subtypes": "24.9.4", - "@vaadin/hilla-generator-plugin-transfertypes": "24.9.4", - "@vaadin/hilla-generator-utils": "24.9.4", - "@vitejs/plugin-react": "4.7.0", + "@vaadin/hilla-generator-cli": "25.0.4", + "@vaadin/hilla-generator-core": "25.0.4", + "@vaadin/hilla-generator-plugin-backbone": "25.0.4", + "@vaadin/hilla-generator-plugin-barrel": "25.0.4", + "@vaadin/hilla-generator-plugin-client": "25.0.4", + "@vaadin/hilla-generator-plugin-model": "25.0.4", + "@vaadin/hilla-generator-plugin-push": "25.0.4", + "@vaadin/hilla-generator-plugin-signals": "25.0.4", + "@vaadin/hilla-generator-plugin-subtypes": "25.0.4", + "@vaadin/hilla-generator-plugin-transfertypes": "25.0.4", + "@vaadin/hilla-generator-utils": "25.0.4", + "@vitejs/plugin-react": "5.1.2", "@vitejs/plugin-react-swc": "^3.7.0", - "glob": "11.0.3", - "magic-string": "0.30.19", + "baseline-browser-mapping": "^2.9.19", + "magic-string": "0.30.21", "rollup-plugin-brotli": "3.1.0", - "rollup-plugin-visualizer": "5.14.0", + "rollup-plugin-visualizer": "6.0.5", "strip-css-comments": "5.0.0", "tailwindcss": "4.1.13", "transform-ast": "2.4.4", - "typescript": "5.8.3", - "vite": "6.4.1", - "vite-plugin-checker": "0.10.3", - "workbox-build": "7.3.0", - "workbox-core": "7.3.0", - "workbox-precaching": "7.3.0" + "typescript": "5.9.3", + "vite": "7.3.1", + "vite-plugin-checker": "0.12.0", + "workbox-build": "7.4.0" }, "overrides": { "@react-aria/utils": "^3.28.1", "classnames": "$classnames", "react": "$react", "react-dom": "$react-dom", - "@vaadin/bundles": "$@vaadin/bundles", "@vaadin/common-frontend": "$@vaadin/common-frontend", - "construct-style-sheets-polyfill": "$construct-style-sheets-polyfill", "lit": "$lit", - "@polymer/polymer": "$@polymer/polymer", "@phosphor-icons/react": "$@phosphor-icons/react", "formik": "$formik", "yup": "$yup", "@heroui/react": "$@heroui/react", "framer-motion": "$framer-motion", "http-status-codes": "$http-status-codes", - "@vaadin/polymer-legacy-adapter": "$@vaadin/polymer-legacy-adapter", "@vaadin/vaadin-development-mode-detector": "$@vaadin/vaadin-development-mode-detector", "@vaadin/vaadin-usage-statistics": "$@vaadin/vaadin-usage-statistics", "@vaadin/react-components": "$@vaadin/react-components", @@ -127,7 +117,6 @@ "date-fns": "$date-fns", "@vaadin/vaadin-themable-mixin": "$@vaadin/vaadin-themable-mixin", "@vaadin/vaadin-lumo-styles": "$@vaadin/vaadin-lumo-styles", - "@vaadin/vaadin-material-styles": "$@vaadin/vaadin-material-styles", "@react-types/shared": "$@react-types/shared", "@react-stately/data": "$@react-stately/data", "react-aria-components": "$react-aria-components", @@ -140,133 +129,128 @@ "remark-breaks": "$remark-breaks", "valtio": "$valtio", "fzf": "$fzf", - "@vaadin/router": "2.0.0", "@tailwindcss/vite": "$@tailwindcss/vite", "postcss": "$postcss", "postcss-import": "$postcss-import", "next-themes": "$next-themes", - "@vaadin/a11y-base": "24.9.4", - "@vaadin/accordion": "24.9.4", - "@vaadin/app-layout": "24.9.4", - "@vaadin/avatar": "24.9.4", - "@vaadin/avatar-group": "24.9.4", - "@vaadin/button": "24.9.4", - "@vaadin/card": "24.9.4", - "@vaadin/checkbox": "24.9.4", - "@vaadin/checkbox-group": "24.9.4", - "@vaadin/combo-box": "24.9.4", - "@vaadin/component-base": "24.9.4", - "@vaadin/confirm-dialog": "24.9.4", - "@vaadin/context-menu": "24.9.4", - "@vaadin/custom-field": "24.9.4", - "@vaadin/date-picker": "24.9.4", - "@vaadin/date-time-picker": "24.9.4", - "@vaadin/details": "24.9.4", - "@vaadin/dialog": "24.9.4", - "@vaadin/email-field": "24.9.4", - "@vaadin/field-base": "24.9.4", - "@vaadin/field-highlighter": "24.9.4", - "@vaadin/form-layout": "24.9.4", - "@vaadin/grid": "24.9.4", - "@vaadin/horizontal-layout": "24.9.4", - "@vaadin/icon": "24.9.4", - "@vaadin/icons": "24.9.4", - "@vaadin/input-container": "24.9.4", - "@vaadin/integer-field": "24.9.4", - "@vaadin/item": "24.9.4", - "@vaadin/list-box": "24.9.4", - "@vaadin/lit-renderer": "24.9.4", - "@vaadin/login": "24.9.4", - "@vaadin/markdown": "24.9.4", - "@vaadin/master-detail-layout": "24.9.4", - "@vaadin/menu-bar": "24.9.4", - "@vaadin/message-input": "24.9.4", - "@vaadin/message-list": "24.9.4", - "@vaadin/multi-select-combo-box": "24.9.4", - "@vaadin/notification": "24.9.4", - "@vaadin/number-field": "24.9.4", - "@vaadin/overlay": "24.9.4", - "@vaadin/password-field": "24.9.4", - "@vaadin/popover": "24.9.4", - "@vaadin/progress-bar": "24.9.4", - "@vaadin/radio-group": "24.9.4", - "@vaadin/scroller": "24.9.4", - "@vaadin/select": "24.9.4", - "@vaadin/side-nav": "24.9.4", - "@vaadin/split-layout": "24.9.4", - "@vaadin/tabs": "24.9.4", - "@vaadin/tabsheet": "24.9.4", - "@vaadin/text-area": "24.9.4", - "@vaadin/text-field": "24.9.4", - "@vaadin/time-picker": "24.9.4", - "@vaadin/tooltip": "24.9.4", - "@vaadin/upload": "24.9.4", - "@vaadin/vertical-layout": "24.9.4", - "@vaadin/virtual-list": "24.9.4", "react-realtime-chart": "$react-realtime-chart", "react-window": "$react-window", - "blurhash": "$blurhash" + "blurhash": "$blurhash", + "@vaadin/aura": "$@vaadin/aura", + "@vaadin/a11y-base": "25.0.3", + "@vaadin/accordion": "25.0.3", + "@vaadin/app-layout": "25.0.3", + "@vaadin/avatar": "25.0.3", + "@vaadin/avatar-group": "25.0.3", + "@vaadin/button": "25.0.3", + "@vaadin/card": "25.0.3", + "@vaadin/checkbox": "25.0.3", + "@vaadin/checkbox-group": "25.0.3", + "@vaadin/combo-box": "25.0.3", + "@vaadin/component-base": "25.0.3", + "@vaadin/confirm-dialog": "25.0.3", + "@vaadin/context-menu": "25.0.3", + "@vaadin/custom-field": "25.0.3", + "@vaadin/date-picker": "25.0.3", + "@vaadin/date-time-picker": "25.0.3", + "@vaadin/details": "25.0.3", + "@vaadin/dialog": "25.0.3", + "@vaadin/email-field": "25.0.3", + "@vaadin/field-base": "25.0.3", + "@vaadin/field-highlighter": "25.0.3", + "@vaadin/form-layout": "25.0.3", + "@vaadin/grid": "25.0.3", + "@vaadin/horizontal-layout": "25.0.3", + "@vaadin/icon": "25.0.3", + "@vaadin/icons": "25.0.3", + "@vaadin/input-container": "25.0.3", + "@vaadin/integer-field": "25.0.3", + "@vaadin/item": "25.0.3", + "@vaadin/list-box": "25.0.3", + "@vaadin/lit-renderer": "25.0.3", + "@vaadin/login": "25.0.3", + "@vaadin/markdown": "25.0.3", + "@vaadin/master-detail-layout": "25.0.3", + "@vaadin/menu-bar": "25.0.3", + "@vaadin/message-input": "25.0.3", + "@vaadin/message-list": "25.0.3", + "@vaadin/multi-select-combo-box": "25.0.3", + "@vaadin/notification": "25.0.3", + "@vaadin/number-field": "25.0.3", + "@vaadin/overlay": "25.0.3", + "@vaadin/password-field": "25.0.3", + "@vaadin/popover": "25.0.3", + "@vaadin/progress-bar": "25.0.3", + "@vaadin/radio-group": "25.0.3", + "@vaadin/scroller": "25.0.3", + "@vaadin/select": "25.0.3", + "@vaadin/side-nav": "25.0.3", + "@vaadin/split-layout": "25.0.3", + "@vaadin/tabs": "25.0.3", + "@vaadin/tabsheet": "25.0.3", + "@vaadin/text-area": "25.0.3", + "@vaadin/text-field": "25.0.3", + "@vaadin/time-picker": "25.0.3", + "@vaadin/tooltip": "25.0.3", + "@vaadin/upload": "25.0.3", + "@vaadin/router": "2.0.1", + "@vaadin/vertical-layout": "25.0.3", + "@vaadin/virtual-list": "25.0.3" }, "vaadin": { "dependencies": { - "@polymer/polymer": "3.5.2", - "@vaadin/bundles": "24.9.4", + "@vaadin/aura": "25.0.3", "@vaadin/common-frontend": "0.0.19", - "@vaadin/hilla-file-router": "24.9.4", - "@vaadin/hilla-frontend": "24.9.4", - "@vaadin/hilla-lit-form": "24.9.4", - "@vaadin/hilla-react-auth": "24.9.4", - "@vaadin/hilla-react-crud": "24.9.4", - "@vaadin/hilla-react-form": "24.9.4", - "@vaadin/hilla-react-i18n": "24.9.4", - "@vaadin/hilla-react-signals": "24.9.4", - "@vaadin/polymer-legacy-adapter": "24.9.4", - "@vaadin/react-components": "24.9.4", + "@vaadin/hilla-file-router": "25.0.4", + "@vaadin/hilla-frontend": "25.0.4", + "@vaadin/hilla-lit-form": "25.0.4", + "@vaadin/hilla-react-auth": "25.0.4", + "@vaadin/hilla-react-crud": "25.0.4", + "@vaadin/hilla-react-form": "25.0.4", + "@vaadin/hilla-react-i18n": "25.0.4", + "@vaadin/hilla-react-signals": "25.0.4", + "@vaadin/react-components": "25.0.3", "@vaadin/vaadin-development-mode-detector": "2.0.7", - "@vaadin/vaadin-lumo-styles": "24.9.4", - "@vaadin/vaadin-material-styles": "24.9.4", - "@vaadin/vaadin-themable-mixin": "24.9.4", + "@vaadin/vaadin-lumo-styles": "25.0.3", + "@vaadin/vaadin-themable-mixin": "25.0.3", "@vaadin/vaadin-usage-statistics": "2.1.3", - "construct-style-sheets-polyfill": "3.1.0", - "date-fns": "2.29.3", - "lit": "3.3.1", - "react": "19.1.1", - "react-dom": "19.1.1", - "react-router": "7.6.3" + "date-fns": "4.1.0", + "lit": "3.3.2", + "react": "19.2.3", + "react-dom": "19.2.3", + "react-router": "7.12.0" }, "devDependencies": { - "@babel/preset-react": "7.27.1", + "@babel/preset-react": "7.28.5", "@preact/signals-react-transform": "0.6.0", - "@rollup/plugin-replace": "6.0.2", + "@rollup/plugin-replace": "6.0.3", "@rollup/pluginutils": "5.3.0", - "@types/react": "19.1.17", - "@types/react-dom": "19.1.11", - "@vaadin/hilla-generator-cli": "24.9.4", - "@vaadin/hilla-generator-core": "24.9.4", - "@vaadin/hilla-generator-plugin-backbone": "24.9.4", - "@vaadin/hilla-generator-plugin-barrel": "24.9.4", - "@vaadin/hilla-generator-plugin-client": "24.9.4", - "@vaadin/hilla-generator-plugin-model": "24.9.4", - "@vaadin/hilla-generator-plugin-push": "24.9.4", - "@vaadin/hilla-generator-plugin-signals": "24.9.4", - "@vaadin/hilla-generator-plugin-subtypes": "24.9.4", - "@vaadin/hilla-generator-plugin-transfertypes": "24.9.4", - "@vaadin/hilla-generator-utils": "24.9.4", - "@vitejs/plugin-react": "4.7.0", - "glob": "11.0.3", - "magic-string": "0.30.19", + "@types/node": "25.0.3", + "@types/react": "19.2.7", + "@types/react-dom": "19.2.3", + "@vaadin/hilla-generator-cli": "25.0.4", + "@vaadin/hilla-generator-core": "25.0.4", + "@vaadin/hilla-generator-plugin-backbone": "25.0.4", + "@vaadin/hilla-generator-plugin-barrel": "25.0.4", + "@vaadin/hilla-generator-plugin-client": "25.0.4", + "@vaadin/hilla-generator-plugin-model": "25.0.4", + "@vaadin/hilla-generator-plugin-push": "25.0.4", + "@vaadin/hilla-generator-plugin-signals": "25.0.4", + "@vaadin/hilla-generator-plugin-subtypes": "25.0.4", + "@vaadin/hilla-generator-plugin-transfertypes": "25.0.4", + "@vaadin/hilla-generator-utils": "25.0.4", + "@vitejs/plugin-react": "5.1.2", + "magic-string": "0.30.21", "rollup-plugin-brotli": "3.1.0", - "rollup-plugin-visualizer": "5.14.0", + "rollup-plugin-visualizer": "6.0.5", "strip-css-comments": "5.0.0", "transform-ast": "2.4.4", - "typescript": "5.8.3", - "vite": "6.4.1", - "vite-plugin-checker": "0.10.3", - "workbox-build": "7.3.0", - "workbox-core": "7.3.0", - "workbox-precaching": "7.3.0" + "typescript": "5.9.3", + "vite": "7.3.1", + "vite-plugin-checker": "0.12.0", + "workbox-build": "7.4.0" }, "disableUsageStatistics": true, - "hash": "760523c518e07bbe0567ae5d1b281ccf90326b285b5feb3c0f269c52ec774f88" + "hash": "d2c583f908a126db3f53ccbc87688b5089107afb58a87159631dc257a3a279ae" } -} +} \ No newline at end of file diff --git a/app/src/main/bundles/prod.bundle b/app/src/main/bundles/prod.bundle index d03b228..fa09a70 100644 Binary files a/app/src/main/bundles/prod.bundle and b/app/src/main/bundles/prod.bundle differ diff --git a/app/src/main/kotlin/org/gameyfin/app/collections/entities/CollectionMetadata.kt b/app/src/main/kotlin/org/gameyfin/app/collections/entities/CollectionMetadata.kt index c9de46c..9221cf3 100644 --- a/app/src/main/kotlin/org/gameyfin/app/collections/entities/CollectionMetadata.kt +++ b/app/src/main/kotlin/org/gameyfin/app/collections/entities/CollectionMetadata.kt @@ -1,5 +1,6 @@ package org.gameyfin.app.collections.entities +import jakarta.persistence.Column import jakarta.persistence.ElementCollection import jakarta.persistence.Embeddable import jakarta.persistence.FetchType @@ -11,5 +12,6 @@ class CollectionMetadata( val displayOrder: Int = -1, @ElementCollection(fetch = FetchType.EAGER) + @Column(nullable = false) val gamesAddedAt: MutableMap = mutableMapOf() ) \ No newline at end of file diff --git a/app/src/main/kotlin/org/gameyfin/app/config/ConfigProperties.kt b/app/src/main/kotlin/org/gameyfin/app/config/ConfigProperties.kt index 6b130ce..c296eb6 100644 --- a/app/src/main/kotlin/org/gameyfin/app/config/ConfigProperties.kt +++ b/app/src/main/kotlin/org/gameyfin/app/config/ConfigProperties.kt @@ -331,6 +331,7 @@ sealed class ConfigProperties( } } +@Suppress("EnumEntryName") enum class MatchUsersBy { username, email } \ No newline at end of file diff --git a/app/src/main/kotlin/org/gameyfin/app/config/ConfigService.kt b/app/src/main/kotlin/org/gameyfin/app/config/ConfigService.kt index e681be8..d79a3f1 100644 --- a/app/src/main/kotlin/org/gameyfin/app/config/ConfigService.kt +++ b/app/src/main/kotlin/org/gameyfin/app/config/ConfigService.kt @@ -1,7 +1,5 @@ package org.gameyfin.app.config -import com.fasterxml.jackson.core.JsonProcessingException -import com.fasterxml.jackson.databind.ObjectMapper import io.github.oshai.kotlinlogging.KotlinLogging import org.gameyfin.app.config.dto.ConfigEntryDto import org.gameyfin.app.config.dto.ConfigUpdateDto @@ -11,6 +9,8 @@ import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import reactor.core.publisher.Flux import reactor.core.publisher.Sinks +import tools.jackson.core.JacksonException +import tools.jackson.databind.ObjectMapper import java.io.Serializable import kotlin.time.Duration.Companion.milliseconds import kotlin.time.toJavaDuration @@ -186,7 +186,7 @@ class ConfigService( return try { val typeReference = objectMapper.typeFactory.constructType(configProperty.type.java) objectMapper.readValue(value.toString(), typeReference) as T - } catch (e: JsonProcessingException) { + } catch (e: JacksonException) { throw IllegalArgumentException( "Failed to deserialize value '$value' for key '${configProperty.key}' to type ${configProperty.type.simpleName}: ${e.message}", e @@ -209,7 +209,7 @@ class ConfigService( private fun serializeValue(value: T, key: String): String { return try { objectMapper.writeValueAsString(value) - } catch (e: JsonProcessingException) { + } catch (e: JacksonException) { throw IllegalArgumentException( "Failed to serialize value for key '$key': ${e.message}", e diff --git a/app/src/main/kotlin/org/gameyfin/app/config/dto/ConfigUpdateDto.kt b/app/src/main/kotlin/org/gameyfin/app/config/dto/ConfigUpdateDto.kt index 182c53f..98378d4 100644 --- a/app/src/main/kotlin/org/gameyfin/app/config/dto/ConfigUpdateDto.kt +++ b/app/src/main/kotlin/org/gameyfin/app/config/dto/ConfigUpdateDto.kt @@ -1,7 +1,7 @@ package org.gameyfin.app.config.dto -import com.fasterxml.jackson.databind.annotation.JsonDeserialize import org.gameyfin.app.core.serialization.ArrayDeserializer +import tools.jackson.databind.annotation.JsonDeserialize import java.io.Serializable data class ConfigUpdateDto( diff --git a/app/src/main/kotlin/org/gameyfin/app/config/dto/CronExpressionVerificationResultDto.kt b/app/src/main/kotlin/org/gameyfin/app/config/dto/CronExpressionVerificationResultDto.kt deleted file mode 100644 index 486800c..0000000 --- a/app/src/main/kotlin/org/gameyfin/app/config/dto/CronExpressionVerificationResultDto.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.gameyfin.app.config.dto - -data class CronExpressionVerificationResultDto( - val valid: Boolean, - val errorMessage: String? = null -) { - companion object { - val valid = CronExpressionVerificationResultDto(true) - fun invalid(errorMessage: String) = CronExpressionVerificationResultDto(false, errorMessage) - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/org/gameyfin/app/core/Role.kt b/app/src/main/kotlin/org/gameyfin/app/core/Role.kt index e3130f3..ece9afd 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/Role.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/Role.kt @@ -3,10 +3,6 @@ package org.gameyfin.app.core import com.fasterxml.jackson.annotation.JsonCreator import com.fasterxml.jackson.annotation.JsonValue import org.gameyfin.app.users.RoleService -import java.lang.Enum -import kotlin.IllegalArgumentException -import kotlin.Int -import kotlin.String enum class Role(val roleName: String, val powerLevel: Int) { @@ -28,10 +24,11 @@ enum class Role(val roleName: String, val powerLevel: Int) { return entries.find { it.roleName == enumString } } - fun safeValueOf(type: String): Role? { + fun safeValueOf(type: String?): Role? { + if (type == null) return null val enumString = type.removePrefix(RoleService.INTERNAL_ROLE_PREFIX) return try { - Enum.valueOf(Role::class.java, enumString) + java.lang.Enum.valueOf(Role::class.java, enumString) } catch (_: IllegalArgumentException) { null } diff --git a/app/src/main/kotlin/org/gameyfin/app/core/SetupDataLoader.kt b/app/src/main/kotlin/org/gameyfin/app/core/SetupDataLoader.kt index 178d8fa..f4e5f1c 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/SetupDataLoader.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/SetupDataLoader.kt @@ -33,11 +33,14 @@ class SetupDataLoader( val protocol = if (env.getProperty("server.ssl.key-store") != null) "https" else "http" val rawAppUrl = env.getProperty("app.url") + + @Suppress("HttpUrlsUsage") val appUrl = when { rawAppUrl.isNullOrBlank() -> null rawAppUrl.startsWith("http://") || rawAppUrl.startsWith("https://") -> rawAppUrl else -> "$protocol://$rawAppUrl" } + val setupUrl = appUrl ?: "${protocol}://${InetAddress.getLocalHost().hostName}:${env.getProperty("server.port")}/setup" log.info { "Visit $setupUrl to complete the setup" } diff --git a/app/src/main/kotlin/org/gameyfin/app/core/config/JacksonConfig.kt b/app/src/main/kotlin/org/gameyfin/app/core/config/JacksonConfig.kt index 166117b..510a52d 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/config/JacksonConfig.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/config/JacksonConfig.kt @@ -1,13 +1,16 @@ package org.gameyfin.app.core.config -import com.fasterxml.jackson.databind.SerializationFeature -import com.fasterxml.jackson.databind.module.SimpleModule -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import com.vaadin.hilla.EndpointController.ENDPOINT_MAPPER_FACTORY_BEAN_QUALIFIER +import com.vaadin.hilla.parser.jackson.ByteArrayModule +import com.vaadin.hilla.parser.jackson.JacksonObjectMapperFactory import org.gameyfin.app.core.serialization.* import org.gameyfin.pluginapi.gamemetadata.* import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration -import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder +import tools.jackson.databind.DeserializationFeature +import tools.jackson.databind.json.JsonMapper +import tools.jackson.databind.module.SimpleModule + /** * Jackson configuration for custom serializers and deserializers. @@ -15,14 +18,21 @@ import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder @Configuration class JacksonConfig { - - @Bean - fun objectMapperCustomizer(): Jackson2ObjectMapperBuilder { - return Jackson2ObjectMapperBuilder() - .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) - .modulesToInstall(JavaTimeModule(), displayableEnumModule()) + @Bean(ENDPOINT_MAPPER_FACTORY_BEAN_QUALIFIER) + fun jsonMapperFactory(): JacksonObjectMapperFactory { + return JacksonObjectMapperFactory { + JsonMapper.builder() + // Default Hilla options + .addModule(ByteArrayModule()) + .configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false) + .enable(DeserializationFeature.ACCEPT_FLOAT_AS_INT) + // Custom modules + .addModule(displayableEnumModule()) + .build() + } } + fun displayableEnumModule(): SimpleModule { val module = SimpleModule("DisplayableEnumModule") diff --git a/app/src/main/kotlin/org/gameyfin/app/core/config/JpaConfiguration.kt b/app/src/main/kotlin/org/gameyfin/app/core/config/JpaConfiguration.kt index a6f86e4..d781090 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/config/JpaConfiguration.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/config/JpaConfiguration.kt @@ -2,7 +2,7 @@ package org.gameyfin.app.core.config import org.gameyfin.app.core.interceptors.EntityUpdateInterceptor import org.hibernate.cfg.AvailableSettings -import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer +import org.springframework.boot.hibernate.autoconfigure.HibernatePropertiesCustomizer import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration diff --git a/app/src/main/kotlin/org/gameyfin/app/core/config/TomcatConfig.kt b/app/src/main/kotlin/org/gameyfin/app/core/config/TomcatConfig.kt deleted file mode 100644 index 4c47512..0000000 --- a/app/src/main/kotlin/org/gameyfin/app/core/config/TomcatConfig.kt +++ /dev/null @@ -1,50 +0,0 @@ -package org.gameyfin.app.core.config - -import io.github.oshai.kotlinlogging.KotlinLogging -import org.apache.coyote.ProtocolHandler -import org.apache.coyote.http11.AbstractHttp11Protocol -import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration - -/** - * Tomcat configuration to optimize for concurrent connections - * and prevent download operations from blocking the server. - */ -@Configuration -class TomcatConfig { - - companion object { - private val log = KotlinLogging.logger { } - } - - @Bean - fun protocolHandlerCustomizer(): TomcatProtocolHandlerCustomizer<*> { - return TomcatProtocolHandlerCustomizer { protocolHandler: ProtocolHandler -> - if (protocolHandler is AbstractHttp11Protocol<*>) { - // Increase max connections to handle more concurrent users - protocolHandler.maxConnections = 10000 - - // Increase max threads to handle more concurrent requests - protocolHandler.maxThreads = 200 - - // Set minimum spare threads - protocolHandler.minSpareThreads = 10 - - // Set connection timeout (20 seconds) - protocolHandler.connectionTimeout = 20000 - - // Keep alive settings to reuse connections - protocolHandler.keepAliveTimeout = 60000 - protocolHandler.maxKeepAliveRequests = 100 - - log.debug { - "Configured Tomcat connector: maxConnections=${protocolHandler.maxConnections}, " + - "maxThreads=${protocolHandler.maxThreads}, " + - "minSpareThreads=${protocolHandler.minSpareThreads}" - } - } - } - } -} - diff --git a/app/src/main/kotlin/org/gameyfin/app/core/download/bandwidth/BandwidthMonitoringEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/core/download/bandwidth/BandwidthMonitoringEndpoint.kt index 15cbd68..a85e248 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/download/bandwidth/BandwidthMonitoringEndpoint.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/download/bandwidth/BandwidthMonitoringEndpoint.kt @@ -1,7 +1,6 @@ package org.gameyfin.app.core.download.bandwidth import com.vaadin.hilla.Endpoint -import io.github.oshai.kotlinlogging.KotlinLogging import jakarta.annotation.security.PermitAll import jakarta.annotation.security.RolesAllowed import org.gameyfin.app.core.Role @@ -17,11 +16,6 @@ import reactor.core.publisher.Flux class BandwidthMonitoringEndpoint( private val bandwidthMonitoringService: BandwidthMonitoringService ) { - - companion object { - private val log = KotlinLogging.logger {} - } - @PermitAll fun subscribe(): Flux>> { return if (isCurrentUserAdmin()) BandwidthMonitoringService.subscribe() diff --git a/app/src/main/kotlin/org/gameyfin/app/core/filesystem/FilesystemService.kt b/app/src/main/kotlin/org/gameyfin/app/core/filesystem/FilesystemService.kt index 2077a94..7502a86 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/filesystem/FilesystemService.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/filesystem/FilesystemService.kt @@ -29,7 +29,7 @@ class FilesystemService( * @return A list of FileDto objects representing the files and directories. */ fun listContents(path: String?): List { - if (path == null || path.isEmpty()) { + if (path.isNullOrEmpty()) { val roots = FileSystems.getDefault().rootDirectories.toList() if (getHostOperatingSystem() == OperatingSystemType.WINDOWS) return roots.map { @@ -145,7 +145,7 @@ class FilesystemService( if (file.isFile) { file.length() } else if (file.isDirectory) { - File(path).walkTopDown().filter { it.isFile }.map { it.length() }.sum() + File(path).walkTopDown().filter { it.isFile }.sumOf { it.length() } } else { 0L } diff --git a/app/src/main/kotlin/org/gameyfin/app/core/interceptors/EntityUpdateInterceptor.kt b/app/src/main/kotlin/org/gameyfin/app/core/interceptors/EntityUpdateInterceptor.kt index 4bd7f1f..b32c581 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/interceptors/EntityUpdateInterceptor.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/interceptors/EntityUpdateInterceptor.kt @@ -12,7 +12,7 @@ import org.hibernate.type.Type import org.springframework.stereotype.Component @Component -class EntityUpdateInterceptor() : Interceptor { +class EntityUpdateInterceptor : Interceptor { override fun onFlushDirty( entity: Any?, diff --git a/app/src/main/kotlin/org/gameyfin/app/core/logging/dto/LogConfigDto.kt b/app/src/main/kotlin/org/gameyfin/app/core/logging/dto/LogConfigDto.kt deleted file mode 100644 index 4d89ccd..0000000 --- a/app/src/main/kotlin/org/gameyfin/app/core/logging/dto/LogConfigDto.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.gameyfin.app.core.logging.dto - -import org.springframework.boot.logging.LogLevel - -data class LogConfigDto( - val logFolder: String, - val maxHistoryDays: Int, - val logLevel: LogLevel -) diff --git a/app/src/main/kotlin/org/gameyfin/app/core/plugins/PluginService.kt b/app/src/main/kotlin/org/gameyfin/app/core/plugins/PluginService.kt index f99418e..45ebb7b 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/plugins/PluginService.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/plugins/PluginService.kt @@ -136,7 +136,7 @@ class PluginService( fun getConfig(pluginWrapper: PluginWrapper): Map { log.debug { "Getting config for plugin ${pluginWrapper.pluginId}" } - return pluginConfigRepository.findAllById_PluginId(pluginWrapper.pluginId).associate { it.id.key to it.value } + return pluginConfigRepository.findAllByPluginId(pluginWrapper.pluginId).associate { it.id.key to it.value } } fun updateConfig(pluginId: String, config: Map) { diff --git a/app/src/main/kotlin/org/gameyfin/app/core/plugins/config/PluginConfigRepository.kt b/app/src/main/kotlin/org/gameyfin/app/core/plugins/config/PluginConfigRepository.kt index e64b79e..f968a92 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/plugins/config/PluginConfigRepository.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/plugins/config/PluginConfigRepository.kt @@ -1,8 +1,10 @@ package org.gameyfin.app.core.plugins.config import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query interface PluginConfigRepository : JpaRepository { - fun findAllById_PluginId(pluginId: String): List - fun findById_PluginIdAndId_Key(pluginId: String, key: String): PluginConfigEntry? + + @Query("SELECT p FROM PluginConfigEntry p WHERE p.id.pluginId = :pluginId") + fun findAllByPluginId(pluginId: String): List } \ No newline at end of file diff --git a/app/src/main/kotlin/org/gameyfin/app/core/plugins/management/DatabasePluginStatusProvider.kt b/app/src/main/kotlin/org/gameyfin/app/core/plugins/management/DatabasePluginStatusProvider.kt index a827ae9..bf336c6 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/plugins/management/DatabasePluginStatusProvider.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/plugins/management/DatabasePluginStatusProvider.kt @@ -10,9 +10,7 @@ class DatabasePluginStatusProvider( ) : PluginStatusProvider { override fun isPluginDisabled(pluginId: String): Boolean { - val pluginManagement = pluginManagementRepository.findByIdOrNull(pluginId) - - if (pluginManagement == null) return true + val pluginManagement = pluginManagementRepository.findByIdOrNull(pluginId) ?: return true return !pluginManagement.enabled } diff --git a/app/src/main/kotlin/org/gameyfin/app/core/plugins/management/GameyfinExtensionFinder.kt b/app/src/main/kotlin/org/gameyfin/app/core/plugins/management/GameyfinExtensionFinder.kt index 36dceb6..9d16a0f 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/plugins/management/GameyfinExtensionFinder.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/plugins/management/GameyfinExtensionFinder.kt @@ -3,10 +3,10 @@ package org.gameyfin.app.core.plugins.management import io.github.oshai.kotlinlogging.KotlinLogging import org.pf4j.ExtensionDescriptor import org.pf4j.ExtensionWrapper -import org.pf4j.LegacyExtensionFinder +import org.pf4j.IndexedExtensionFinder import org.pf4j.PluginManager -class GameyfinExtensionFinder(pluginManager: PluginManager) : LegacyExtensionFinder(pluginManager) { +class GameyfinExtensionFinder(pluginManager: PluginManager) : IndexedExtensionFinder(pluginManager) { companion object { private val log = KotlinLogging.logger { } } @@ -27,7 +27,7 @@ class GameyfinExtensionFinder(pluginManager: PluginManager) : LegacyExtensionFin } val classLoader = - if (pluginId != null) pluginManager.getPluginClassLoader(pluginId) else javaClass.getClassLoader() + if (pluginId != null) pluginManager.getPluginClassLoader(pluginId) else javaClass.classLoader for (className in classNames) { try { diff --git a/app/src/main/kotlin/org/gameyfin/app/core/plugins/management/GameyfinManifestPluginDescriptorFinder.kt b/app/src/main/kotlin/org/gameyfin/app/core/plugins/management/GameyfinManifestPluginDescriptorFinder.kt index 6b403d1..37b3be3 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/plugins/management/GameyfinManifestPluginDescriptorFinder.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/plugins/management/GameyfinManifestPluginDescriptorFinder.kt @@ -3,7 +3,7 @@ package org.gameyfin.app.core.plugins.management import org.pf4j.ManifestPluginDescriptorFinder import java.util.jar.Manifest -class GameyfinManifestPluginDescriptorFinder() : ManifestPluginDescriptorFinder() { +class GameyfinManifestPluginDescriptorFinder : ManifestPluginDescriptorFinder() { companion object { const val PLUGIN_NAME: String = "Plugin-Name" diff --git a/app/src/main/kotlin/org/gameyfin/app/core/plugins/management/GameyfinPluginManager.kt b/app/src/main/kotlin/org/gameyfin/app/core/plugins/management/GameyfinPluginManager.kt index a65ac79..3de55c1 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/plugins/management/GameyfinPluginManager.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/plugins/management/GameyfinPluginManager.kt @@ -253,7 +253,7 @@ class GameyfinPluginManager( } private fun getConfig(pluginId: String): Map { - return pluginConfigRepository.findAllById_PluginId(pluginId).associate { it.id.key to it.value } + return pluginConfigRepository.findAllByPluginId(pluginId).associate { it.id.key to it.value } } private fun loadPluginSignaturePublicKey(): PublicKey { diff --git a/app/src/main/kotlin/org/gameyfin/app/core/security/AppKeyValidator.kt b/app/src/main/kotlin/org/gameyfin/app/core/security/AppKeyValidator.kt index cd186d8..dbea900 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/security/AppKeyValidator.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/security/AppKeyValidator.kt @@ -12,7 +12,7 @@ class AppKeyValidator : CommandLineRunner { val log = KotlinLogging.logger {} } - override fun run(vararg args: String?) { + override fun run(vararg args: String) { val base64Key = System.getenv("APP_KEY") if (!hasValidAppKey(base64Key)) exitProcess(1) } diff --git a/app/src/main/kotlin/org/gameyfin/app/core/security/CustomAuthenticationEntryPoint.kt b/app/src/main/kotlin/org/gameyfin/app/core/security/CustomAuthenticationEntryPoint.kt index 560ba1a..18d7fe3 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/security/CustomAuthenticationEntryPoint.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/security/CustomAuthenticationEntryPoint.kt @@ -11,7 +11,7 @@ class CustomAuthenticationEntryPoint : AuthenticationEntryPoint { override fun commence( request: HttpServletRequest, response: HttpServletResponse, - authException: AuthenticationException? + authException: AuthenticationException ) { if (request.getParameter("direct") == "1") { response.sendRedirect("/login") diff --git a/app/src/main/kotlin/org/gameyfin/app/core/security/DynamicPublicAccessAuthorizationManager.kt b/app/src/main/kotlin/org/gameyfin/app/core/security/DynamicPublicAccessAuthorizationManager.kt index ec485bf..aa799fa 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/security/DynamicPublicAccessAuthorizationManager.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/security/DynamicPublicAccessAuthorizationManager.kt @@ -12,23 +12,13 @@ import java.util.function.Supplier class DynamicPublicAccessAuthorizationManager( private val config: ConfigService ) : AuthorizationManager { - - @Deprecated("Deprecated in superclass") - override fun check( - authentication: Supplier?, - `object`: RequestAuthorizationContext? - ): AuthorizationDecision { - val auth = authentication?.get() - val allow = (auth?.isAuthenticated == true && auth.principal != "anonymousUser") || + override fun authorize( + authentication: Supplier, + `object`: RequestAuthorizationContext + ): AuthorizationResult { + val auth = authentication.get() + val allow = (auth.isAuthenticated && auth.principal != "anonymousUser") || config.get(ConfigProperties.Security.AllowPublicAccess) == true return AuthorizationDecision(allow) } - - override fun authorize( - authentication: Supplier?, - `object`: RequestAuthorizationContext? - ): AuthorizationResult { - @Suppress("DEPRECATION") - return check(authentication, `object`) - } } \ No newline at end of file diff --git a/app/src/main/kotlin/org/gameyfin/app/core/security/EncryptionMapConverter.kt b/app/src/main/kotlin/org/gameyfin/app/core/security/EncryptionMapConverter.kt index 2e78223..25d9bec 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/security/EncryptionMapConverter.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/security/EncryptionMapConverter.kt @@ -1,8 +1,8 @@ package org.gameyfin.app.core.security -import com.fasterxml.jackson.databind.ObjectMapper import jakarta.persistence.AttributeConverter import jakarta.persistence.Converter +import tools.jackson.databind.ObjectMapper @Converter class EncryptionMapConverter : AttributeConverter, String> { diff --git a/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt b/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt index 1fd47b1..f7d6949 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt @@ -1,6 +1,5 @@ package org.gameyfin.app.core.security -import com.vaadin.flow.spring.security.VaadinAwareSecurityContextHolderStrategyConfiguration import com.vaadin.flow.spring.security.VaadinSecurityConfigurer import com.vaadin.hilla.route.RouteUtil import org.gameyfin.app.config.ConfigProperties @@ -8,7 +7,6 @@ import org.gameyfin.app.config.ConfigService import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Conditional import org.springframework.context.annotation.Configuration -import org.springframework.context.annotation.Import import org.springframework.core.env.Environment import org.springframework.http.HttpStatus import org.springframework.security.config.annotation.web.builders.HttpSecurity @@ -25,9 +23,6 @@ import org.springframework.security.web.authentication.logout.HttpStatusReturnin @Configuration @EnableWebSecurity -@Import( - VaadinAwareSecurityContextHolderStrategyConfiguration::class -) class SecurityConfig( private val environment: Environment, private val config: ConfigService, @@ -41,6 +36,31 @@ class SecurityConfig( @Bean fun filterChain(http: HttpSecurity, routeUtil: RouteUtil): SecurityFilterChain { + // Apply Vaadin configuration first to properly configure CSRF and request matchers + if (config.get(ConfigProperties.SSO.OIDC.Enabled) == true) { + http.with(VaadinSecurityConfigurer.vaadin()) { configurer -> + // Redirect to SSO provider on logout + configurer.loginView("/login", config.get(ConfigProperties.SSO.OIDC.LogoutUrl)) + } + + // Use custom success handler to handle user registration + http.oauth2Login { oauth2Login -> + oauth2Login.successHandler(ssoAuthenticationSuccessHandler) + } + // Prevent unnecessary redirects + http.logout { logout -> logout.logoutSuccessHandler((HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK))) } + + // Custom authentication entry point to support SSO and direct login + http.exceptionHandling { exceptionHandling -> + exceptionHandling.authenticationEntryPoint(CustomAuthenticationEntryPoint()) + } + } else { + // Use default Vaadin login URLs + http.with(VaadinSecurityConfigurer.vaadin()) { configurer -> + configurer.loginView("/login") + } + } + http.authorizeHttpRequests { auth -> // Set default security policy that permits Hilla internal requests and denies all other auth.requestMatchers(routeUtil::isRouteAllowed).permitAll() @@ -56,6 +76,13 @@ class SecurityConfig( "/favicon.ico", "/favicon.svg" ).permitAll() + // Client-side SPA routes - these need to pass through to serve index.html + // Authentication will be handled by Hilla on the client side + .requestMatchers( + "/administration/**", + "/settings/**", + "/collection/**" + ).permitAll() // Dynamic public access for certain endpoints .requestMatchers( "/", @@ -78,30 +105,6 @@ class SecurityConfig( http.cors { cors -> cors.disable() } - if (config.get(ConfigProperties.SSO.OIDC.Enabled) == true) { - - http.with(VaadinSecurityConfigurer.vaadin()) { configurer -> - // Redirect to SSO provider on logout - configurer.loginView("/login", config.get(ConfigProperties.SSO.OIDC.LogoutUrl)) - } - - // Use custom success handler to handle user registration - http.oauth2Login { oauth2Login -> - oauth2Login.successHandler(ssoAuthenticationSuccessHandler) - } - // Prevent unnecessary redirects - http.logout { logout -> logout.logoutSuccessHandler((HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK))) } - - // Custom authentication entry point to support SSO and direct login - http.exceptionHandling { exceptionHandling -> - exceptionHandling.authenticationEntryPoint(CustomAuthenticationEntryPoint()) - } - } else { - // Use default Vaadin login URLs - http.with(VaadinSecurityConfigurer.vaadin()) { configurer -> - configurer.loginView("/login") - } - } if ("dev" in environment.activeProfiles) { http.authorizeHttpRequests { auth -> auth.requestMatchers("/h2-console/**").permitAll() } diff --git a/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityUtils.kt b/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityUtils.kt index 385162b..8831089 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityUtils.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityUtils.kt @@ -13,5 +13,5 @@ fun isCurrentUserAdmin(): Boolean { } fun Authentication.isAdmin(): Boolean { - return this.authorities?.any { it.authority == Role.Names.ADMIN || it.authority == Role.Names.SUPERADMIN } ?: false + return this.authorities.any { it.authority == Role.Names.ADMIN || it.authority == Role.Names.SUPERADMIN } } \ No newline at end of file diff --git a/app/src/main/kotlin/org/gameyfin/app/core/security/SsoAuthenticationSuccessHandler.kt b/app/src/main/kotlin/org/gameyfin/app/core/security/SsoAuthenticationSuccessHandler.kt index 906c1e6..cfee1ab 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/security/SsoAuthenticationSuccessHandler.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/security/SsoAuthenticationSuccessHandler.kt @@ -23,7 +23,7 @@ class SsoAuthenticationSuccessHandler( private val userService: UserService, private val roleService: RoleService, private val config: ConfigService, - private val roleHierarchy: RoleHierarchy, + roleHierarchy: RoleHierarchy, ) : AuthenticationSuccessHandler { private val authoritiesMapper = RoleHierarchyAuthoritiesMapper(roleHierarchy) @@ -77,9 +77,13 @@ class SsoAuthenticationSuccessHandler( // Update SecurityContext with expanded authorities through RoleHierarchy val mappedAuthorities = authoritiesMapper.mapAuthorities(grantedAuthorities) - val newAuth = - UsernamePasswordAuthenticationToken(authentication.principal, authentication.credentials, mappedAuthorities) - SecurityContextHolder.getContext().authentication = newAuth + val authPrincipal = authentication.principal + val authCredentials = authentication.credentials + + if (authPrincipal != null && authCredentials != null) { + val newAuth = UsernamePasswordAuthenticationToken(authPrincipal, authCredentials, mappedAuthorities) + SecurityContextHolder.getContext().authentication = newAuth + } // Get the continue parameter from the request to redirect back to the original page val continueUrl = request.getParameter("continue") diff --git a/app/src/main/kotlin/org/gameyfin/app/core/security/SsoEnabledCondition.kt b/app/src/main/kotlin/org/gameyfin/app/core/security/SsoEnabledCondition.kt index af4ae64..0442997 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/security/SsoEnabledCondition.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/security/SsoEnabledCondition.kt @@ -1,6 +1,8 @@ package org.gameyfin.app.core.security +import io.github.oshai.kotlinlogging.KotlinLogging import org.gameyfin.app.config.ConfigProperties +import org.springframework.beans.factory.getBean import org.springframework.context.annotation.Condition import org.springframework.context.annotation.ConditionContext import org.springframework.core.env.Environment @@ -13,13 +15,24 @@ import java.sql.DriverManager * So we are rawdogging the database connection and query execution here. */ class SsoEnabledCondition : Condition { + + companion object { + private val log = KotlinLogging.logger { } + } + override fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean { try { - val environment = context.beanFactory!!.getBean(Environment::class.java); - val url = environment.getProperty("spring.datasource.url"); - val user = environment.getProperty("spring.datasource.username"); - val password = environment.getProperty("spring.datasource.password"); - val connection = DriverManager.getConnection(url, user, password); + val environment = context.beanFactory?.getBean() + + if (environment == null) { + log.warn { "Environment hasn't been loaded yet, cannot determine if SSO is enabled." } + return false + } + + val url = environment.getProperty("spring.datasource.url") + val user = environment.getProperty("spring.datasource.username") + val password = environment.getProperty("spring.datasource.password") + val connection = DriverManager.getConnection(url, user, password) connection.use { c -> val statement = c.prepareStatement("SELECT \"value\" FROM app_config WHERE \"key\" = ?") diff --git a/app/src/main/kotlin/org/gameyfin/app/core/serialization/ArrayDeserializer.kt b/app/src/main/kotlin/org/gameyfin/app/core/serialization/ArrayDeserializer.kt index db637c4..b22a387 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/serialization/ArrayDeserializer.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/serialization/ArrayDeserializer.kt @@ -1,18 +1,18 @@ package org.gameyfin.app.core.serialization -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonDeserializer -import com.fasterxml.jackson.databind.JsonNode +import tools.jackson.core.JsonParser +import tools.jackson.databind.DeserializationContext +import tools.jackson.databind.JsonNode +import tools.jackson.databind.ValueDeserializer import java.io.Serializable -class ArrayDeserializer : JsonDeserializer() { +class ArrayDeserializer : ValueDeserializer() { override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Serializable { - val node = p.codec.readTree(p) + val node = p.objectReadContext().readTree(p) return if (node.isArray) { - node.map { it.asText() }.toTypedArray() + node.map { it.asString() }.toTypedArray() } else { - p.codec.treeToValue(node, Serializable::class.java) + ctxt.readTreeAsValue(node, Serializable::class.java) } } } \ No newline at end of file diff --git a/app/src/main/kotlin/org/gameyfin/app/core/serialization/DisplayableSerializer.kt b/app/src/main/kotlin/org/gameyfin/app/core/serialization/DisplayableSerializer.kt index d8ba8f0..a4cf047 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/serialization/DisplayableSerializer.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/serialization/DisplayableSerializer.kt @@ -1,15 +1,16 @@ package org.gameyfin.app.core.serialization -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.databind.JsonSerializer -import com.fasterxml.jackson.databind.SerializerProvider +import tools.jackson.core.JsonGenerator +import tools.jackson.databind.SerializationContext +import tools.jackson.databind.ValueSerializer + /** * A generic Jackson serializer for enums that have a displayName property. * This serializer writes the displayName value instead of the enum constant name. */ -class DisplayableSerializer : JsonSerializer() { - override fun serialize(value: Any?, gen: JsonGenerator, serializers: SerializerProvider) { +class DisplayableSerializer : ValueSerializer() { + override fun serialize(value: Any?, gen: JsonGenerator, serializers: SerializationContext) { if (value == null) { return } diff --git a/app/src/main/kotlin/org/gameyfin/app/core/serialization/GameFeatureDeserializer.kt b/app/src/main/kotlin/org/gameyfin/app/core/serialization/GameFeatureDeserializer.kt index 4839e12..3cf41f8 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/serialization/GameFeatureDeserializer.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/serialization/GameFeatureDeserializer.kt @@ -1,17 +1,17 @@ package org.gameyfin.app.core.serialization -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonDeserializer import org.gameyfin.pluginapi.gamemetadata.GameFeature +import tools.jackson.core.JsonParser +import tools.jackson.databind.DeserializationContext +import tools.jackson.databind.ValueDeserializer /** * Jackson deserializer for GameFeature enum. * Deserializes JSON strings by matching against the GameFeature's displayName property. */ -class GameFeatureDeserializer : JsonDeserializer() { +class GameFeatureDeserializer : ValueDeserializer() { override fun deserialize(p: JsonParser, ctxt: DeserializationContext): GameFeature? { - val displayName = p.text ?: return null + val displayName = p.string ?: return null if (displayName.isEmpty()) { return null diff --git a/app/src/main/kotlin/org/gameyfin/app/core/serialization/GenreDeserializer.kt b/app/src/main/kotlin/org/gameyfin/app/core/serialization/GenreDeserializer.kt index 3ea84a7..546ae6a 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/serialization/GenreDeserializer.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/serialization/GenreDeserializer.kt @@ -1,17 +1,17 @@ package org.gameyfin.app.core.serialization -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonDeserializer import org.gameyfin.pluginapi.gamemetadata.Genre +import tools.jackson.core.JsonParser +import tools.jackson.databind.DeserializationContext +import tools.jackson.databind.ValueDeserializer /** * Jackson deserializer for Genre enum. * Deserializes JSON strings by matching against the Genre's displayName property. */ -class GenreDeserializer : JsonDeserializer() { +class GenreDeserializer : ValueDeserializer() { override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Genre? { - val displayName = p.text ?: return null + val displayName = p.string ?: return null if (displayName.isEmpty()) { return null diff --git a/app/src/main/kotlin/org/gameyfin/app/core/serialization/PlatformDeserializer.kt b/app/src/main/kotlin/org/gameyfin/app/core/serialization/PlatformDeserializer.kt index 440f80b..352d604 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/serialization/PlatformDeserializer.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/serialization/PlatformDeserializer.kt @@ -1,17 +1,17 @@ package org.gameyfin.app.core.serialization -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonDeserializer import org.gameyfin.pluginapi.gamemetadata.Platform +import tools.jackson.core.JsonParser +import tools.jackson.databind.DeserializationContext +import tools.jackson.databind.ValueDeserializer /** * Jackson deserializer for Platform enum. * Deserializes JSON strings by matching against the Platform's displayName property. */ -class PlatformDeserializer : JsonDeserializer() { +class PlatformDeserializer : ValueDeserializer() { override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Platform? { - val displayName = p.text ?: return null + val displayName = p.string ?: return null if (displayName.isEmpty()) { return null diff --git a/app/src/main/kotlin/org/gameyfin/app/core/serialization/PlayerPerspectiveDeserializer.kt b/app/src/main/kotlin/org/gameyfin/app/core/serialization/PlayerPerspectiveDeserializer.kt index b18959c..e8b8028 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/serialization/PlayerPerspectiveDeserializer.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/serialization/PlayerPerspectiveDeserializer.kt @@ -1,17 +1,17 @@ package org.gameyfin.app.core.serialization -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonDeserializer import org.gameyfin.pluginapi.gamemetadata.PlayerPerspective +import tools.jackson.core.JsonParser +import tools.jackson.databind.DeserializationContext +import tools.jackson.databind.ValueDeserializer /** * Jackson deserializer for PlayerPerspective enum. * Deserializes JSON strings by matching against the PlayerPerspective's displayName property. */ -class PlayerPerspectiveDeserializer : JsonDeserializer() { +class PlayerPerspectiveDeserializer : ValueDeserializer() { override fun deserialize(p: JsonParser, ctxt: DeserializationContext): PlayerPerspective? { - val displayName = p.text ?: return null + val displayName = p.string ?: return null if (displayName.isEmpty()) { return null diff --git a/app/src/main/kotlin/org/gameyfin/app/core/serialization/ThemeDeserializer.kt b/app/src/main/kotlin/org/gameyfin/app/core/serialization/ThemeDeserializer.kt index 21e77e6..d82ddcc 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/serialization/ThemeDeserializer.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/serialization/ThemeDeserializer.kt @@ -1,17 +1,17 @@ package org.gameyfin.app.core.serialization -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonDeserializer import org.gameyfin.pluginapi.gamemetadata.Theme +import tools.jackson.core.JsonParser +import tools.jackson.databind.DeserializationContext +import tools.jackson.databind.ValueDeserializer /** * Jackson deserializer for Theme enum. * Deserializes JSON strings by matching against the Theme's displayName property. */ -class ThemeDeserializer : JsonDeserializer() { +class ThemeDeserializer : ValueDeserializer() { override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Theme? { - val displayName = p.text ?: return null + val displayName = p.string ?: return null if (displayName.isEmpty()) { return null diff --git a/app/src/main/kotlin/org/gameyfin/app/core/token/Token.kt b/app/src/main/kotlin/org/gameyfin/app/core/token/Token.kt index c1d29b0..7e60b85 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/token/Token.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/token/Token.kt @@ -15,6 +15,10 @@ import kotlin.time.toJavaDuration @Entity class Token( @Id + @GeneratedValue(strategy = GenerationType.UUID) + val id: String? = null, + + @Column(unique = true, nullable = false) @Convert(converter = EncryptionConverter::class) val secret: String = UUID.randomUUID().toString(), diff --git a/app/src/main/kotlin/org/gameyfin/app/core/token/TokenTypeUserType.kt b/app/src/main/kotlin/org/gameyfin/app/core/token/TokenTypeUserType.kt index 7a858ee..69eee04 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/token/TokenTypeUserType.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/token/TokenTypeUserType.kt @@ -1,6 +1,6 @@ package org.gameyfin.app.core.token -import org.hibernate.engine.spi.SharedSessionContractImplementor +import org.hibernate.type.descriptor.WrapperOptions import org.hibernate.usertype.UserType import java.io.Serializable import java.sql.PreparedStatement @@ -25,8 +25,7 @@ class TokenTypeUserType : UserType { override fun nullSafeGet( rs: ResultSet, position: Int, - session: SharedSessionContractImplementor, - owner: Any? + options: WrapperOptions ): TokenType? { val key = rs.getString(position) ?: return null val tokenTypeClass = TokenType::class @@ -41,7 +40,7 @@ class TokenTypeUserType : UserType { st: PreparedStatement, value: TokenType?, index: Int, - session: SharedSessionContractImplementor + options: WrapperOptions ) { if (value == null) { st.setNull(index, Types.VARCHAR) diff --git a/app/src/main/kotlin/org/gameyfin/app/core/token/TokenValidationResult.kt b/app/src/main/kotlin/org/gameyfin/app/core/token/TokenValidationResult.kt index c9909f1..04372dc 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/token/TokenValidationResult.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/token/TokenValidationResult.kt @@ -1,5 +1,5 @@ package org.gameyfin.app.core.token -enum class TokenValidationResult() { +enum class TokenValidationResult { VALID, INVALID, EXPIRED } \ No newline at end of file diff --git a/app/src/main/kotlin/org/gameyfin/app/games/GameService.kt b/app/src/main/kotlin/org/gameyfin/app/games/GameService.kt index 19f01e6..64f4828 100644 --- a/app/src/main/kotlin/org/gameyfin/app/games/GameService.kt +++ b/app/src/main/kotlin/org/gameyfin/app/games/GameService.kt @@ -120,7 +120,7 @@ class GameService( imageService.downloadIfNew(it) } - game.images.map { + game.images.forEach { imageService.downloadIfNew(it) } } catch (e: Exception) { @@ -647,7 +647,7 @@ class GameService( // (Optional) Step 0: Extract title from filename using regex if (config.get(ConfigProperties.Libraries.Scan.ExtractTitleUsingRegex) == true) { val regexString = config.get(ConfigProperties.Libraries.Scan.TitleExtractionRegex) - if (regexString != null && regexString.isNotEmpty()) { + if (!regexString.isNullOrEmpty()) { try { val regex = Regex(regexString) val originalQuery = query diff --git a/app/src/main/kotlin/org/gameyfin/app/games/entities/Game.kt b/app/src/main/kotlin/org/gameyfin/app/games/entities/Game.kt index 6cc67db..7050ccb 100644 --- a/app/src/main/kotlin/org/gameyfin/app/games/entities/Game.kt +++ b/app/src/main/kotlin/org/gameyfin/app/games/entities/Game.kt @@ -32,6 +32,7 @@ class Game( @ElementCollection(targetClass = Platform::class, fetch = FetchType.EAGER) @Enumerated(EnumType.STRING) + @Column(nullable = false) var platforms: MutableList = mutableListOf(), var title: String? = null, diff --git a/app/src/main/kotlin/org/gameyfin/app/games/repositories/ImageContentStore.kt b/app/src/main/kotlin/org/gameyfin/app/games/repositories/ImageContentStore.kt deleted file mode 100644 index 5f5f271..0000000 --- a/app/src/main/kotlin/org/gameyfin/app/games/repositories/ImageContentStore.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.gameyfin.app.games.repositories - -import org.gameyfin.app.media.Image -import org.springframework.content.commons.store.ContentStore -import org.springframework.stereotype.Repository - -@Repository -interface ImageContentStore : ContentStore \ No newline at end of file diff --git a/app/src/main/kotlin/org/gameyfin/app/libraries/entities/IgnoredPath.kt b/app/src/main/kotlin/org/gameyfin/app/libraries/entities/IgnoredPath.kt index 185bc2a..1afac88 100644 --- a/app/src/main/kotlin/org/gameyfin/app/libraries/entities/IgnoredPath.kt +++ b/app/src/main/kotlin/org/gameyfin/app/libraries/entities/IgnoredPath.kt @@ -9,9 +9,12 @@ class IgnoredPath( @Id @GeneratedValue(strategy = GenerationType.AUTO) var id: Long? = null, + @Column(unique = true, nullable = false, length = 1024) val path: String, + @OneToOne(cascade = [CascadeType.ALL], orphanRemoval = true, fetch = FetchType.EAGER) + @JoinColumn(nullable = false) val source: IgnoredPathSource ) { fun getType(): IgnoredPathSourceType { diff --git a/app/src/main/kotlin/org/gameyfin/app/libraries/entities/Library.kt b/app/src/main/kotlin/org/gameyfin/app/libraries/entities/Library.kt index 311aa70..a7b8faf 100644 --- a/app/src/main/kotlin/org/gameyfin/app/libraries/entities/Library.kt +++ b/app/src/main/kotlin/org/gameyfin/app/libraries/entities/Library.kt @@ -29,6 +29,7 @@ class Library( @ElementCollection(targetClass = Platform::class, fetch = FetchType.EAGER) @Enumerated(EnumType.STRING) + @Column(nullable = false) var platforms: MutableList = ArrayList(), @OneToMany(mappedBy = "library", fetch = FetchType.EAGER, orphanRemoval = true) diff --git a/app/src/main/kotlin/org/gameyfin/app/media/FileStorageService.kt b/app/src/main/kotlin/org/gameyfin/app/media/FileStorageService.kt new file mode 100644 index 0000000..9d17826 --- /dev/null +++ b/app/src/main/kotlin/org/gameyfin/app/media/FileStorageService.kt @@ -0,0 +1,90 @@ +package org.gameyfin.app.media + +import io.github.oshai.kotlinlogging.KotlinLogging +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Service +import java.io.InputStream +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardCopyOption +import java.util.* +import kotlin.io.path.createDirectories +import kotlin.io.path.deleteExisting +import kotlin.io.path.exists + +private val logger = KotlinLogging.logger {} + +/** + * Service for handling file storage operations. + * Files are stored in the filesystem under the specified root directory. + * The content ID is a UUID string used as the filename. + * Files are stored without extensions; MIME type is managed separately. + * + * Note: This is a drop-in replacement for Spring Content's filesystem storage (which has been discontinued). + */ +@Service +class FileStorageService( + @param:Value($$"${spring.content.fs.filesystem-root:./data/}") private val storageRoot: String +) { + private val rootPath: Path = Path.of(storageRoot) + + init { + // Ensure storage directory exists + if (!rootPath.exists()) { + rootPath.createDirectories() + logger.info { "Created file storage directory: $rootPath" } + } + } + + /** + * Stores a file and returns the generated content ID (UUID). + */ + fun saveFile(inputStream: InputStream): String { + val contentId = UUID.randomUUID().toString() + val filePath = rootPath.resolve(contentId) + + inputStream.use { input -> + Files.copy(input, filePath, StandardCopyOption.REPLACE_EXISTING) + } + + logger.debug { "Saved file with contentId: $contentId" } + return contentId + } + + /** + * Retrieves a file by content ID. + * Returns null if the file doesn't exist. + */ + fun getFile(contentId: String?): InputStream? { + if (contentId == null) return null + + val filePath = rootPath.resolve(contentId) + return if (filePath.exists()) { + Files.newInputStream(filePath) + } else { + logger.warn { "File not found for contentId: $contentId" } + null + } + } + + /** + * Deletes a file by content ID. + */ + fun deleteFile(contentId: String?) { + if (contentId == null) return + + val filePath = rootPath.resolve(contentId) + if (filePath.exists()) { + filePath.deleteExisting() + logger.debug { "Deleted file with contentId: $contentId" } + } + } + + /** + * Checks if a file exists for the given content ID. + */ + fun fileExists(contentId: String?): Boolean { + if (contentId == null) return false + return rootPath.resolve(contentId).exists() + } +} diff --git a/app/src/main/kotlin/org/gameyfin/app/media/Image.kt b/app/src/main/kotlin/org/gameyfin/app/media/Image.kt index a049348..be72ce9 100644 --- a/app/src/main/kotlin/org/gameyfin/app/media/Image.kt +++ b/app/src/main/kotlin/org/gameyfin/app/media/Image.kt @@ -1,9 +1,6 @@ package org.gameyfin.app.media import jakarta.persistence.* -import org.springframework.content.commons.annotations.ContentId -import org.springframework.content.commons.annotations.ContentLength -import org.springframework.content.commons.annotations.MimeType @Entity class Image( @@ -16,13 +13,10 @@ class Image( val type: ImageType, - @ContentId var contentId: String? = null, - @ContentLength var contentLength: Long? = null, - @MimeType var mimeType: String? = null, var blurhash: String? = null diff --git a/app/src/main/kotlin/org/gameyfin/app/media/ImageEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/media/ImageEndpoint.kt index 5167304..c6501cf 100644 --- a/app/src/main/kotlin/org/gameyfin/app/media/ImageEndpoint.kt +++ b/app/src/main/kotlin/org/gameyfin/app/media/ImageEndpoint.kt @@ -28,22 +28,22 @@ class ImageEndpoint( ) { @GetMapping("/screenshot/{id}") - fun getScreenshot(@PathVariable("id") id: Long): ResponseEntity? { + fun getScreenshot(@PathVariable id: Long): ResponseEntity? { return getImageContent(id) } @GetMapping("/cover/{id}") - fun getCover(@PathVariable("id") id: Long): ResponseEntity? { + fun getCover(@PathVariable id: Long): ResponseEntity? { return getImageContent(id) } @GetMapping("/header/{id}") - fun getHeader(@PathVariable("id") id: Long): ResponseEntity? { + fun getHeader(@PathVariable id: Long): ResponseEntity? { return getImageContent(id) } - @GetMapping("/plugins/{id}/logo") - fun getPluginLogo(@PathVariable("id") pluginId: String): ResponseEntity? { + @GetMapping("/plugins/{pluginId}/logo") + fun getPluginLogo(@PathVariable pluginId: String): ResponseEntity? { val logo = pluginService.getLogo(pluginId) return Utils.inputStreamToResponseEntity(logo) } diff --git a/app/src/main/kotlin/org/gameyfin/app/media/ImageService.kt b/app/src/main/kotlin/org/gameyfin/app/media/ImageService.kt index c2bd42b..f02b24b 100644 --- a/app/src/main/kotlin/org/gameyfin/app/media/ImageService.kt +++ b/app/src/main/kotlin/org/gameyfin/app/media/ImageService.kt @@ -8,7 +8,6 @@ import org.gameyfin.app.core.events.GameUpdatedEvent import org.gameyfin.app.core.events.UserDeletedEvent import org.gameyfin.app.core.events.UserUpdatedEvent import org.gameyfin.app.games.repositories.GameRepository -import org.gameyfin.app.games.repositories.ImageContentStore import org.gameyfin.app.games.repositories.ImageRepository import org.gameyfin.app.users.persistence.UserRepository import org.springframework.dao.DataIntegrityViolationException @@ -28,7 +27,7 @@ import javax.imageio.ImageIO @Service class ImageService( private val imageRepository: ImageRepository, - private val imageContentStore: ImageContentStore, + private val fileStorageService: FileStorageService, private val gameRepository: GameRepository, private val userRepository: UserRepository ) { @@ -39,6 +38,7 @@ class ImageService( * Scale down image for faster blurhash calculation. * Blurhash doesn't need full resolution - 100px width is plenty for a good blur. */ + @Suppress("DuplicatedCode") fun scaleImageForBlurhash(original: BufferedImage, maxWidth: Int = 100): BufferedImage { val originalWidth = original.width val originalHeight = original.height @@ -49,10 +49,9 @@ class ImageService( } val scale = maxWidth.toDouble() / originalWidth - val targetWidth = maxWidth val targetHeight = (originalHeight * scale).toInt() - val scaled = BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB) + val scaled = BufferedImage(maxWidth, targetHeight, BufferedImage.TYPE_INT_RGB) val g2d = scaled.createGraphics() // Use fast scaling for blurhash - quality doesn't matter much for a blur @@ -60,7 +59,7 @@ class ImageService( g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED) g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF) - g2d.drawImage(original, 0, 0, targetWidth, targetHeight, null) + g2d.drawImage(original, 0, 0, maxWidth, targetHeight, null) g2d.dispose() return scaled @@ -152,7 +151,6 @@ class ImageService( // If the existing image has valid content we can just associate it instead of downloading again if (existingImageHasValidContent && existingImage.contentId != null) { // Associate existing content with the current image entity reference - imageContentStore.associate(image, existingImage.contentId) image.contentId = existingImage.contentId image.contentLength = existingImage.contentLength image.mimeType = existingImage.mimeType @@ -162,19 +160,7 @@ class ImageService( // If no existing image or existing image has no valid content, download it TikaInputStream.get { URI.create(image.originalUrl).toURL().openStream() }.use { input -> image.mimeType = tika.detect(input) - - // Read the input stream into a byte array so we can use it twice - val imageBytes = input.readBytes() - - // Calculate blurhash - ByteArrayInputStream(imageBytes).use { blurhashStream -> - image.blurhash = calculateBlurhash(blurhashStream) - } - - // Store content - ByteArrayInputStream(imageBytes).use { contentStream -> - imageContentStore.setContent(image, contentStream) - } + processImageContent(image, input) } // Save or update the image to ensure it's persisted @@ -187,21 +173,7 @@ class ImageService( fun createFromInputStream(type: ImageType, content: InputStream, mimeType: String): Image { val image = Image(type = type, mimeType = mimeType) - - // Read the input stream into a byte array so we can use it twice - val imageBytes = content.readBytes() - - // Calculate blurhash - ByteArrayInputStream(imageBytes).use { blurhashStream -> - image.blurhash = calculateBlurhash(blurhashStream) - } - - // Store content - ByteArrayInputStream(imageBytes).use { contentStream -> - imageContentStore.setContent(image, contentStream) - } - - // Save with blurhash + processImageContent(image, content) return imageRepository.save(image) } @@ -210,8 +182,7 @@ class ImageService( } fun getFileContent(image: Image): InputStream? { - return imageContentStore.getContent(image) - + return fileStorageService.getFile(image.contentId) } fun deleteImageIfUnused(image: Image) { @@ -221,13 +192,30 @@ class ImageService( if (!isImageStillInUse) { imageRepository.delete(image) - imageContentStore.unsetContent(image) + fileStorageService.deleteFile(image.contentId) } } fun updateFileContent(image: Image, content: InputStream, mimeType: String? = null): Image { mimeType?.let { image.mimeType = it } + // Delete old file if it exists + image.contentId?.let { fileStorageService.deleteFile(it) } + + // Process and store new content + processImageContent(image, content) + + return imageRepository.save(image) + } + + private fun imageHasValidContent(image: Image): Boolean { + return image.contentId != null + && fileStorageService.fileExists(image.contentId) + && image.contentLength != null + && image.contentLength!! > 0 + } + + private fun processImageContent(image: Image, content: InputStream) { // Read the input stream into a byte array so we can use it twice val imageBytes = content.readBytes() @@ -238,16 +226,9 @@ class ImageService( // Store content ByteArrayInputStream(imageBytes).use { contentStream -> - imageContentStore.setContent(image, contentStream) + image.contentId = fileStorageService.saveFile(contentStream) + image.contentLength = imageBytes.size.toLong() } - - // Save with blurhash - return imageRepository.save(image) - } - - private fun imageHasValidContent(image: Image): Boolean { - val imageContent = imageContentStore.getContent(image) - return imageContent != null && image.contentLength != null && image.contentLength!! > 0 } private fun calculateBlurhash(inputStream: InputStream): String? { diff --git a/app/src/main/kotlin/org/gameyfin/app/messages/MessageService.kt b/app/src/main/kotlin/org/gameyfin/app/messages/MessageService.kt index bf277ea..bf08d33 100644 --- a/app/src/main/kotlin/org/gameyfin/app/messages/MessageService.kt +++ b/app/src/main/kotlin/org/gameyfin/app/messages/MessageService.kt @@ -8,6 +8,7 @@ import org.gameyfin.app.messages.providers.AbstractMessageProvider import org.gameyfin.app.messages.templates.MessageTemplateService import org.gameyfin.app.messages.templates.MessageTemplates import org.gameyfin.app.users.UserService +import org.springframework.beans.factory.getBeansOfType import org.springframework.context.ApplicationContext import org.springframework.context.event.EventListener import org.springframework.scheduling.annotation.Async @@ -27,7 +28,7 @@ class MessageService( get() = providers.any { it.enabled } private val providers: List - get() = applicationContext.getBeansOfType(AbstractMessageProvider::class.java).values.toList() + get() = applicationContext.getBeansOfType().values.toList() fun testCredentials(provider: String, credentials: Map): Boolean { val messageProvider = providers.find { it.providerKey == provider } diff --git a/app/src/main/kotlin/org/gameyfin/app/messages/providers/EmailMessageProvider.kt b/app/src/main/kotlin/org/gameyfin/app/messages/providers/EmailMessageProvider.kt index 459cde2..dca60ff 100644 --- a/app/src/main/kotlin/org/gameyfin/app/messages/providers/EmailMessageProvider.kt +++ b/app/src/main/kotlin/org/gameyfin/app/messages/providers/EmailMessageProvider.kt @@ -67,7 +67,7 @@ class EmailMessageProvider( val transport = session.getTransport("smtp") - try { + transport.use { transport -> transport.connect( credentials["host"] as String, credentials["port"] as Int, @@ -75,8 +75,6 @@ class EmailMessageProvider( credentials["password"] as String ) transport.sendMessage(mimeMessage, mimeMessage.allRecipients) - } finally { - transport.close() } } } \ No newline at end of file diff --git a/app/src/main/kotlin/org/gameyfin/app/system/SystemService.kt b/app/src/main/kotlin/org/gameyfin/app/system/SystemService.kt index a616e4d..a7e81b1 100644 --- a/app/src/main/kotlin/org/gameyfin/app/system/SystemService.kt +++ b/app/src/main/kotlin/org/gameyfin/app/system/SystemService.kt @@ -7,18 +7,7 @@ import org.springframework.stereotype.Service class SystemService( private val restartEndpoint: RestartEndpoint, ) { - - private var restartRequired = false; - fun restart() { restartEndpoint.restart() } - - fun setRestartRequired() { - restartRequired = true - } - - fun isRestartRequired(): Boolean { - return restartRequired - } } \ No newline at end of file diff --git a/app/src/main/kotlin/org/gameyfin/app/users/RoleService.kt b/app/src/main/kotlin/org/gameyfin/app/users/RoleService.kt index c729574..2d7cc53 100644 --- a/app/src/main/kotlin/org/gameyfin/app/users/RoleService.kt +++ b/app/src/main/kotlin/org/gameyfin/app/users/RoleService.kt @@ -47,12 +47,12 @@ class RoleService( } fun getRolesBelowAuth(auth: Authentication): List { - val highestUserRole = getHighestRole(auth.authorities.mapNotNull { Role.Companion.safeValueOf(it.authority) }) + val highestUserRole = getHighestRole(auth.authorities.mapNotNull { Role.safeValueOf(it.authority) }) return Role.entries.filter { it.powerLevel < highestUserRole.powerLevel } } fun authoritiesToRoles(authorities: Collection): List { - return authorities.mapNotNull { Role.Companion.safeValueOf(it.authority) } + return authorities.mapNotNull { Role.safeValueOf(it.authority) } } /** diff --git a/app/src/main/kotlin/org/gameyfin/app/users/SessionService.kt b/app/src/main/kotlin/org/gameyfin/app/users/SessionService.kt index 4a8fc95..cf5fe30 100644 --- a/app/src/main/kotlin/org/gameyfin/app/users/SessionService.kt +++ b/app/src/main/kotlin/org/gameyfin/app/users/SessionService.kt @@ -11,8 +11,8 @@ import org.springframework.stereotype.Service class SessionService(private val sessionRegistry: SessionRegistry) { fun logoutAllSessions() { - val auth = getCurrentAuth() - val sessions: List = sessionRegistry.getAllSessions(auth?.principal, false) + val authPrincipal = getCurrentAuth()?.principal ?: return + val sessions: List = sessionRegistry.getAllSessions(authPrincipal, false) for (sessionInfo in sessions) { sessionInfo.expireNow() } diff --git a/app/src/main/kotlin/org/gameyfin/app/users/passwordreset/PasswordResetEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/users/passwordreset/PasswordResetEndpoint.kt index 21a4fe2..548e7d4 100644 --- a/app/src/main/kotlin/org/gameyfin/app/users/passwordreset/PasswordResetEndpoint.kt +++ b/app/src/main/kotlin/org/gameyfin/app/users/passwordreset/PasswordResetEndpoint.kt @@ -6,13 +6,11 @@ import jakarta.annotation.security.RolesAllowed import org.gameyfin.app.core.Role import org.gameyfin.app.core.token.TokenDto import org.gameyfin.app.core.token.TokenValidationResult -import org.gameyfin.app.users.UserService @Endpoint @AnonymousAllowed class PasswordResetEndpoint( - private val passwordResetService: PasswordResetService, - private val userService: UserService + private val passwordResetService: PasswordResetService ) { fun requestPasswordReset(email: String) { diff --git a/app/src/main/kotlin/org/gameyfin/app/users/passwordreset/PasswordResetService.kt b/app/src/main/kotlin/org/gameyfin/app/users/passwordreset/PasswordResetService.kt index 7f74755..992fa68 100644 --- a/app/src/main/kotlin/org/gameyfin/app/users/passwordreset/PasswordResetService.kt +++ b/app/src/main/kotlin/org/gameyfin/app/users/passwordreset/PasswordResetService.kt @@ -59,7 +59,7 @@ class PasswordResetService( */ fun requestPasswordReset(email: String) { - val maskedEmail = Utils.Companion.maskEmail(email) + val maskedEmail = Utils.maskEmail(email) log.info { "Initiating password reset request for '${maskedEmail}'" } @@ -81,7 +81,7 @@ class PasswordResetService( } val token = generate(user) - eventPublisher.publishEvent(PasswordResetRequestEvent(this, token, Utils.Companion.getBaseUrl())) + eventPublisher.publishEvent(PasswordResetRequestEvent(this, token, Utils.getBaseUrl())) // Simulate a delay to prevent timing attacks Thread.sleep(secureRandom.nextLong(1024)) diff --git a/app/src/main/kotlin/org/gameyfin/app/users/preferences/UserPreferencesService.kt b/app/src/main/kotlin/org/gameyfin/app/users/preferences/UserPreferencesService.kt index 2d88f89..0d9a392 100644 --- a/app/src/main/kotlin/org/gameyfin/app/users/preferences/UserPreferencesService.kt +++ b/app/src/main/kotlin/org/gameyfin/app/users/preferences/UserPreferencesService.kt @@ -29,7 +29,7 @@ class UserPreferencesService( return if (appConfig != null) { getValue(appConfig.value, userPreference) } else { - return null + null } } @@ -51,7 +51,7 @@ class UserPreferencesService( return if (appConfig != null) { getValue(appConfig.value, userPreference).toString() } else { - return null + null } } diff --git a/app/src/main/kotlin/org/gameyfin/app/users/registration/InvitationService.kt b/app/src/main/kotlin/org/gameyfin/app/users/registration/InvitationService.kt index 2dc3a1f..ee5710d 100644 --- a/app/src/main/kotlin/org/gameyfin/app/users/registration/InvitationService.kt +++ b/app/src/main/kotlin/org/gameyfin/app/users/registration/InvitationService.kt @@ -27,14 +27,14 @@ class InvitationService( fun createInvitation(email: String): TokenDto { if (userService.existsByEmail(email)) - throw IllegalStateException("User with email ${Utils.Companion.maskEmail(email)} is already registered") + throw IllegalStateException("User with email ${Utils.maskEmail(email)} is already registered") val auth = getCurrentAuth() ?: throw IllegalStateException("No authentication found") val user = userService.getByUsername(auth.name) ?: throw IllegalStateException("User not found") val payload = mapOf(EMAIL_KEY to email) val token = super.generateWithPayload(user, payload) - eventPublisher.publishEvent(UserInvitationEvent(this, token, Utils.Companion.getBaseUrl(), email)) + eventPublisher.publishEvent(UserInvitationEvent(this, token, Utils.getBaseUrl(), email)) return TokenDto(token) } @@ -52,8 +52,8 @@ class InvitationService( try { val user = userService.registerUserFromInvitation(registration, email) super.delete(invitationToken) - eventPublisher.publishEvent(AccountStatusChangedEvent(this, user, Utils.Companion.getBaseUrl())) - } catch (e: IllegalStateException) { + eventPublisher.publishEvent(AccountStatusChangedEvent(this, user, Utils.getBaseUrl())) + } catch (_: IllegalStateException) { return UserInvitationAcceptanceResult.USERNAME_TAKEN } diff --git a/app/src/main/kotlin/org/gameyfin/app/util/EntityManagerHolder.kt b/app/src/main/kotlin/org/gameyfin/app/util/EntityManagerHolder.kt index 08059b7..bc17988 100644 --- a/app/src/main/kotlin/org/gameyfin/app/util/EntityManagerHolder.kt +++ b/app/src/main/kotlin/org/gameyfin/app/util/EntityManagerHolder.kt @@ -1,6 +1,7 @@ package org.gameyfin.app.util import jakarta.persistence.EntityManager +import org.springframework.beans.factory.getBean import org.springframework.context.ApplicationContext import org.springframework.context.ApplicationContextAware import org.springframework.stereotype.Component @@ -10,7 +11,7 @@ object EntityManagerHolder : ApplicationContextAware { private var entityManager: EntityManager? = null override fun setApplicationContext(context: ApplicationContext) { - entityManager = context.getBean(EntityManager::class.java) + entityManager = context.getBean() } fun getEntityManager(): EntityManager { diff --git a/app/src/main/kotlin/org/gameyfin/db/h2/BlurhashMigration.kt b/app/src/main/kotlin/org/gameyfin/db/h2/BlurhashMigration.kt index 900200c..6aae9c6 100644 --- a/app/src/main/kotlin/org/gameyfin/db/h2/BlurhashMigration.kt +++ b/app/src/main/kotlin/org/gameyfin/db/h2/BlurhashMigration.kt @@ -22,6 +22,7 @@ object BlurhashMigration { * Scale down image for faster blurhash calculation. * Blurhash doesn't need full resolution - 100px width is plenty for a good blur. */ + @Suppress("DuplicatedCode") private fun scaleImageForBlurhash(original: BufferedImage, maxWidth: Int = 100): BufferedImage { val originalWidth = original.width val originalHeight = original.height @@ -32,10 +33,9 @@ object BlurhashMigration { } val scale = maxWidth.toDouble() / originalWidth - val targetWidth = maxWidth val targetHeight = (originalHeight * scale).toInt() - val scaled = BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB) + val scaled = BufferedImage(maxWidth, targetHeight, BufferedImage.TYPE_INT_RGB) val g2d = scaled.createGraphics() // Use fast scaling for blurhash - quality doesn't matter much for a blur @@ -43,7 +43,7 @@ object BlurhashMigration { g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED) g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF) - g2d.drawImage(original, 0, 0, targetWidth, targetHeight, null) + g2d.drawImage(original, 0, 0, maxWidth, targetHeight, null) g2d.dispose() return scaled diff --git a/app/src/main/kotlin/org/gameyfin/db/h2/H2Aliases.kt b/app/src/main/kotlin/org/gameyfin/db/h2/H2Aliases.kt index a91a7c5..94158c0 100644 --- a/app/src/main/kotlin/org/gameyfin/db/h2/H2Aliases.kt +++ b/app/src/main/kotlin/org/gameyfin/db/h2/H2Aliases.kt @@ -1,7 +1,7 @@ package org.gameyfin.db.h2 -import com.fasterxml.jackson.databind.ObjectMapper import org.gameyfin.app.core.security.EncryptionUtils +import tools.jackson.databind.ObjectMapper import java.sql.Connection import java.sql.SQLException diff --git a/app/src/main/resources/application.yml b/app/src/main/resources/application.yml index ea387b7..a185a03 100644 --- a/app/src/main/resources/application.yml +++ b/app/src/main/resources/application.yml @@ -7,8 +7,6 @@ logging.level: org.gameyfin.GameyfinApplicationKt: warn # Suppress false positive warnings from Spring Security 6 org.springframework.security.config.annotation.authentication.configuration.InitializeUserDetailsBeanManagerConfigurer: error - # Hides an error log on the first aborted download - org.apache.catalina.core.ContainerBase: off server: port: 8080 @@ -17,10 +15,10 @@ server: tracking-modes: cookie timeout: 24h forward-headers-strategy: framework - tomcat: - remoteip: - protocol-header: X-Forwarded-Proto - remote-ip-header: X-Forwarded-For + jetty: + threads: + max: 200 + min: 8 management: server: diff --git a/app/src/main/resources/db/migration/V2.4.0__Refactor_token_primary_key_for_encryption.sql b/app/src/main/resources/db/migration/V2.4.0__Refactor_token_primary_key_for_encryption.sql new file mode 100644 index 0000000..13e307b --- /dev/null +++ b/app/src/main/resources/db/migration/V2.4.0__Refactor_token_primary_key_for_encryption.sql @@ -0,0 +1,30 @@ +-- Flyway Migration: V2.4.0 +-- Purpose: Refactor TOKEN table to support encryption on secret field by separating primary key from secret. +-- Context: Hibernate 6.x (Spring Boot 4) does not allow AttributeConverter on @Id fields. +-- The secret field contains sensitive token data (password reset tokens, etc.) that needs encryption. +-- Strategy: +-- Modify the existing TOKEN table in-place by adding a new ID column and restructuring constraints. + +-- Step 1: Add new ID column (nullable initially to allow data population) +ALTER TABLE TOKEN ADD COLUMN ID CHARACTER VARYING(255); + +-- Step 2: Populate ID column with new UUIDs for existing rows +UPDATE TOKEN SET ID = RANDOM_UUID() WHERE ID IS NULL; + +-- Step 3: Make ID column non-null now that it has values +ALTER TABLE TOKEN ALTER COLUMN ID SET NOT NULL; + +-- Step 4: Drop the primary key constraint on SECRET +-- H2 uses auto-generated constraint names, so we need to find and drop it +-- The primary key constraint is typically named PRIMARY_KEY_XXX or CONSTRAINT_XXX +ALTER TABLE TOKEN DROP PRIMARY KEY; + +-- Step 5: Add primary key constraint on ID +ALTER TABLE TOKEN ADD PRIMARY KEY (ID); + +-- Step 6: Add unique constraint on SECRET (it was previously the primary key, so it was already unique) +-- The SECRET column should remain unique for lookups +ALTER TABLE TOKEN ADD CONSTRAINT UK_TOKEN_SECRET UNIQUE (SECRET); + +-- Step 7: Create index on SECRET for fast lookups +CREATE INDEX IDX_TOKEN_SECRET ON TOKEN(SECRET); diff --git a/app/src/main/resources/vaadin-featureflags.properties b/app/src/main/resources/vaadin-featureflags.properties deleted file mode 100644 index 0c1b702..0000000 --- a/app/src/main/resources/vaadin-featureflags.properties +++ /dev/null @@ -1 +0,0 @@ -com.vaadin.experimental.react19=true \ No newline at end of file diff --git a/app/src/test/kotlin/org/gameyfin/app/config/ConfigServiceTest.kt b/app/src/test/kotlin/org/gameyfin/app/config/ConfigServiceTest.kt index 7bc8bdd..9de60cc 100644 --- a/app/src/test/kotlin/org/gameyfin/app/config/ConfigServiceTest.kt +++ b/app/src/test/kotlin/org/gameyfin/app/config/ConfigServiceTest.kt @@ -1,6 +1,5 @@ package org.gameyfin.app.config -import com.fasterxml.jackson.databind.ObjectMapper import io.mockk.* import org.gameyfin.app.config.entities.ConfigEntry import org.gameyfin.app.config.persistence.ConfigRepository @@ -9,6 +8,7 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.springframework.boot.logging.LogLevel import org.springframework.data.repository.findByIdOrNull +import tools.jackson.databind.ObjectMapper import java.io.Serializable import kotlin.test.assertEquals import kotlin.test.assertNotNull diff --git a/app/src/test/kotlin/org/gameyfin/app/core/download/bandwidth/SessionBandwidthManagerTest.kt b/app/src/test/kotlin/org/gameyfin/app/core/download/bandwidth/SessionBandwidthManagerTest.kt index 59af0ac..f7f7cf9 100644 --- a/app/src/test/kotlin/org/gameyfin/app/core/download/bandwidth/SessionBandwidthManagerTest.kt +++ b/app/src/test/kotlin/org/gameyfin/app/core/download/bandwidth/SessionBandwidthManagerTest.kt @@ -1,6 +1,5 @@ package org.gameyfin.app.core.download.bandwidth -import com.helger.commons.mock.CommonsAssert.assertEquals import org.junit.jupiter.api.Assertions.assertDoesNotThrow import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -104,7 +103,7 @@ class SessionBandwidthManagerTest { val stat1 = stats["session-1"] assertNotNull(stat1) - assertEquals("session-1", stat1!!.sessionId) + assertEquals("session-1", stat1.sessionId) assertEquals(1, stat1.activeDownloads) assertEquals("user1", stat1.username) assertEquals("192.168.1.1", stat1.remoteIp) diff --git a/app/src/test/kotlin/org/gameyfin/app/core/download/bandwidth/SessionBandwidthTrackerTest.kt b/app/src/test/kotlin/org/gameyfin/app/core/download/bandwidth/SessionBandwidthTrackerTest.kt index 4f9ef2a..cbf3556 100644 --- a/app/src/test/kotlin/org/gameyfin/app/core/download/bandwidth/SessionBandwidthTrackerTest.kt +++ b/app/src/test/kotlin/org/gameyfin/app/core/download/bandwidth/SessionBandwidthTrackerTest.kt @@ -425,5 +425,122 @@ class SessionBandwidthTrackerTest { val afterRecordTime = tracker.lastActivityTime assertTrue(afterRecordTime > initialTime) } + + @Test + fun `updateMonitoringStatistics should handle concurrent window rotation`() { + val threadCount = 10 + val executor = Executors.newFixedThreadPool(threadCount) + val latch = CountDownLatch(threadCount) + + // Set window start to 11 seconds ago to trigger rotation + val monitoringWindowStartField = tracker.javaClass.getDeclaredField("monitoringWindowStart") + monitoringWindowStartField.isAccessible = true + val elevenSecondsAgo = System.nanoTime() - 11_000_000_000L + monitoringWindowStartField.setLong(tracker, elevenSecondsAgo) + + // Have multiple threads try to record bytes at the same time + // This should trigger concurrent window rotation attempts + repeat(threadCount) { + executor.submit { + try { + tracker.recordBytes(100) + } finally { + latch.countDown() + } + } + } + + assertTrue(latch.await(5, TimeUnit.SECONDS)) + executor.shutdown() + assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS)) + + // All bytes should be recorded despite concurrent rotation + assertEquals(1000, tracker.totalBytesTransferred) + } + + @Test + fun `updateMonitoringStatistics should update totalBytesTransferred atomically`() { + tracker.recordBytes(1000) + assertEquals(1000, tracker.totalBytesTransferred) + + tracker.recordBytes(2000) + assertEquals(3000, tracker.totalBytesTransferred) + + tracker.recordBytes(500) + assertEquals(3500, tracker.totalBytesTransferred) + } + + @Test + fun `updateMonitoringStatistics should update lastActivityTime on each call`() { + val time1 = tracker.lastActivityTime + Thread.sleep(10) + + tracker.recordBytes(100) + val time2 = tracker.lastActivityTime + assertTrue(time2 > time1, "Activity time should increase after recordBytes") + + Thread.sleep(10) + tracker.throttle(100) + val time3 = tracker.lastActivityTime + assertTrue(time3 > time2, "Activity time should increase after throttle") + } + + @Test + fun `getCurrentBytesPerSecond should blend with previous window when current window is young`() { + // Record bytes in first window + tracker.recordBytes(10_000) + Thread.sleep(1100) // Wait over 1 second to ensure first window is mature + + // Force window rotation by setting window start to 11 seconds ago + val monitoringWindowStartField = tracker.javaClass.getDeclaredField("monitoringWindowStart") + monitoringWindowStartField.isAccessible = true + val elevenSecondsAgo = System.nanoTime() - 11_000_000_000L + monitoringWindowStartField.setLong(tracker, elevenSecondsAgo) + + // Record bytes to trigger rotation + tracker.recordBytes(5_000) + + // Immediately check rate - should blend with previous window since current is young + Thread.sleep(100) // Sleep a bit but less than 1 second + val rate = tracker.getCurrentBytesPerSecond() + + // The rate should be positive and influenced by both windows + assertTrue(rate > 0, "Rate should be positive with blended windows") + assertTrue(tracker.totalBytesTransferred == 15_000L, "Total should be 15,000 bytes") + } + + @Test + fun `updateMonitoringStatistics should handle synchronized block correctly during rotation`() { + // Record initial bytes + tracker.recordBytes(1000) + + // Set up for window rotation + val monitoringWindowStartField = tracker.javaClass.getDeclaredField("monitoringWindowStart") + monitoringWindowStartField.isAccessible = true + val elevenSecondsAgo = System.nanoTime() - 11_000_000_000L + monitoringWindowStartField.setLong(tracker, elevenSecondsAgo) + + // Record more bytes - should trigger synchronized block for rotation + tracker.recordBytes(2000) + + // Verify the bytes were recorded correctly + assertEquals(3000, tracker.totalBytesTransferred) + + // Record more bytes in the new window + tracker.recordBytes(500) + assertEquals(3500, tracker.totalBytesTransferred) + } + + @Test + fun `throttle should call updateMonitoringStatistics with correct byte count`() { + val bytes = 5000L + tracker.throttle(bytes) + + // Verify bytes were recorded + assertEquals(bytes, tracker.totalBytesTransferred) + + // Verify activity time was updated + assertTrue(tracker.lastActivityTime > 0) + } } diff --git a/app/src/test/kotlin/org/gameyfin/app/core/download/files/DownloadEndpointTest.kt b/app/src/test/kotlin/org/gameyfin/app/core/download/files/DownloadEndpointTest.kt index 055bf70..027f44f 100644 --- a/app/src/test/kotlin/org/gameyfin/app/core/download/files/DownloadEndpointTest.kt +++ b/app/src/test/kotlin/org/gameyfin/app/core/download/files/DownloadEndpointTest.kt @@ -56,7 +56,7 @@ class DownloadEndpointTest { * Helper method to wait for DeferredResult to complete and get the result. * Handles async processing with timeout. */ - private fun awaitDeferredResult(deferredResult: DeferredResult, timeoutSeconds: Long = 5): T { + private fun awaitDeferredResult(deferredResult: DeferredResult, timeoutSeconds: Long = 5): T { val latch = CountDownLatch(1) var result: T? = null var error: Throwable? = null @@ -108,7 +108,7 @@ class DownloadEndpointTest { assertEquals(HttpStatus.OK, response.statusCode) assertNotNull(response.body) - assertTrue(response.headers.containsKey("Content-Disposition")) + assertTrue(response.headers.containsHeader("Content-Disposition")) assertTrue(response.headers["Content-Disposition"]!![0].contains("Test Game.zip")) verify(exactly = 1) { gameService.getById(gameId) } @@ -142,9 +142,9 @@ class DownloadEndpointTest { val response = awaitDeferredResult(deferredResult) assertEquals(HttpStatus.OK, response.statusCode) - assertTrue(response.headers.containsKey("Content-Disposition")) + assertTrue(response.headers.containsHeader("Content-Disposition")) // Content-Length should not be present for directories - assertFalse(response.headers.containsKey("Content-Length")) + assertFalse(response.headers.containsHeader("Content-Length")) } @Test @@ -171,7 +171,7 @@ class DownloadEndpointTest { val response = awaitDeferredResult(deferredResult) assertEquals(HttpStatus.OK, response.statusCode) - assertFalse(response.headers.containsKey("Content-Length")) + assertFalse(response.headers.containsHeader("Content-Length")) } @Test @@ -198,7 +198,7 @@ class DownloadEndpointTest { val response = awaitDeferredResult(deferredResult) assertEquals(HttpStatus.OK, response.statusCode) - assertFalse(response.headers.containsKey("Content-Length")) + assertFalse(response.headers.containsHeader("Content-Length")) } @Test diff --git a/app/src/test/kotlin/org/gameyfin/app/core/logging/LogEndpointTest.kt b/app/src/test/kotlin/org/gameyfin/app/core/logging/LogEndpointTest.kt index 4007c89..181c1be 100644 --- a/app/src/test/kotlin/org/gameyfin/app/core/logging/LogEndpointTest.kt +++ b/app/src/test/kotlin/org/gameyfin/app/core/logging/LogEndpointTest.kt @@ -131,7 +131,8 @@ class LogEndpointTest { @Test fun `getApplicationLogs should return empty flux when authentication is null`() { - val securityContext = SecurityContextImpl(null) + val mockAuthentication = mockk(relaxed = true) + val securityContext = SecurityContextImpl(mockAuthentication) SecurityContextHolder.setContext(securityContext) val result = logEndpoint.getApplicationLogs() diff --git a/app/src/test/kotlin/org/gameyfin/app/core/plugins/management/GameyfinPluginManagerTest.kt b/app/src/test/kotlin/org/gameyfin/app/core/plugins/management/GameyfinPluginManagerTest.kt index b412d6c..e92af1d 100644 --- a/app/src/test/kotlin/org/gameyfin/app/core/plugins/management/GameyfinPluginManagerTest.kt +++ b/app/src/test/kotlin/org/gameyfin/app/core/plugins/management/GameyfinPluginManagerTest.kt @@ -42,7 +42,7 @@ class GameyfinPluginManagerTest { pluginManagementRepository = mockk(relaxed = true) // Set up default mocks - every { pluginConfigRepository.findAllById_PluginId(any()) } returns emptyList() + every { pluginConfigRepository.findAllByPluginId(any()) } returns emptyList() every { pluginManagementRepository.findByIdOrNull(any()) } returns null every { pluginManagementRepository.save(any()) } returnsArgument 0 every { pluginManagementRepository.findMaxPriority() } returns null @@ -233,7 +233,7 @@ class GameyfinPluginManagerTest { every { pluginManager.getPlugin("test-plugin") } returns pluginWrapper every { pluginManager.stopPlugin("test-plugin") } returns PluginState.STOPPED every { pluginManager.startPlugin("test-plugin") } returns PluginState.STARTED - every { pluginConfigRepository.findAllById_PluginId("test-plugin") } returns configEntries + every { pluginConfigRepository.findAllByPluginId("test-plugin") } returns configEntries pluginManager.restart("test-plugin") diff --git a/app/src/test/kotlin/org/gameyfin/app/core/security/CustomAuthenticationEntryPointTest.kt b/app/src/test/kotlin/org/gameyfin/app/core/security/CustomAuthenticationEntryPointTest.kt index cd4a9c1..70e873d 100644 --- a/app/src/test/kotlin/org/gameyfin/app/core/security/CustomAuthenticationEntryPointTest.kt +++ b/app/src/test/kotlin/org/gameyfin/app/core/security/CustomAuthenticationEntryPointTest.kt @@ -6,6 +6,7 @@ import jakarta.servlet.http.HttpServletResponse import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException import org.springframework.security.authentication.BadCredentialsException import org.springframework.security.core.AuthenticationException import kotlin.test.assertEquals @@ -96,7 +97,7 @@ class CustomAuthenticationEntryPointTest { every { request.getParameter("direct") } returns "1" every { response.sendRedirect(any()) } just Runs - entryPoint.commence(request, response, null) + entryPoint.commence(request, response, AuthenticationCredentialsNotFoundException("Test")) verify(exactly = 1) { response.sendRedirect("/login") } } diff --git a/app/src/test/kotlin/org/gameyfin/app/core/security/DynamicPublicAccessAuthorizationManagerTest.kt b/app/src/test/kotlin/org/gameyfin/app/core/security/DynamicPublicAccessAuthorizationManagerTest.kt index 90463c6..eaa9baf 100644 --- a/app/src/test/kotlin/org/gameyfin/app/core/security/DynamicPublicAccessAuthorizationManagerTest.kt +++ b/app/src/test/kotlin/org/gameyfin/app/core/security/DynamicPublicAccessAuthorizationManagerTest.kt @@ -46,7 +46,7 @@ class DynamicPublicAccessAuthorizationManagerTest { "password", listOf(SimpleGrantedAuthority("ROLE_USER")) ) - val authSupplier = Supplier { authentication } + val authSupplier = Supplier { authentication } val decision = manager.authorize(authSupplier, context) @@ -63,7 +63,7 @@ class DynamicPublicAccessAuthorizationManagerTest { "password", listOf(SimpleGrantedAuthority("ROLE_USER")) ) - val authSupplier = Supplier { authentication } + val authSupplier = Supplier { authentication } val decision = manager.authorize(authSupplier, context) @@ -79,7 +79,7 @@ class DynamicPublicAccessAuthorizationManagerTest { every { authentication.isAuthenticated } returns false every { authentication.principal } returns "anonymousUser" - val authSupplier = Supplier { authentication } + val authSupplier = Supplier { authentication } val decision = manager.authorize(authSupplier, context) @@ -95,7 +95,7 @@ class DynamicPublicAccessAuthorizationManagerTest { every { authentication.isAuthenticated } returns false every { authentication.principal } returns "anonymousUser" - val authSupplier = Supplier { authentication } + val authSupplier = Supplier { authentication } val decision = manager.authorize(authSupplier, context) @@ -107,7 +107,8 @@ class DynamicPublicAccessAuthorizationManagerTest { fun `check should deny access when authentication is null and public access is disabled`() { every { configService.get(ConfigProperties.Security.AllowPublicAccess) } returns false - val authSupplier = Supplier { null } + val mockAuthentication = mockk(relaxed = true) + val authSupplier = Supplier { mockAuthentication } val decision = manager.authorize(authSupplier, context) @@ -119,7 +120,8 @@ class DynamicPublicAccessAuthorizationManagerTest { fun `check should allow access when authentication is null and public access is enabled`() { every { configService.get(ConfigProperties.Security.AllowPublicAccess) } returns true - val authSupplier = Supplier { null } + val mockAuthentication = mockk(relaxed = true) + val authSupplier = Supplier { mockAuthentication } val decision = manager.authorize(authSupplier, context) @@ -135,7 +137,7 @@ class DynamicPublicAccessAuthorizationManagerTest { every { authentication.isAuthenticated } returns true every { authentication.principal } returns "anonymousUser" - val authSupplier = Supplier { authentication } + val authSupplier = Supplier { authentication } val decision = manager.authorize(authSupplier, context) @@ -151,7 +153,7 @@ class DynamicPublicAccessAuthorizationManagerTest { every { authentication.isAuthenticated } returns true every { authentication.principal } returns "john.doe" - val authSupplier = Supplier { authentication } + val authSupplier = Supplier { authentication } val decision = manager.authorize(authSupplier, context) @@ -167,38 +169,11 @@ class DynamicPublicAccessAuthorizationManagerTest { every { authentication.isAuthenticated } returns false every { authentication.principal } returns "anonymousUser" - val authSupplier = Supplier { authentication } + val authSupplier = Supplier { authentication } val decision = manager.authorize(authSupplier, context) assertNotNull(decision) assertFalse(decision.isGranted) } - - @Test - fun `check should work when supplier is null`() { - every { configService.get(ConfigProperties.Security.AllowPublicAccess) } returns false - - val decision = manager.authorize(null, context) - - assertNotNull(decision) - assertFalse(decision.isGranted) - } - - @Test - fun `check should work when context is null`() { - every { configService.get(ConfigProperties.Security.AllowPublicAccess) } returns false - - val authentication = UsernamePasswordAuthenticationToken( - "user", - "password", - listOf(SimpleGrantedAuthority("ROLE_USER")) - ) - val authSupplier = Supplier { authentication } - - val decision = manager.authorize(authSupplier, null) - - assertNotNull(decision) - assertTrue(decision.isGranted) - } } \ No newline at end of file diff --git a/app/src/test/kotlin/org/gameyfin/app/core/security/PasswordEncoderConfigTest.kt b/app/src/test/kotlin/org/gameyfin/app/core/security/PasswordEncoderConfigTest.kt index 66b7f35..5424796 100644 --- a/app/src/test/kotlin/org/gameyfin/app/core/security/PasswordEncoderConfigTest.kt +++ b/app/src/test/kotlin/org/gameyfin/app/core/security/PasswordEncoderConfigTest.kt @@ -42,17 +42,6 @@ class PasswordEncoderConfigTest { assertTrue(encoder.matches(password, encoded2)) } - @Test - fun `passwordEncoder should handle empty password`() { - val encoder = config.passwordEncoder() - val password = "" - - val encoded = encoder.encode(password) - - assertNotNull(encoded) - assertTrue(encoder.matches(password, encoded)) - } - @Test fun `passwordEncoder should handle long password`() { val encoder = config.passwordEncoder() diff --git a/app/src/test/kotlin/org/gameyfin/app/core/security/SecurityUtilsTest.kt b/app/src/test/kotlin/org/gameyfin/app/core/security/SecurityUtilsTest.kt index bc71b95..8fd4672 100644 --- a/app/src/test/kotlin/org/gameyfin/app/core/security/SecurityUtilsTest.kt +++ b/app/src/test/kotlin/org/gameyfin/app/core/security/SecurityUtilsTest.kt @@ -116,17 +116,6 @@ class SecurityUtilsTest { assertFalse(result) } - @Test - fun `isCurrentUserAdmin should return false when authorities is null`() { - val authentication = mockk() - every { authentication.authorities } returns null - every { securityContext.authentication } returns authentication - - val result = isCurrentUserAdmin() - - assertFalse(result) - } - @Test fun `isCurrentUserAdmin should return true when user has both ADMIN and USER roles`() { val authentication = UsernamePasswordAuthenticationToken( @@ -188,14 +177,6 @@ class SecurityUtilsTest { assertFalse(authentication.isAdmin()) } - @Test - fun `Authentication isAdmin should return false when authorities is null`() { - val authentication = mockk() - every { authentication.authorities } returns null - - assertFalse(authentication.isAdmin()) - } - @Test fun `Authentication isAdmin should return true when user has SUPERADMIN among multiple roles`() { val authentication = UsernamePasswordAuthenticationToken( diff --git a/app/src/test/kotlin/org/gameyfin/app/core/security/SsoEnabledConditionTest.kt b/app/src/test/kotlin/org/gameyfin/app/core/security/SsoEnabledConditionTest.kt index 4274adb..2b2af32 100644 --- a/app/src/test/kotlin/org/gameyfin/app/core/security/SsoEnabledConditionTest.kt +++ b/app/src/test/kotlin/org/gameyfin/app/core/security/SsoEnabledConditionTest.kt @@ -5,6 +5,7 @@ import org.gameyfin.app.config.ConfigProperties import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.springframework.beans.factory.getBean import org.springframework.context.annotation.ConditionContext import org.springframework.core.env.Environment import org.springframework.core.type.AnnotatedTypeMetadata @@ -30,7 +31,7 @@ class SsoEnabledConditionTest { environment = mockk() every { context.beanFactory } returns mockk { - every { getBean(Environment::class.java) } returns environment + every { getBean() } returns environment } } diff --git a/app/src/test/kotlin/org/gameyfin/app/core/serialization/ArrayDeserializerTest.kt b/app/src/test/kotlin/org/gameyfin/app/core/serialization/ArrayDeserializerTest.kt new file mode 100644 index 0000000..a373ae5 --- /dev/null +++ b/app/src/test/kotlin/org/gameyfin/app/core/serialization/ArrayDeserializerTest.kt @@ -0,0 +1,106 @@ +package org.gameyfin.app.core.serialization + +import io.mockk.every +import io.mockk.mockk +import io.mockk.unmockkAll +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import tools.jackson.core.JsonParser +import tools.jackson.core.ObjectReadContext +import tools.jackson.databind.DeserializationContext +import tools.jackson.databind.JsonNode +import java.io.Serializable +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class ArrayDeserializerTest { + + private lateinit var deserializer: ArrayDeserializer + private lateinit var jsonParser: JsonParser + private lateinit var deserializationContext: DeserializationContext + private lateinit var objectReadContext: ObjectReadContext + + @BeforeEach + fun setup() { + deserializer = ArrayDeserializer() + jsonParser = mockk() + deserializationContext = mockk() + objectReadContext = mockk() + } + + @AfterEach + fun tearDown() { + unmockkAll() + } + + @Test + fun `deserialize should convert JSON array to String array`() { + val textNode1 = mockk() + val textNode2 = mockk() + val textNode3 = mockk() + every { textNode1.asString() } returns "item1" + every { textNode2.asString() } returns "item2" + every { textNode3.asString() } returns "item3" + + val arrayNode = mockk() + every { arrayNode.isArray } returns true + every { arrayNode.iterator() } returns mutableListOf(textNode1, textNode2, textNode3).iterator() + every { jsonParser.objectReadContext() } returns objectReadContext + every { objectReadContext.readTree(jsonParser) } returns arrayNode + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertTrue(result is Array<*>) + assertEquals(3, result.size) + assertEquals("item1", result[0]) + assertEquals("item2", result[1]) + assertEquals("item3", result[2]) + } + + @Test + fun `deserialize should convert empty JSON array to empty String array`() { + val arrayNode = mockk() + every { arrayNode.isArray } returns true + every { arrayNode.iterator() } returns mutableListOf().iterator() + every { jsonParser.objectReadContext() } returns objectReadContext + every { objectReadContext.readTree(jsonParser) } returns arrayNode + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertTrue(result is Array<*>) + assertEquals(0, result.size) + } + + @Test + fun `deserialize should handle non-array JSON node`() { + val textNode = mockk() + val serializable = "test string" as Serializable + every { textNode.isArray } returns false + every { jsonParser.objectReadContext() } returns objectReadContext + every { objectReadContext.readTree(jsonParser) } returns textNode + every { deserializationContext.readTreeAsValue(textNode, Serializable::class.java) } returns serializable + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(serializable, result) + } + + @Test + fun `deserialize should handle array with single element`() { + val textNode = mockk() + every { textNode.asString() } returns "single" + + val arrayNode = mockk() + every { arrayNode.isArray } returns true + every { arrayNode.iterator() } returns mutableListOf(textNode).iterator() + every { jsonParser.objectReadContext() } returns objectReadContext + every { objectReadContext.readTree(jsonParser) } returns arrayNode + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertTrue(result is Array<*>) + assertEquals(1, result.size) + assertEquals("single", result[0]) + } +} diff --git a/app/src/test/kotlin/org/gameyfin/app/core/serialization/DisplayableSerializerTest.kt b/app/src/test/kotlin/org/gameyfin/app/core/serialization/DisplayableSerializerTest.kt new file mode 100644 index 0000000..d63f65d --- /dev/null +++ b/app/src/test/kotlin/org/gameyfin/app/core/serialization/DisplayableSerializerTest.kt @@ -0,0 +1,157 @@ +package org.gameyfin.app.core.serialization + +import io.mockk.mockk +import io.mockk.unmockkAll +import io.mockk.verify +import org.gameyfin.pluginapi.gamemetadata.GameFeature +import org.gameyfin.pluginapi.gamemetadata.Genre +import org.gameyfin.pluginapi.gamemetadata.PlayerPerspective +import org.gameyfin.pluginapi.gamemetadata.Theme +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import tools.jackson.core.JsonGenerator +import tools.jackson.databind.SerializationContext + +class DisplayableSerializerTest { + + private lateinit var serializer: DisplayableSerializer + private lateinit var jsonGenerator: JsonGenerator + private lateinit var serializationContext: SerializationContext + + @BeforeEach + fun setup() { + serializer = DisplayableSerializer() + jsonGenerator = mockk(relaxed = true) + serializationContext = mockk() + } + + @AfterEach + fun tearDown() { + unmockkAll() + } + + @Test + fun `serialize should write displayName for valid theme`() { + val theme = Theme.SCIENCE_FICTION + + serializer.serialize(theme, jsonGenerator, serializationContext) + + verify(exactly = 1) { jsonGenerator.writeString("Science Fiction") } + } + + @Test + fun `serialize should handle null value`() { + serializer.serialize(null, jsonGenerator, serializationContext) + + verify(exactly = 0) { jsonGenerator.writeString(any()) } + } + + @Test + fun `serialize should write displayName for valid genre`() { + val genre = Genre.ROLE_PLAYING + + serializer.serialize(genre, jsonGenerator, serializationContext) + + verify(exactly = 1) { jsonGenerator.writeString("Role-Playing") } + } + + @Test + fun `serialize should write displayName for valid game feature`() { + val feature = GameFeature.MULTIPLAYER + + serializer.serialize(feature, jsonGenerator, serializationContext) + + verify(exactly = 1) { jsonGenerator.writeString("Multiplayer") } + } + + @Test + fun `serialize should write displayName for valid player perspective`() { + val perspective = PlayerPerspective.FIRST_PERSON + + serializer.serialize(perspective, jsonGenerator, serializationContext) + + verify(exactly = 1) { jsonGenerator.writeString("First-Person") } + } + + @Test + fun `serialize should handle theme with hyphens`() { + val theme = Theme.NON_FICTION + + serializer.serialize(theme, jsonGenerator, serializationContext) + + verify(exactly = 1) { jsonGenerator.writeString("Non-Fiction") } + } + + @Test + fun `serialize should handle genre with ampersand`() { + val genre = Genre.CARD_AND_BOARD_GAME + + serializer.serialize(genre, jsonGenerator, serializationContext) + + verify(exactly = 1) { jsonGenerator.writeString("Card & Board Game") } + } + + @Test + fun `serialize should handle genre with slash and apostrophe`() { + val genre = Genre.HACK_AND_SLASH_BEAT_EM_UP + + serializer.serialize(genre, jsonGenerator, serializationContext) + + verify(exactly = 1) { jsonGenerator.writeString("Hack and Slash/Beat 'em up") } + } + + @Test + fun `serialize should handle feature with hyphen`() { + val feature = GameFeature.CROSS_PLATFORM + + serializer.serialize(feature, jsonGenerator, serializationContext) + + verify(exactly = 1) { jsonGenerator.writeString("Cross-Platform") } + } + + @Test + fun `serialize should handle perspective with slash`() { + val perspective = PlayerPerspective.BIRD_VIEW_ISOMETRIC + + serializer.serialize(perspective, jsonGenerator, serializationContext) + + verify(exactly = 1) { jsonGenerator.writeString("Bird View/Isometric") } + } + + @Test + fun `serialize should handle all theme values correctly`() { + Theme.entries.forEach { theme -> + serializer.serialize(theme, jsonGenerator, serializationContext) + + verify(exactly = 1) { jsonGenerator.writeString(theme.displayName) } + } + } + + @Test + fun `serialize should handle all genre values correctly`() { + Genre.entries.forEach { genre -> + serializer.serialize(genre, jsonGenerator, serializationContext) + + verify(atLeast = 1) { jsonGenerator.writeString(genre.displayName) } + } + } + + @Test + fun `serialize should handle all game feature values correctly`() { + GameFeature.entries.forEach { feature -> + serializer.serialize(feature, jsonGenerator, serializationContext) + + verify(atLeast = 1) { jsonGenerator.writeString(feature.displayName) } + } + } + + @Test + fun `serialize should handle all player perspective values correctly`() { + PlayerPerspective.entries.forEach { perspective -> + serializer.serialize(perspective, jsonGenerator, serializationContext) + + verify(atLeast = 1) { jsonGenerator.writeString(perspective.displayName) } + } + } +} diff --git a/app/src/test/kotlin/org/gameyfin/app/core/serialization/GameFeatureDeserializerTest.kt b/app/src/test/kotlin/org/gameyfin/app/core/serialization/GameFeatureDeserializerTest.kt new file mode 100644 index 0000000..e3d5986 --- /dev/null +++ b/app/src/test/kotlin/org/gameyfin/app/core/serialization/GameFeatureDeserializerTest.kt @@ -0,0 +1,196 @@ +package org.gameyfin.app.core.serialization + +import io.mockk.every +import io.mockk.mockk +import io.mockk.unmockkAll +import org.gameyfin.pluginapi.gamemetadata.GameFeature +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import tools.jackson.core.JsonParser +import tools.jackson.databind.DeserializationContext +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class GameFeatureDeserializerTest { + + private lateinit var deserializer: GameFeatureDeserializer + private lateinit var jsonParser: JsonParser + private lateinit var deserializationContext: DeserializationContext + + @BeforeEach + fun setup() { + deserializer = GameFeatureDeserializer() + jsonParser = mockk() + deserializationContext = mockk() + } + + @AfterEach + fun tearDown() { + unmockkAll() + } + + @Test + fun `deserialize should return correct feature for valid displayName`() { + every { jsonParser.string } returns "Singleplayer" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(GameFeature.SINGLEPLAYER, result) + } + + @Test + fun `deserialize should return null for unknown displayName`() { + every { jsonParser.string } returns "Unknown Feature" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertNull(result) + } + + @Test + fun `deserialize should return null for empty string`() { + every { jsonParser.string } returns "" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertNull(result) + } + + @Test + fun `deserialize should return null for null string`() { + every { jsonParser.string } returns null + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertNull(result) + } + + @Test + fun `deserialize should be case-sensitive`() { + every { jsonParser.string } returns "multiplayer" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertNull(result) + } + + @Test + fun `deserialize should handle Multiplayer feature`() { + every { jsonParser.string } returns "Multiplayer" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(GameFeature.MULTIPLAYER, result) + } + + @Test + fun `deserialize should handle Co-op feature`() { + every { jsonParser.string } returns "Co-op" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(GameFeature.CO_OP, result) + } + + @Test + fun `deserialize should handle Cross-Platform feature`() { + every { jsonParser.string } returns "Cross-Platform" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(GameFeature.CROSS_PLATFORM, result) + } + + @Test + fun `deserialize should handle VR feature`() { + every { jsonParser.string } returns "VR" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(GameFeature.VR, result) + } + + @Test + fun `deserialize should handle AR feature`() { + every { jsonParser.string } returns "AR" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(GameFeature.AR, result) + } + + @Test + fun `deserialize should handle Cloud Saves feature`() { + every { jsonParser.string } returns "Cloud Saves" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(GameFeature.CLOUD_SAVES, result) + } + + @Test + fun `deserialize should handle Controller Support feature`() { + every { jsonParser.string } returns "Controller Support" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(GameFeature.CONTROLLER_SUPPORT, result) + } + + @Test + fun `deserialize should handle Local Multiplayer feature`() { + every { jsonParser.string } returns "Local Multiplayer" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(GameFeature.LOCAL_MULTIPLAYER, result) + } + + @Test + fun `deserialize should handle Online Co-op feature`() { + every { jsonParser.string } returns "Online Co-op" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(GameFeature.ONLINE_CO_OP, result) + } + + @Test + fun `deserialize should handle Online PvP feature`() { + every { jsonParser.string } returns "Online PvP" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(GameFeature.ONLINE_PVP, result) + } + + @Test + fun `deserialize should handle Crossplay feature`() { + every { jsonParser.string } returns "Crossplay" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(GameFeature.CROSSPLAY, result) + } + + @Test + fun `deserialize should handle Splitscreen feature`() { + every { jsonParser.string } returns "Splitscreen" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(GameFeature.SPLITSCREEN, result) + } + + @Test + fun `deserialize should handle all valid feature displayNames correctly`() { + GameFeature.entries.forEach { feature -> + every { jsonParser.string } returns feature.displayName + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(feature, result, "Failed to deserialize ${feature.displayName}") + } + } +} diff --git a/app/src/test/kotlin/org/gameyfin/app/core/serialization/GenreDeserializerTest.kt b/app/src/test/kotlin/org/gameyfin/app/core/serialization/GenreDeserializerTest.kt new file mode 100644 index 0000000..6a0723d --- /dev/null +++ b/app/src/test/kotlin/org/gameyfin/app/core/serialization/GenreDeserializerTest.kt @@ -0,0 +1,178 @@ +package org.gameyfin.app.core.serialization + +import io.mockk.every +import io.mockk.mockk +import io.mockk.unmockkAll +import org.gameyfin.pluginapi.gamemetadata.Genre +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import tools.jackson.core.JsonParser +import tools.jackson.databind.DeserializationContext +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class GenreDeserializerTest { + + private lateinit var deserializer: GenreDeserializer + private lateinit var jsonParser: JsonParser + private lateinit var deserializationContext: DeserializationContext + + @BeforeEach + fun setup() { + deserializer = GenreDeserializer() + jsonParser = mockk() + deserializationContext = mockk() + } + + @AfterEach + fun tearDown() { + unmockkAll() + } + + @Test + fun `deserialize should return correct genre for valid displayName`() { + every { jsonParser.string } returns "Action" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(Genre.ACTION, result) + } + + @Test + fun `deserialize should return null for unknown displayName`() { + every { jsonParser.string } returns "Unknown Genre" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertNull(result) + } + + @Test + fun `deserialize should return null for empty string`() { + every { jsonParser.string } returns "" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertNull(result) + } + + @Test + fun `deserialize should return null for null string`() { + every { jsonParser.string } returns null + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertNull(result) + } + + @Test + fun `deserialize should be case-sensitive`() { + every { jsonParser.string } returns "action" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertNull(result) + } + + @Test + fun `deserialize should handle Visual Novel genre`() { + every { jsonParser.string } returns "Visual Novel" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(Genre.VISUAL_NOVEL, result) + } + + @Test + fun `deserialize should handle Card & Board Game genre`() { + every { jsonParser.string } returns "Card & Board Game" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(Genre.CARD_AND_BOARD_GAME, result) + } + + @Test + fun `deserialize should handle Point-and-Click genre`() { + every { jsonParser.string } returns "Point-and-Click" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(Genre.POINT_AND_CLICK, result) + } + + @Test + fun `deserialize should handle Real-Time Strategy genre`() { + every { jsonParser.string } returns "Real-Time Strategy" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(Genre.REAL_TIME_STRATEGY, result) + } + + @Test + fun `deserialize should handle Turn-Based Strategy genre`() { + every { jsonParser.string } returns "Turn-Based Strategy" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(Genre.TURN_BASED_STRATEGY, result) + } + + @Test + fun `deserialize should handle Hack and Slash Beat em up genre`() { + every { jsonParser.string } returns "Hack and Slash/Beat 'em up" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(Genre.HACK_AND_SLASH_BEAT_EM_UP, result) + } + + @Test + fun `deserialize should handle Quiz Trivia genre`() { + every { jsonParser.string } returns "Quiz/Trivia" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(Genre.QUIZ_TRIVIA, result) + } + + @Test + fun `deserialize should handle Role-Playing genre`() { + every { jsonParser.string } returns "Role-Playing" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(Genre.ROLE_PLAYING, result) + } + + @Test + fun `deserialize should handle MOBA genre`() { + every { jsonParser.string } returns "MOBA" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(Genre.MOBA, result) + } + + @Test + fun `deserialize should handle MMO genre`() { + every { jsonParser.string } returns "MMO" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(Genre.MMO, result) + } + + @Test + fun `deserialize should handle all valid genre displayNames correctly`() { + Genre.entries.forEach { genre -> + every { jsonParser.string } returns genre.displayName + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(genre, result, "Failed to deserialize ${genre.displayName}") + } + } +} diff --git a/app/src/test/kotlin/org/gameyfin/app/core/serialization/PlayerPerspectiveDeserializerTest.kt b/app/src/test/kotlin/org/gameyfin/app/core/serialization/PlayerPerspectiveDeserializerTest.kt new file mode 100644 index 0000000..f706b72 --- /dev/null +++ b/app/src/test/kotlin/org/gameyfin/app/core/serialization/PlayerPerspectiveDeserializerTest.kt @@ -0,0 +1,151 @@ +package org.gameyfin.app.core.serialization + +import io.mockk.every +import io.mockk.mockk +import io.mockk.unmockkAll +import org.gameyfin.pluginapi.gamemetadata.PlayerPerspective +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import tools.jackson.core.JsonParser +import tools.jackson.databind.DeserializationContext +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class PlayerPerspectiveDeserializerTest { + + private lateinit var deserializer: PlayerPerspectiveDeserializer + private lateinit var jsonParser: JsonParser + private lateinit var deserializationContext: DeserializationContext + + @BeforeEach + fun setup() { + deserializer = PlayerPerspectiveDeserializer() + jsonParser = mockk() + deserializationContext = mockk() + } + + @AfterEach + fun tearDown() { + unmockkAll() + } + + @Test + fun `deserialize should return correct perspective for valid displayName`() { + every { jsonParser.string } returns "First-Person" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(PlayerPerspective.FIRST_PERSON, result) + } + + @Test + fun `deserialize should return null for unknown displayName`() { + every { jsonParser.string } returns "Unknown Perspective" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertNull(result) + } + + @Test + fun `deserialize should return null for empty string`() { + every { jsonParser.string } returns "" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertNull(result) + } + + @Test + fun `deserialize should return null for null string`() { + every { jsonParser.string } returns null + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertNull(result) + } + + @Test + fun `deserialize should be case-sensitive`() { + every { jsonParser.string } returns "first-person" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertNull(result) + } + + @Test + fun `deserialize should handle Third-Person perspective`() { + every { jsonParser.string } returns "Third-Person" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(PlayerPerspective.THIRD_PERSON, result) + } + + @Test + fun `deserialize should handle Bird View Isometric perspective`() { + every { jsonParser.string } returns "Bird View/Isometric" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(PlayerPerspective.BIRD_VIEW_ISOMETRIC, result) + } + + @Test + fun `deserialize should handle Side View perspective`() { + every { jsonParser.string } returns "Side View" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(PlayerPerspective.SIDE_VIEW, result) + } + + @Test + fun `deserialize should handle Text perspective`() { + every { jsonParser.string } returns "Text" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(PlayerPerspective.TEXT, result) + } + + @Test + fun `deserialize should handle Auditory perspective`() { + every { jsonParser.string } returns "Auditory" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(PlayerPerspective.AUDITORY, result) + } + + @Test + fun `deserialize should handle Virtual Reality perspective`() { + every { jsonParser.string } returns "Virtual Reality" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(PlayerPerspective.VIRTUAL_REALITY, result) + } + + @Test + fun `deserialize should handle Unknown perspective`() { + every { jsonParser.string } returns "Unknown" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(PlayerPerspective.UNKNOWN, result) + } + + @Test + fun `deserialize should handle all valid perspective displayNames correctly`() { + PlayerPerspective.entries.forEach { perspective -> + every { jsonParser.string } returns perspective.displayName + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(perspective, result, "Failed to deserialize ${perspective.displayName}") + } + } +} diff --git a/app/src/test/kotlin/org/gameyfin/app/core/serialization/ThemeDeserializerTest.kt b/app/src/test/kotlin/org/gameyfin/app/core/serialization/ThemeDeserializerTest.kt new file mode 100644 index 0000000..99ba63c --- /dev/null +++ b/app/src/test/kotlin/org/gameyfin/app/core/serialization/ThemeDeserializerTest.kt @@ -0,0 +1,151 @@ +package org.gameyfin.app.core.serialization + +import io.mockk.every +import io.mockk.mockk +import io.mockk.unmockkAll +import org.gameyfin.pluginapi.gamemetadata.Theme +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import tools.jackson.core.JsonParser +import tools.jackson.databind.DeserializationContext +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class ThemeDeserializerTest { + + private lateinit var deserializer: ThemeDeserializer + private lateinit var jsonParser: JsonParser + private lateinit var deserializationContext: DeserializationContext + + @BeforeEach + fun setup() { + deserializer = ThemeDeserializer() + jsonParser = mockk() + deserializationContext = mockk() + } + + @AfterEach + fun tearDown() { + unmockkAll() + } + + @Test + fun `deserialize should return correct theme for valid displayName`() { + every { jsonParser.string } returns "Action" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(Theme.ACTION, result) + } + + @Test + fun `deserialize should return null for unknown displayName`() { + every { jsonParser.string } returns "Unknown Theme" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertNull(result) + } + + @Test + fun `deserialize should return null for empty string`() { + every { jsonParser.string } returns "" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertNull(result) + } + + @Test + fun `deserialize should return null for null string`() { + every { jsonParser.string } returns null + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertNull(result) + } + + @Test + fun `deserialize should be case-sensitive`() { + every { jsonParser.string } returns "action" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertNull(result) + } + + @Test + fun `deserialize should handle Science Fiction theme`() { + every { jsonParser.string } returns "Science Fiction" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(Theme.SCIENCE_FICTION, result) + } + + @Test + fun `deserialize should handle Non-Fiction theme`() { + every { jsonParser.string } returns "Non-Fiction" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(Theme.NON_FICTION, result) + } + + @Test + fun `deserialize should handle 4X theme`() { + every { jsonParser.string } returns "4X" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(Theme.FOUR_X, result) + } + + @Test + fun `deserialize should handle Open World theme`() { + every { jsonParser.string } returns "Open World" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(Theme.OPEN_WORLD, result) + } + + @Test + fun `deserialize should handle Horror theme`() { + every { jsonParser.string } returns "Horror" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(Theme.HORROR, result) + } + + @Test + fun `deserialize should handle Fantasy theme`() { + every { jsonParser.string } returns "Fantasy" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(Theme.FANTASY, result) + } + + @Test + fun `deserialize should handle Survival theme`() { + every { jsonParser.string } returns "Survival" + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(Theme.SURVIVAL, result) + } + + @Test + fun `deserialize should handle all valid theme displayNames correctly`() { + Theme.entries.forEach { theme -> + every { jsonParser.string } returns theme.displayName + + val result = deserializer.deserialize(jsonParser, deserializationContext) + + assertEquals(theme, result, "Failed to deserialize ${theme.displayName}") + } + } +} diff --git a/app/src/test/kotlin/org/gameyfin/app/libraries/LibraryWatcherServiceTest.kt b/app/src/test/kotlin/org/gameyfin/app/libraries/LibraryWatcherServiceTest.kt index 74843fa..840517c 100644 --- a/app/src/test/kotlin/org/gameyfin/app/libraries/LibraryWatcherServiceTest.kt +++ b/app/src/test/kotlin/org/gameyfin/app/libraries/LibraryWatcherServiceTest.kt @@ -18,6 +18,8 @@ import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.condition.DisabledOnOs +import org.junit.jupiter.api.condition.OS import java.nio.file.Files import java.nio.file.Path import java.util.* @@ -169,6 +171,7 @@ class LibraryWatcherServiceTest { } @Test + @DisabledOnOs(OS.MAC, disabledReason = "File system watcher events are unreliable on macOS due to FSEvents latency") fun `file create event should trigger quick scan`() { val libraryDir = tempDir.resolve("watch-create") libraryDir.createDirectories() @@ -197,6 +200,7 @@ class LibraryWatcherServiceTest { } @Test + @DisabledOnOs(OS.MAC, disabledReason = "File system watcher events are unreliable on macOS due to FSEvents latency") fun `file delete event should trigger quick scan`() { val libraryDir = tempDir.resolve("watch-delete") libraryDir.createDirectories() @@ -227,6 +231,7 @@ class LibraryWatcherServiceTest { } @Test + @DisabledOnOs(OS.MAC, disabledReason = "File system watcher events are unreliable on macOS due to FSEvents latency") fun `directory delete event should trigger quick scan`() { val libraryDir = tempDir.resolve("watch-delete-dir") libraryDir.createDirectories() @@ -256,6 +261,7 @@ class LibraryWatcherServiceTest { } @Test + @DisabledOnOs(OS.MAC, disabledReason = "File system watcher events are unreliable on macOS due to FSEvents latency") fun `file modify event should update game file size`() { val libraryDir = tempDir.resolve("watch-modify") libraryDir.createDirectories() @@ -290,6 +296,7 @@ class LibraryWatcherServiceTest { } @Test + @DisabledOnOs(OS.MAC, disabledReason = "File system watcher events are unreliable on macOS due to FSEvents latency") fun `should handle multiple rapid file changes`() { val libraryDir = tempDir.resolve("watch-rapid") libraryDir.createDirectories() @@ -320,6 +327,7 @@ class LibraryWatcherServiceTest { } @Test + @DisabledOnOs(OS.MAC, disabledReason = "File system watcher events are unreliable on macOS due to FSEvents latency") fun `should handle library with multiple directories`() { val dir1 = tempDir.resolve("lib-dir1") val dir2 = tempDir.resolve("lib-dir2") @@ -354,6 +362,7 @@ class LibraryWatcherServiceTest { } @Test + @DisabledOnOs(OS.MAC, disabledReason = "File system watcher events are unreliable on macOS due to FSEvents latency") fun `should handle directory creation`() { val libraryDir = tempDir.resolve("watch-dir") libraryDir.createDirectories() diff --git a/app/src/test/kotlin/org/gameyfin/app/media/FileStorageServiceTest.kt b/app/src/test/kotlin/org/gameyfin/app/media/FileStorageServiceTest.kt new file mode 100644 index 0000000..e4cc115 --- /dev/null +++ b/app/src/test/kotlin/org/gameyfin/app/media/FileStorageServiceTest.kt @@ -0,0 +1,235 @@ +package org.gameyfin.app.media + +import io.mockk.clearAllMocks +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir +import java.io.ByteArrayInputStream +import java.nio.file.Files +import java.nio.file.Path +import kotlin.test.* + +class FileStorageServiceTest { + + @TempDir + lateinit var tempDir: Path + + private lateinit var fileStorageService: FileStorageService + + @BeforeEach + fun setup() { + fileStorageService = FileStorageService(tempDir.toString()) + } + + @AfterEach + fun tearDown() { + clearAllMocks() + } + + @Test + fun `init should create storage directory if it does not exist`() { + val newDir = tempDir.resolve("newStorage") + assertFalse(Files.exists(newDir)) + + FileStorageService(newDir.toString()) + + assertTrue(Files.exists(newDir)) + } + + @Test + fun `saveFile should store file and return content ID`() { + val testData = "test file content".toByteArray() + val inputStream = ByteArrayInputStream(testData) + + val contentId = fileStorageService.saveFile(inputStream) + + assertNotNull(contentId) + assertTrue(contentId.isNotEmpty()) + + val filePath = tempDir.resolve(contentId) + assertTrue(Files.exists(filePath)) + + val savedContent = Files.readAllBytes(filePath) + assertContentEquals(testData, savedContent) + } + + @Test + fun `saveFile should generate unique content IDs for multiple files`() { + val testData1 = "first file".toByteArray() + val testData2 = "second file".toByteArray() + + val contentId1 = fileStorageService.saveFile(ByteArrayInputStream(testData1)) + val contentId2 = fileStorageService.saveFile(ByteArrayInputStream(testData2)) + + assertNotEquals(contentId1, contentId2) + assertTrue(Files.exists(tempDir.resolve(contentId1))) + assertTrue(Files.exists(tempDir.resolve(contentId2))) + } + + @Test + fun `saveFile should replace existing file with same content ID`() { + val originalData = "original content".toByteArray() + val newData = "new content".toByteArray() + + val contentId = fileStorageService.saveFile(ByteArrayInputStream(originalData)) + + // Manually save a file with a predictable name + val filePath = tempDir.resolve(contentId) + Files.write(filePath, newData) + + val retrievedContent = Files.readAllBytes(filePath) + assertContentEquals(newData, retrievedContent) + } + + @Test + fun `getFile should return input stream when file exists`() { + val testData = "test file content".toByteArray() + val contentId = fileStorageService.saveFile(ByteArrayInputStream(testData)) + + val inputStream = fileStorageService.getFile(contentId) + + assertNotNull(inputStream) + val retrievedData = inputStream.readAllBytes() + assertContentEquals(testData, retrievedData) + } + + @Test + fun `getFile should return null when file does not exist`() { + val nonExistentId = "non-existent-file-id" + + val inputStream = fileStorageService.getFile(nonExistentId) + + assertNull(inputStream) + } + + @Test + fun `getFile should return null when content ID is null`() { + val inputStream = fileStorageService.getFile(null) + + assertNull(inputStream) + } + + @Test + fun `getFile should allow reading file multiple times`() { + val testData = "test file content".toByteArray() + val contentId = fileStorageService.saveFile(ByteArrayInputStream(testData)) + + val inputStream1 = fileStorageService.getFile(contentId) + assertNotNull(inputStream1) + val retrievedData1 = inputStream1.readAllBytes() + assertContentEquals(testData, retrievedData1) + + val inputStream2 = fileStorageService.getFile(contentId) + assertNotNull(inputStream2) + val retrievedData2 = inputStream2.readAllBytes() + assertContentEquals(testData, retrievedData2) + } + + @Test + fun `deleteFile should remove file when it exists`() { + val testData = "test file content".toByteArray() + val contentId = fileStorageService.saveFile(ByteArrayInputStream(testData)) + + assertTrue(Files.exists(tempDir.resolve(contentId))) + + fileStorageService.deleteFile(contentId) + + assertFalse(Files.exists(tempDir.resolve(contentId))) + } + + @Test + fun `deleteFile should not throw exception when file does not exist`() { + val nonExistentId = "non-existent-file-id" + + // Should not throw any exception + fileStorageService.deleteFile(nonExistentId) + + assertFalse(Files.exists(tempDir.resolve(nonExistentId))) + } + + @Test + fun `deleteFile should do nothing when content ID is null`() { + // Should not throw any exception + fileStorageService.deleteFile(null) + } + + @Test + fun `fileExists should return true when file exists`() { + val testData = "test file content".toByteArray() + val contentId = fileStorageService.saveFile(ByteArrayInputStream(testData)) + + val exists = fileStorageService.fileExists(contentId) + + assertTrue(exists) + } + + @Test + fun `fileExists should return false when file does not exist`() { + val nonExistentId = "non-existent-file-id" + + val exists = fileStorageService.fileExists(nonExistentId) + + assertFalse(exists) + } + + @Test + fun `fileExists should return false when content ID is null`() { + val exists = fileStorageService.fileExists(null) + + assertFalse(exists) + } + + @Test + fun `saveFile should handle large files`() { + val largeData = ByteArray(10 * 1024 * 1024) { it.toByte() } // 10 MB + val inputStream = ByteArrayInputStream(largeData) + + val contentId = fileStorageService.saveFile(inputStream) + + assertNotNull(contentId) + assertTrue(fileStorageService.fileExists(contentId)) + + val retrievedStream = fileStorageService.getFile(contentId) + assertNotNull(retrievedStream) + val retrievedData = retrievedStream.readAllBytes() + assertContentEquals(largeData, retrievedData) + } + + @Test + fun `saveFile should handle empty files`() { + val emptyData = ByteArray(0) + val inputStream = ByteArrayInputStream(emptyData) + + val contentId = fileStorageService.saveFile(inputStream) + + assertNotNull(contentId) + assertTrue(fileStorageService.fileExists(contentId)) + + val retrievedStream = fileStorageService.getFile(contentId) + assertNotNull(retrievedStream) + val retrievedData = retrievedStream.readAllBytes() + assertEquals(0, retrievedData.size) + } + + @Test + fun `integration test - save, retrieve, and delete file lifecycle`() { + val testData = "lifecycle test content".toByteArray() + + // Save file + val contentId = fileStorageService.saveFile(ByteArrayInputStream(testData)) + assertNotNull(contentId) + assertTrue(fileStorageService.fileExists(contentId)) + + // Retrieve file + val retrievedStream = fileStorageService.getFile(contentId) + assertNotNull(retrievedStream) + val retrievedData = retrievedStream.readAllBytes() + assertContentEquals(testData, retrievedData) + + // Delete file + fileStorageService.deleteFile(contentId) + assertFalse(fileStorageService.fileExists(contentId)) + assertNull(fileStorageService.getFile(contentId)) + } +} diff --git a/app/src/test/kotlin/org/gameyfin/app/media/ImageServiceTest.kt b/app/src/test/kotlin/org/gameyfin/app/media/ImageServiceTest.kt index 23a9530..83b4c94 100644 --- a/app/src/test/kotlin/org/gameyfin/app/media/ImageServiceTest.kt +++ b/app/src/test/kotlin/org/gameyfin/app/media/ImageServiceTest.kt @@ -8,7 +8,6 @@ import org.gameyfin.app.core.events.UserDeletedEvent import org.gameyfin.app.core.events.UserUpdatedEvent import org.gameyfin.app.games.entities.Game import org.gameyfin.app.games.repositories.GameRepository -import org.gameyfin.app.games.repositories.ImageContentStore import org.gameyfin.app.games.repositories.ImageRepository import org.gameyfin.app.users.entities.User import org.gameyfin.app.users.persistence.UserRepository @@ -19,7 +18,6 @@ import org.junit.jupiter.api.Test import org.springframework.dao.DataIntegrityViolationException import org.springframework.data.repository.findByIdOrNull import java.io.ByteArrayInputStream -import java.io.InputStream import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertNull @@ -27,7 +25,7 @@ import kotlin.test.assertNull class ImageServiceTest { private lateinit var imageRepository: ImageRepository - private lateinit var imageContentStore: ImageContentStore + private lateinit var fileStorageService: FileStorageService private lateinit var gameRepository: GameRepository private lateinit var userRepository: UserRepository private lateinit var imageService: ImageService @@ -35,10 +33,10 @@ class ImageServiceTest { @BeforeEach fun setup() { imageRepository = mockk() - imageContentStore = mockk() + fileStorageService = mockk() gameRepository = mockk() userRepository = mockk() - imageService = ImageService(imageRepository, imageContentStore, gameRepository, userRepository) + imageService = ImageService(imageRepository, fileStorageService, gameRepository, userRepository) } @AfterEach @@ -181,19 +179,16 @@ class ImageServiceTest { contentLength = 1024L, mimeType = "image/jpeg" ) - val inputStream = ByteArrayInputStream("image data".toByteArray()) every { imageRepository.findAllByOriginalUrl(url) } returns listOf(existingImage) - every { imageContentStore.getContent(existingImage) } returns inputStream - every { imageContentStore.associate(image, "existing-content-id") } just Runs + every { fileStorageService.fileExists("existing-content-id") } returns true imageService.downloadIfNew(image) assertEquals("existing-content-id", image.contentId) assertEquals(1024L, image.contentLength) assertEquals("image/jpeg", image.mimeType) - verify(exactly = 1) { imageContentStore.associate(image, "existing-content-id") } - verify(exactly = 0) { imageContentStore.setContent(any(), any()) } + verify(exactly = 0) { fileStorageService.saveFile(any()) } } @Test @@ -216,14 +211,13 @@ class ImageServiceTest { } every { imageRepository.findAllByOriginalUrl(url) } returns listOf(existingImage) - every { imageContentStore.getContent(existingImage) } returns null - every { imageContentStore.setContent(any(), any()) } returnsArgument 0 + every { fileStorageService.fileExists(null) } returns false + every { fileStorageService.saveFile(any()) } returns "new-content-id" every { imageRepository.save(image) } returns image imageService.downloadIfNew(image) - verify(exactly = 0) { imageContentStore.associate(any(), any()) } - verify(exactly = 1) { imageContentStore.setContent(image, any()) } + verify(exactly = 1) { fileStorageService.saveFile(any()) } verify(exactly = 1) { imageRepository.save(image) } unmockkStatic(TikaInputStream::class) } @@ -240,7 +234,6 @@ class ImageServiceTest { contentLength = 0L, mimeType = "image/jpeg" ) - val inputStream = ByteArrayInputStream("image data".toByteArray()) mockkStatic(TikaInputStream::class) val testData = "test image data".toByteArray() @@ -249,14 +242,13 @@ class ImageServiceTest { } every { imageRepository.findAllByOriginalUrl(url) } returns listOf(existingImage) - every { imageContentStore.getContent(existingImage) } returns inputStream - every { imageContentStore.setContent(any(), any()) } returnsArgument 0 + every { fileStorageService.fileExists("existing-content-id") } returns true + every { fileStorageService.saveFile(any()) } returns "new-content-id" every { imageRepository.save(image) } returns image imageService.downloadIfNew(image) - verify(exactly = 0) { imageContentStore.associate(any(), any()) } - verify(exactly = 1) { imageContentStore.setContent(image, any()) } + verify(exactly = 1) { fileStorageService.saveFile(any()) } verify(exactly = 1) { imageRepository.save(image) } unmockkStatic(TikaInputStream::class) } @@ -273,12 +265,12 @@ class ImageServiceTest { } every { imageRepository.findAllByOriginalUrl(url) } returns emptyList() - every { imageContentStore.setContent(any(), any()) } returnsArgument 0 + every { fileStorageService.saveFile(any()) } returns "new-content-id" every { imageRepository.save(image) } returns image imageService.downloadIfNew(image) - verify(exactly = 1) { imageContentStore.setContent(image, any()) } + verify(exactly = 1) { fileStorageService.saveFile(any()) } verify(exactly = 1) { imageRepository.save(image) } unmockkStatic(TikaInputStream::class) } @@ -295,12 +287,12 @@ class ImageServiceTest { } every { imageRepository.findAllByOriginalUrl(url) } returns emptyList() - every { imageContentStore.setContent(any(), any()) } returnsArgument 0 + every { fileStorageService.saveFile(any()) } returns "new-content-id" every { imageRepository.save(image) } throws DataIntegrityViolationException("Duplicate") imageService.downloadIfNew(image) - verify(exactly = 1) { imageContentStore.setContent(image, any()) } + verify(exactly = 1) { fileStorageService.saveFile(any()) } verify(exactly = 1) { imageRepository.save(image) } unmockkStatic(TikaInputStream::class) } @@ -311,13 +303,13 @@ class ImageServiceTest { val savedImage = Image(id = 1L, type = ImageType.AVATAR, mimeType = "image/png") every { imageRepository.save(any()) } returns savedImage - every { imageContentStore.setContent(any(), any()) } returns savedImage + every { fileStorageService.saveFile(any()) } returns "content-id" val result = imageService.createFromInputStream(ImageType.AVATAR, inputStream, "image/png") assertNotNull(result) verify(exactly = 1) { imageRepository.save(any()) } - verify(exactly = 1) { imageContentStore.setContent(any(), any()) } + verify(exactly = 1) { fileStorageService.saveFile(any()) } } @Test @@ -347,24 +339,24 @@ class ImageServiceTest { val image = Image(id = 1L, type = ImageType.COVER, contentId = "content-id") val inputStream = ByteArrayInputStream("image data".toByteArray()) - every { imageContentStore.getContent(image) } returns inputStream + every { fileStorageService.getFile("content-id") } returns inputStream val result = imageService.getFileContent(image) assertEquals(inputStream, result) - verify(exactly = 1) { imageContentStore.getContent(image) } + verify(exactly = 1) { fileStorageService.getFile("content-id") } } @Test fun `getFileContent should return null when content store returns null`() { val image = Image(id = 1L, type = ImageType.COVER, contentId = "content-id") - every { imageContentStore.getContent(image) } returns null + every { fileStorageService.getFile("content-id") } returns null val result = imageService.getFileContent(image) assertNull(result) - verify(exactly = 1) { imageContentStore.getContent(image) } + verify(exactly = 1) { fileStorageService.getFile("content-id") } } @Test @@ -374,14 +366,14 @@ class ImageServiceTest { every { gameRepository.existsByImage(1L) } returns false every { userRepository.existsByAvatar(1L) } returns false every { imageRepository.delete(image) } just Runs - every { imageContentStore.unsetContent(image) } returnsArgument 0 + every { fileStorageService.deleteFile(any()) } just Runs imageService.deleteImageIfUnused(image) verify(exactly = 1) { gameRepository.existsByImage(1L) } verify(exactly = 1) { userRepository.existsByAvatar(1L) } verify(exactly = 1) { imageRepository.delete(image) } - verify(exactly = 1) { imageContentStore.unsetContent(image) } + verify(exactly = 1) { fileStorageService.deleteFile(any()) } } @Test @@ -395,7 +387,7 @@ class ImageServiceTest { verify(exactly = 1) { gameRepository.existsByImage(1L) } verify(exactly = 0) { userRepository.existsByAvatar(any()) } verify(exactly = 0) { imageRepository.delete(any()) } - verify(exactly = 0) { imageContentStore.unsetContent(any()) } + verify(exactly = 0) { fileStorageService.deleteFile(any()) } } @Test @@ -410,7 +402,7 @@ class ImageServiceTest { verify(exactly = 1) { gameRepository.existsByImage(1L) } verify(exactly = 1) { userRepository.existsByAvatar(1L) } verify(exactly = 0) { imageRepository.delete(any()) } - verify(exactly = 0) { imageContentStore.unsetContent(any()) } + verify(exactly = 0) { fileStorageService.deleteFile(any()) } } @Test @@ -422,7 +414,7 @@ class ImageServiceTest { verify(exactly = 0) { gameRepository.existsByImage(any()) } verify(exactly = 0) { userRepository.existsByAvatar(any()) } verify(exactly = 0) { imageRepository.delete(any()) } - verify(exactly = 0) { imageContentStore.unsetContent(any()) } + verify(exactly = 0) { fileStorageService.deleteFile(any()) } } @Test @@ -431,13 +423,14 @@ class ImageServiceTest { val inputStream = ByteArrayInputStream("new image data".toByteArray()) every { imageRepository.save(image) } returns image - every { imageContentStore.setContent(any(), any()) } returns image + every { fileStorageService.deleteFile(any()) } just Runs + every { fileStorageService.saveFile(any()) } returns "new-content-id" imageService.updateFileContent(image, inputStream, "image/jpeg") assertEquals("image/jpeg", image.mimeType) verify(exactly = 1) { imageRepository.save(image) } - verify(exactly = 1) { imageContentStore.setContent(image, any()) } + verify(exactly = 1) { fileStorageService.saveFile(any()) } } @Test @@ -446,13 +439,14 @@ class ImageServiceTest { val inputStream = ByteArrayInputStream("new image data".toByteArray()) every { imageRepository.save(image) } returns image - every { imageContentStore.setContent(any(), any()) } returns image + every { fileStorageService.deleteFile(any()) } just Runs + every { fileStorageService.saveFile(any()) } returns "new-content-id" imageService.updateFileContent(image, inputStream) assertEquals("image/png", image.mimeType) verify(exactly = 1) { imageRepository.save(image) } - verify(exactly = 1) { imageContentStore.setContent(image, any()) } + verify(exactly = 1) { fileStorageService.saveFile(any()) } } @Test @@ -474,7 +468,7 @@ class ImageServiceTest { every { gameRepository.existsByImage(3L) } returns false every { userRepository.existsByAvatar(3L) } returns false every { imageRepository.delete(any()) } just Runs - every { imageContentStore.unsetContent(any()) } returnsArgument 0 + every { fileStorageService.deleteFile(any()) } just Runs imageService.onGameDeleted(event) @@ -496,12 +490,12 @@ class ImageServiceTest { every { gameRepository.existsByImage(1L) } returns false every { userRepository.existsByAvatar(1L) } returns false every { imageRepository.delete(screenshot) } just Runs - every { imageContentStore.unsetContent(screenshot) } returnsArgument 0 + every { fileStorageService.deleteFile(any()) } just Runs imageService.onGameDeleted(event) verify(exactly = 1) { imageRepository.delete(screenshot) } - verify(exactly = 1) { imageContentStore.unsetContent(screenshot) } + verify(exactly = 1) { fileStorageService.deleteFile(any()) } } @Test @@ -528,7 +522,7 @@ class ImageServiceTest { every { gameRepository.existsByImage(3L) } returns false every { userRepository.existsByAvatar(3L) } returns false every { imageRepository.delete(any()) } just Runs - every { imageContentStore.unsetContent(any()) } returnsArgument 0 + every { fileStorageService.deleteFile(any()) } just Runs imageService.onGameUpdated(event) @@ -558,7 +552,7 @@ class ImageServiceTest { imageService.onGameUpdated(event) verify(exactly = 0) { imageRepository.delete(any()) } - verify(exactly = 0) { imageContentStore.unsetContent(any()) } + verify(exactly = 0) { fileStorageService.deleteFile(any()) } } @Test @@ -572,12 +566,12 @@ class ImageServiceTest { every { gameRepository.existsByImage(1L) } returns false every { userRepository.existsByAvatar(1L) } returns false every { imageRepository.delete(avatar) } just Runs - every { imageContentStore.unsetContent(avatar) } returnsArgument 0 + every { fileStorageService.deleteFile(any()) } just Runs imageService.onAccountDeleted(event) verify(exactly = 1) { imageRepository.delete(avatar) } - verify(exactly = 1) { imageContentStore.unsetContent(avatar) } + verify(exactly = 1) { fileStorageService.deleteFile(any()) } } @Test @@ -590,7 +584,7 @@ class ImageServiceTest { imageService.onAccountDeleted(event) verify(exactly = 0) { imageRepository.delete(any()) } - verify(exactly = 0) { imageContentStore.unsetContent(any()) } + verify(exactly = 0) { fileStorageService.deleteFile(any()) } } @Test @@ -608,12 +602,12 @@ class ImageServiceTest { every { gameRepository.existsByImage(1L) } returns false every { userRepository.existsByAvatar(1L) } returns false every { imageRepository.delete(oldAvatar) } just Runs - every { imageContentStore.unsetContent(oldAvatar) } returnsArgument 0 + every { fileStorageService.deleteFile(any()) } just Runs imageService.onUserUpdated(event) verify(exactly = 1) { imageRepository.delete(oldAvatar) } - verify(exactly = 1) { imageContentStore.unsetContent(oldAvatar) } + verify(exactly = 1) { fileStorageService.deleteFile(any()) } } @Test @@ -630,7 +624,7 @@ class ImageServiceTest { imageService.onUserUpdated(event) verify(exactly = 0) { imageRepository.delete(any()) } - verify(exactly = 0) { imageContentStore.unsetContent(any()) } + verify(exactly = 0) { fileStorageService.deleteFile(any()) } } @Test @@ -647,7 +641,7 @@ class ImageServiceTest { imageService.onUserUpdated(event) verify(exactly = 0) { imageRepository.delete(any()) } - verify(exactly = 0) { imageContentStore.unsetContent(any()) } + verify(exactly = 0) { fileStorageService.deleteFile(any()) } } @Test @@ -664,11 +658,11 @@ class ImageServiceTest { every { gameRepository.existsByImage(1L) } returns false every { userRepository.existsByAvatar(1L) } returns false every { imageRepository.delete(oldAvatar) } just Runs - every { imageContentStore.unsetContent(oldAvatar) } returnsArgument 0 + every { fileStorageService.deleteFile(any()) } just Runs imageService.onUserUpdated(event) verify(exactly = 1) { imageRepository.delete(oldAvatar) } - verify(exactly = 1) { imageContentStore.unsetContent(oldAvatar) } + verify(exactly = 1) { fileStorageService.deleteFile(any()) } } } diff --git a/app/src/test/kotlin/org/gameyfin/app/messages/MessageServiceTest.kt b/app/src/test/kotlin/org/gameyfin/app/messages/MessageServiceTest.kt index 36cd945..ba40ea9 100644 --- a/app/src/test/kotlin/org/gameyfin/app/messages/MessageServiceTest.kt +++ b/app/src/test/kotlin/org/gameyfin/app/messages/MessageServiceTest.kt @@ -17,6 +17,7 @@ import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.springframework.beans.factory.getBeansOfType import org.springframework.context.ApplicationContext import org.springframework.security.core.Authentication import org.springframework.security.core.context.SecurityContext @@ -55,7 +56,7 @@ class MessageServiceTest { fun `enabled should return true when at least one provider is enabled`() { every { mockProvider1.enabled } returns true every { mockProvider2.enabled } returns false - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns mapOf( + every { applicationContext.getBeansOfType() } returns mapOf( "provider1" to mockProvider1, "provider2" to mockProvider2 ) @@ -69,7 +70,7 @@ class MessageServiceTest { fun `enabled should return false when no providers are enabled`() { every { mockProvider1.enabled } returns false every { mockProvider2.enabled } returns false - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns mapOf( + every { applicationContext.getBeansOfType() } returns mapOf( "provider1" to mockProvider1, "provider2" to mockProvider2 ) @@ -81,7 +82,7 @@ class MessageServiceTest { @Test fun `enabled should return false when no providers exist`() { - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns emptyMap() + every { applicationContext.getBeansOfType() } returns emptyMap() val result = messageService.enabled @@ -95,7 +96,7 @@ class MessageServiceTest { every { mockProvider1.providerKey } returns providerKey every { mockProvider1.testCredentials(any()) } returns true - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns mapOf( + every { applicationContext.getBeansOfType() } returns mapOf( "provider1" to mockProvider1 ) @@ -112,7 +113,7 @@ class MessageServiceTest { every { mockProvider1.providerKey } returns providerKey every { mockProvider1.testCredentials(any()) } returns false - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns mapOf( + every { applicationContext.getBeansOfType() } returns mapOf( "provider1" to mockProvider1 ) @@ -127,7 +128,7 @@ class MessageServiceTest { val credentials = mapOf("host" to "smtp.example.com") every { mockProvider1.providerKey } returns "email" - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns mapOf( + every { applicationContext.getBeansOfType() } returns mapOf( "provider1" to mockProvider1 ) @@ -145,7 +146,7 @@ class MessageServiceTest { every { mockProvider1.providerKey } returns providerKey every { mockProvider1.testCredentials(any()) } returns true - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns mapOf( + every { applicationContext.getBeansOfType() } returns mapOf( "provider1" to mockProvider1 ) @@ -167,7 +168,7 @@ class MessageServiceTest { every { mockProvider1.supportedTemplateType } returns TemplateType.MJML every { mockProvider2.enabled } returns true every { mockProvider2.supportedTemplateType } returns TemplateType.TEXT - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns mapOf( + every { applicationContext.getBeansOfType() } returns mapOf( "provider1" to mockProvider1, "provider2" to mockProvider2 ) @@ -197,7 +198,7 @@ class MessageServiceTest { every { mockProvider1.enabled } returns true every { mockProvider1.supportedTemplateType } returns TemplateType.MJML every { mockProvider2.enabled } returns false - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns mapOf( + every { applicationContext.getBeansOfType() } returns mapOf( "provider1" to mockProvider1, "provider2" to mockProvider2 ) @@ -218,7 +219,7 @@ class MessageServiceTest { every { mockProvider1.enabled } returns false every { mockProvider2.enabled } returns false - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns mapOf( + every { applicationContext.getBeansOfType() } returns mapOf( "provider1" to mockProvider1, "provider2" to mockProvider2 ) @@ -231,7 +232,7 @@ class MessageServiceTest { @Test fun `sendTestNotification should return false when messaging is disabled`() { - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns emptyMap() + every { applicationContext.getBeansOfType() } returns emptyMap() val result = messageService.sendTestNotification("password-reset-request", emptyMap()) @@ -249,7 +250,7 @@ class MessageServiceTest { every { mockProvider1.enabled } returns true every { mockProvider1.supportedTemplateType } returns TemplateType.MJML - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns mapOf( + every { applicationContext.getBeansOfType() } returns mapOf( "provider1" to mockProvider1 ) every { userService.getByUsername("testuser") } returns user @@ -274,7 +275,7 @@ class MessageServiceTest { val placeholders = mapOf("username" to "testuser", "resetLink" to "http://example.com/reset") every { mockProvider1.enabled } returns true - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns mapOf( + every { applicationContext.getBeansOfType() } returns mapOf( "provider1" to mockProvider1 ) @@ -291,7 +292,7 @@ class MessageServiceTest { setupSecurityContext("testuser") every { mockProvider1.enabled } returns true - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns mapOf( + every { applicationContext.getBeansOfType() } returns mapOf( "provider1" to mockProvider1 ) every { userService.getByUsername("testuser") } returns null @@ -310,7 +311,7 @@ class MessageServiceTest { setupSecurityContext("testuser") every { mockProvider1.enabled } returns true - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns mapOf( + every { applicationContext.getBeansOfType() } returns mapOf( "provider1" to mockProvider1 ) every { userService.getByUsername("testuser") } returns user @@ -330,7 +331,7 @@ class MessageServiceTest { every { mockProvider1.enabled } returns true every { mockProvider1.supportedTemplateType } returns TemplateType.MJML - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns mapOf( + every { applicationContext.getBeansOfType() } returns mapOf( "provider1" to mockProvider1 ) every { @@ -358,7 +359,7 @@ class MessageServiceTest { val token = Token(creator = user, secret = "secret123", type = TokenType.PasswordReset) val event = PasswordResetRequestEvent(this, token, "http://example.com") - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns emptyMap() + every { applicationContext.getBeansOfType() } returns emptyMap() messageService.onPasswordResetRequest(event) @@ -373,7 +374,7 @@ class MessageServiceTest { every { mockProvider1.enabled } returns true every { mockProvider1.supportedTemplateType } returns TemplateType.MJML - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns mapOf( + every { applicationContext.getBeansOfType() } returns mapOf( "provider1" to mockProvider1 ) every { @@ -394,7 +395,7 @@ class MessageServiceTest { val user = User(username = "newuser", email = "new@example.com", password = "hash") val event = UserRegistrationWaitingForApprovalEvent(this, user) - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns emptyMap() + every { applicationContext.getBeansOfType() } returns emptyMap() messageService.onUserRegistrationWaitingForApproval(event) @@ -409,7 +410,7 @@ class MessageServiceTest { every { mockProvider1.enabled } returns true every { mockProvider1.supportedTemplateType } returns TemplateType.MJML - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns mapOf( + every { applicationContext.getBeansOfType() } returns mapOf( "provider1" to mockProvider1 ) every { @@ -439,7 +440,7 @@ class MessageServiceTest { every { mockProvider1.enabled } returns true every { mockProvider1.supportedTemplateType } returns TemplateType.MJML - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns mapOf( + every { applicationContext.getBeansOfType() } returns mapOf( "provider1" to mockProvider1 ) every { @@ -466,7 +467,7 @@ class MessageServiceTest { val user = User(username = "testuser", email = "user@example.com", password = "hash") val event = AccountStatusChangedEvent(this, user, "http://example.com") - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns emptyMap() + every { applicationContext.getBeansOfType() } returns emptyMap() messageService.onAccountStatusChanged(event) @@ -481,7 +482,7 @@ class MessageServiceTest { every { mockProvider1.enabled } returns true every { mockProvider1.supportedTemplateType } returns TemplateType.MJML - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns mapOf( + every { applicationContext.getBeansOfType() } returns mapOf( "provider1" to mockProvider1 ) every { @@ -502,7 +503,7 @@ class MessageServiceTest { val user = User(username = "existinguser", email = "existing@example.com", password = "hash") val event = RegistrationAttemptWithExistingEmailEvent(this, user, "http://example.com") - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns emptyMap() + every { applicationContext.getBeansOfType() } returns emptyMap() messageService.onRegistrationAttemptWithExistingEmail(event) @@ -518,7 +519,7 @@ class MessageServiceTest { every { mockProvider1.enabled } returns true every { mockProvider1.supportedTemplateType } returns TemplateType.MJML - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns mapOf( + every { applicationContext.getBeansOfType() } returns mapOf( "provider1" to mockProvider1 ) every { @@ -543,7 +544,7 @@ class MessageServiceTest { val token = Token(creator = user, secret = "confirm123", type = TokenType.EmailConfirmation) val event = EmailNeedsConfirmationEvent(this, token, "http://example.com") - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns emptyMap() + every { applicationContext.getBeansOfType() } returns emptyMap() messageService.onEmailNeedsConfirmation(event) @@ -559,7 +560,7 @@ class MessageServiceTest { every { mockProvider1.enabled } returns true every { mockProvider1.supportedTemplateType } returns TemplateType.MJML - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns mapOf( + every { applicationContext.getBeansOfType() } returns mapOf( "provider1" to mockProvider1 ) every { @@ -587,7 +588,7 @@ class MessageServiceTest { val token = Token(creator = user, secret = "invite123", type = TokenType.Invitation) val event = UserInvitationEvent(this, token, "http://example.com", "invited@example.com") - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns emptyMap() + every { applicationContext.getBeansOfType() } returns emptyMap() messageService.onUserInvitation(event) @@ -602,7 +603,7 @@ class MessageServiceTest { every { mockProvider1.enabled } returns true every { mockProvider1.supportedTemplateType } returns TemplateType.MJML - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns mapOf( + every { applicationContext.getBeansOfType() } returns mapOf( "provider1" to mockProvider1 ) every { @@ -629,7 +630,7 @@ class MessageServiceTest { val user = User(username = "deleteduser", email = "deleted@example.com", password = "hash") val event = UserDeletedEvent(this, user, "http://example.com") - every { applicationContext.getBeansOfType(AbstractMessageProvider::class.java) } returns emptyMap() + every { applicationContext.getBeansOfType() } returns emptyMap() messageService.onAccountDeletion(event) diff --git a/app/src/test/kotlin/org/gameyfin/app/platforms/serialization/PlatformDeserializerTest.kt b/app/src/test/kotlin/org/gameyfin/app/platforms/serialization/PlatformDeserializerTest.kt index aa3e8fc..0ca65ae 100644 --- a/app/src/test/kotlin/org/gameyfin/app/platforms/serialization/PlatformDeserializerTest.kt +++ b/app/src/test/kotlin/org/gameyfin/app/platforms/serialization/PlatformDeserializerTest.kt @@ -1,7 +1,5 @@ package org.gameyfin.app.platforms.serialization -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext import io.mockk.every import io.mockk.mockk import io.mockk.unmockkAll @@ -10,6 +8,8 @@ import org.gameyfin.pluginapi.gamemetadata.Platform import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import tools.jackson.core.JsonParser +import tools.jackson.databind.DeserializationContext import kotlin.test.assertEquals import kotlin.test.assertNull @@ -33,7 +33,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should return correct platform for valid displayName`() { - every { jsonParser.text } returns "PC (Microsoft Windows)" + every { jsonParser.string } returns "PC (Microsoft Windows)" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -42,7 +42,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should return null for unknown displayName`() { - every { jsonParser.text } returns "Unknown Platform" + every { jsonParser.string } returns "Unknown Platform" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -51,7 +51,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should return null for empty string`() { - every { jsonParser.text } returns "" + every { jsonParser.string } returns "" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -60,7 +60,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should return correct platform for PlayStation 5`() { - every { jsonParser.text } returns "PlayStation 5" + every { jsonParser.string } returns "PlayStation 5" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -69,7 +69,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should return correct platform for Xbox Series X S`() { - every { jsonParser.text } returns "Xbox Series X|S" + every { jsonParser.string } returns "Xbox Series X|S" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -78,7 +78,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should return correct platform for Nintendo Switch`() { - every { jsonParser.text } returns "Nintendo Switch" + every { jsonParser.string } returns "Nintendo Switch" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -87,7 +87,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should be case-sensitive`() { - every { jsonParser.text } returns "playstation 5" + every { jsonParser.string } returns "playstation 5" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -96,7 +96,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should handle platforms with special characters`() { - every { jsonParser.text } returns "Odyssey 2 / Videopac G7000" + every { jsonParser.string } returns "Odyssey 2 / Videopac G7000" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -105,7 +105,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should handle platforms with numbers at start`() { - every { jsonParser.text } returns "3DO Interactive Multiplayer" + every { jsonParser.string } returns "3DO Interactive Multiplayer" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -114,7 +114,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should handle platforms with hyphens`() { - every { jsonParser.text } returns "Atari 8-bit" + every { jsonParser.string } returns "Atari 8-bit" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -123,7 +123,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should handle platforms with apostrophes`() { - every { jsonParser.text } returns "Super A'Can" + every { jsonParser.string } returns "Super A'Can" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -132,7 +132,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should return null for whitespace-only string`() { - every { jsonParser.text } returns " " + every { jsonParser.string } returns " " val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -141,7 +141,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should not trim whitespace from displayName`() { - every { jsonParser.text } returns " PlayStation 5 " + every { jsonParser.string } returns " PlayStation 5 " val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -150,7 +150,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should handle Arcade platform`() { - every { jsonParser.text } returns "Arcade" + every { jsonParser.string } returns "Arcade" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -159,7 +159,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should handle Web browser platform`() { - every { jsonParser.text } returns "Web browser" + every { jsonParser.string } returns "Web browser" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -168,7 +168,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should handle Android platform`() { - every { jsonParser.text } returns "Android" + every { jsonParser.string } returns "Android" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -177,7 +177,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should handle iOS platform`() { - every { jsonParser.text } returns "iOS" + every { jsonParser.string } returns "iOS" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -186,7 +186,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should handle Linux platform`() { - every { jsonParser.text } returns "Linux" + every { jsonParser.string } returns "Linux" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -195,7 +195,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should handle Mac platform`() { - every { jsonParser.text } returns "Mac" + every { jsonParser.string } returns "Mac" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -204,7 +204,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should handle DOS platform`() { - every { jsonParser.text } returns "DOS" + every { jsonParser.string } returns "DOS" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -213,7 +213,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should handle Dreamcast platform`() { - every { jsonParser.text } returns "Dreamcast" + every { jsonParser.string } returns "Dreamcast" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -222,7 +222,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should handle Virtual Boy platform`() { - every { jsonParser.text } returns "Virtual Boy" + every { jsonParser.string } returns "Virtual Boy" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -231,7 +231,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should handle ZX Spectrum platform`() { - every { jsonParser.text } returns "ZX Spectrum" + every { jsonParser.string } returns "ZX Spectrum" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -240,7 +240,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should handle Game Boy platform`() { - every { jsonParser.text } returns "Game Boy" + every { jsonParser.string } returns "Game Boy" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -249,7 +249,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should handle PlayStation VR2 platform`() { - every { jsonParser.text } returns "PlayStation VR2" + every { jsonParser.string } returns "PlayStation VR2" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -258,7 +258,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should handle Nintendo Entertainment System platform`() { - every { jsonParser.text } returns "Nintendo Entertainment System" + every { jsonParser.string } returns "Nintendo Entertainment System" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -267,7 +267,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should handle Super Nintendo Entertainment System platform`() { - every { jsonParser.text } returns "Super Nintendo Entertainment System" + every { jsonParser.string } returns "Super Nintendo Entertainment System" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -276,7 +276,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should handle Sega Mega Drive Genesis platform`() { - every { jsonParser.text } returns "Sega Mega Drive/Genesis" + every { jsonParser.string } returns "Sega Mega Drive/Genesis" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -285,7 +285,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should handle platforms with long names`() { - every { jsonParser.text } returns "Call-A-Computer time-shared mainframe computer system" + every { jsonParser.string } returns "Call-A-Computer time-shared mainframe computer system" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -294,7 +294,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should return null for partial match`() { - every { jsonParser.text } returns "PlayStation" + every { jsonParser.string } returns "PlayStation" val result = deserializer.deserialize(jsonParser, deserializationContext) @@ -304,7 +304,7 @@ class PlatformDeserializerTest { @Test fun `deserialize should handle all valid platform displayNames correctly`() { Platform.entries.forEach { platform -> - every { jsonParser.text } returns platform.displayName + every { jsonParser.string } returns platform.displayName val result = deserializer.deserialize(jsonParser, deserializationContext) diff --git a/app/src/test/kotlin/org/gameyfin/app/platforms/serialization/PlatformSerializerTest.kt b/app/src/test/kotlin/org/gameyfin/app/platforms/serialization/PlatformSerializerTest.kt index fd91266..339aee4 100644 --- a/app/src/test/kotlin/org/gameyfin/app/platforms/serialization/PlatformSerializerTest.kt +++ b/app/src/test/kotlin/org/gameyfin/app/platforms/serialization/PlatformSerializerTest.kt @@ -1,7 +1,5 @@ package org.gameyfin.app.platforms.serialization -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.databind.SerializerProvider import io.mockk.mockk import io.mockk.unmockkAll import io.mockk.verify @@ -10,18 +8,20 @@ import org.gameyfin.pluginapi.gamemetadata.Platform import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import tools.jackson.core.JsonGenerator +import tools.jackson.databind.SerializationContext class PlatformSerializerTest { private lateinit var serializer: DisplayableSerializer private lateinit var jsonGenerator: JsonGenerator - private lateinit var serializerProvider: SerializerProvider + private lateinit var serializationContext: SerializationContext @BeforeEach fun setup() { serializer = DisplayableSerializer() jsonGenerator = mockk(relaxed = true) - serializerProvider = mockk() + serializationContext = mockk() } @AfterEach @@ -33,14 +33,14 @@ class PlatformSerializerTest { fun `serialize should write displayName for valid platform`() { val platform = Platform.PC_MICROSOFT_WINDOWS - serializer.serialize(platform, jsonGenerator, serializerProvider) + serializer.serialize(platform, jsonGenerator, serializationContext) verify(exactly = 1) { jsonGenerator.writeString("PC (Microsoft Windows)") } } @Test fun `serialize should handle null platform value`() { - serializer.serialize(null, jsonGenerator, serializerProvider) + serializer.serialize(null, jsonGenerator, serializationContext) verify(exactly = 0) { jsonGenerator.writeString(any()) } } @@ -49,7 +49,7 @@ class PlatformSerializerTest { fun `serialize should write correct displayName for PlayStation 5`() { val platform = Platform.PLAYSTATION_5 - serializer.serialize(platform, jsonGenerator, serializerProvider) + serializer.serialize(platform, jsonGenerator, serializationContext) verify(exactly = 1) { jsonGenerator.writeString("PlayStation 5") } } @@ -58,7 +58,7 @@ class PlatformSerializerTest { fun `serialize should write correct displayName for Xbox Series X S`() { val platform = Platform.XBOX_SERIES_X_S - serializer.serialize(platform, jsonGenerator, serializerProvider) + serializer.serialize(platform, jsonGenerator, serializationContext) verify(exactly = 1) { jsonGenerator.writeString("Xbox Series X|S") } } @@ -67,7 +67,7 @@ class PlatformSerializerTest { fun `serialize should write correct displayName for Nintendo Switch`() { val platform = Platform.NINTENDO_SWITCH - serializer.serialize(platform, jsonGenerator, serializerProvider) + serializer.serialize(platform, jsonGenerator, serializationContext) verify(exactly = 1) { jsonGenerator.writeString("Nintendo Switch") } } @@ -76,7 +76,7 @@ class PlatformSerializerTest { fun `serialize should handle platforms with special characters in name`() { val platform = Platform.ODYSSEY_2_VIDEOPAC_G7000 - serializer.serialize(platform, jsonGenerator, serializerProvider) + serializer.serialize(platform, jsonGenerator, serializationContext) verify(exactly = 1) { jsonGenerator.writeString("Odyssey 2 / Videopac G7000") } } @@ -85,7 +85,7 @@ class PlatformSerializerTest { fun `serialize should handle platforms with numbers in name`() { val platform = Platform._3DO_INTERACTIVE_MULTIPLAYER - serializer.serialize(platform, jsonGenerator, serializerProvider) + serializer.serialize(platform, jsonGenerator, serializationContext) verify(exactly = 1) { jsonGenerator.writeString("3DO Interactive Multiplayer") } } @@ -94,7 +94,7 @@ class PlatformSerializerTest { fun `serialize should handle platforms with hyphens in name`() { val platform = Platform.ATARI_8_BIT - serializer.serialize(platform, jsonGenerator, serializerProvider) + serializer.serialize(platform, jsonGenerator, serializationContext) verify(exactly = 1) { jsonGenerator.writeString("Atari 8-bit") } } @@ -103,7 +103,7 @@ class PlatformSerializerTest { fun `serialize should handle platforms with apostrophes in name`() { val platform = Platform.SUPER_ACAN - serializer.serialize(platform, jsonGenerator, serializerProvider) + serializer.serialize(platform, jsonGenerator, serializationContext) verify(exactly = 1) { jsonGenerator.writeString("Super A'Can") } } @@ -112,7 +112,7 @@ class PlatformSerializerTest { fun `serialize should handle arcade platform`() { val platform = Platform.ARCADE - serializer.serialize(platform, jsonGenerator, serializerProvider) + serializer.serialize(platform, jsonGenerator, serializationContext) verify(exactly = 1) { jsonGenerator.writeString("Arcade") } } @@ -121,7 +121,7 @@ class PlatformSerializerTest { fun `serialize should handle web browser platform`() { val platform = Platform.WEB_BROWSER - serializer.serialize(platform, jsonGenerator, serializerProvider) + serializer.serialize(platform, jsonGenerator, serializationContext) verify(exactly = 1) { jsonGenerator.writeString("Web browser") } } @@ -130,7 +130,7 @@ class PlatformSerializerTest { fun `serialize should handle Android platform`() { val platform = Platform.ANDROID - serializer.serialize(platform, jsonGenerator, serializerProvider) + serializer.serialize(platform, jsonGenerator, serializationContext) verify(exactly = 1) { jsonGenerator.writeString("Android") } } @@ -139,7 +139,7 @@ class PlatformSerializerTest { fun `serialize should handle iOS platform`() { val platform = Platform.IOS - serializer.serialize(platform, jsonGenerator, serializerProvider) + serializer.serialize(platform, jsonGenerator, serializationContext) verify(exactly = 1) { jsonGenerator.writeString("iOS") } } @@ -148,7 +148,7 @@ class PlatformSerializerTest { fun `serialize should handle Linux platform`() { val platform = Platform.LINUX - serializer.serialize(platform, jsonGenerator, serializerProvider) + serializer.serialize(platform, jsonGenerator, serializationContext) verify(exactly = 1) { jsonGenerator.writeString("Linux") } } @@ -157,7 +157,7 @@ class PlatformSerializerTest { fun `serialize should handle Mac platform`() { val platform = Platform.MAC - serializer.serialize(platform, jsonGenerator, serializerProvider) + serializer.serialize(platform, jsonGenerator, serializationContext) verify(exactly = 1) { jsonGenerator.writeString("Mac") } } @@ -166,7 +166,7 @@ class PlatformSerializerTest { fun `serialize should handle DOS platform`() { val platform = Platform.DOS - serializer.serialize(platform, jsonGenerator, serializerProvider) + serializer.serialize(platform, jsonGenerator, serializationContext) verify(exactly = 1) { jsonGenerator.writeString("DOS") } } @@ -175,7 +175,7 @@ class PlatformSerializerTest { fun `serialize should handle Dreamcast platform`() { val platform = Platform.DREAMCAST - serializer.serialize(platform, jsonGenerator, serializerProvider) + serializer.serialize(platform, jsonGenerator, serializationContext) verify(exactly = 1) { jsonGenerator.writeString("Dreamcast") } } @@ -184,7 +184,7 @@ class PlatformSerializerTest { fun `serialize should handle Virtual Boy platform`() { val platform = Platform.VIRTUAL_BOY - serializer.serialize(platform, jsonGenerator, serializerProvider) + serializer.serialize(platform, jsonGenerator, serializationContext) verify(exactly = 1) { jsonGenerator.writeString("Virtual Boy") } } @@ -193,7 +193,7 @@ class PlatformSerializerTest { fun `serialize should handle ZX Spectrum platform`() { val platform = Platform.ZX_SPECTRUM - serializer.serialize(platform, jsonGenerator, serializerProvider) + serializer.serialize(platform, jsonGenerator, serializationContext) verify(exactly = 1) { jsonGenerator.writeString("ZX Spectrum") } } @@ -202,7 +202,7 @@ class PlatformSerializerTest { fun `serialize should handle Game Boy platform`() { val platform = Platform.GAME_BOY - serializer.serialize(platform, jsonGenerator, serializerProvider) + serializer.serialize(platform, jsonGenerator, serializationContext) verify(exactly = 1) { jsonGenerator.writeString("Game Boy") } } @@ -211,7 +211,7 @@ class PlatformSerializerTest { fun `serialize should handle PlayStation VR2 platform`() { val platform = Platform.PLAYSTATION_VR2 - serializer.serialize(platform, jsonGenerator, serializerProvider) + serializer.serialize(platform, jsonGenerator, serializationContext) verify(exactly = 1) { jsonGenerator.writeString("PlayStation VR2") } } diff --git a/app/src/test/kotlin/org/gameyfin/app/users/passwordreset/PasswordResetEndpointTest.kt b/app/src/test/kotlin/org/gameyfin/app/users/passwordreset/PasswordResetEndpointTest.kt index 516dd92..446a84c 100644 --- a/app/src/test/kotlin/org/gameyfin/app/users/passwordreset/PasswordResetEndpointTest.kt +++ b/app/src/test/kotlin/org/gameyfin/app/users/passwordreset/PasswordResetEndpointTest.kt @@ -23,7 +23,7 @@ class PasswordResetEndpointTest { fun setup() { passwordResetService = mockk() userService = mockk() - passwordResetEndpoint = PasswordResetEndpoint(passwordResetService, userService) + passwordResetEndpoint = PasswordResetEndpoint(passwordResetService) } @AfterEach diff --git a/app/tsconfig.json b/app/tsconfig.json index 40f8f64..936eadf 100644 --- a/app/tsconfig.json +++ b/app/tsconfig.json @@ -10,7 +10,7 @@ "jsx": "react-jsx", "inlineSources": true, "module": "esNext", - "target": "es2022", + "target": "es2023", "moduleResolution": "bundler", "strict": true, "skipLibCheck": true, diff --git a/app/vite.generated.ts b/app/vite.generated.ts index 703ba4c..8ec9fcc 100644 --- a/app/vite.generated.ts +++ b/app/vite.generated.ts @@ -29,16 +29,13 @@ import checker from 'vite-plugin-checker'; import postcssLit from './build/plugins/rollup-plugin-postcss-lit-custom/rollup-plugin-postcss-lit.js'; import vaadinI18n from './build/plugins/rollup-plugin-vaadin-i18n/rollup-plugin-vaadin-i18n.js'; import serviceWorkerPlugin from './build/plugins/vite-plugin-service-worker'; - -import { createRequire } from 'module'; +import vaadinBundlesPlugin from './build/plugins/vite-plugin-vaadin-bundles'; import { visualizer } from 'rollup-plugin-visualizer'; import reactPlugin from '@vitejs/plugin-react'; -import vitePluginFileSystemRouter from '@vaadin/hilla-file-router/vite-plugin.js'; -// Make `require` compatible with ES modules -const require = createRequire(import.meta.url); +import vitePluginFileSystemRouter from '@vaadin/hilla-file-router/vite-plugin.js'; const frontendFolder = path.resolve(__dirname, settings.frontendFolder); const themeFolder = path.resolve(frontendFolder, settings.themeFolder); @@ -78,14 +75,16 @@ const themeOptions = { projectStaticAssetsOutputFolder: devBundle ? path.resolve(devBundleFolder, '../assets') : path.resolve(__dirname, settings.staticOutput), - frontendGeneratedFolder: path.resolve(frontendFolder, settings.generatedFolder) + frontendGeneratedFolder: path.resolve(frontendFolder, settings.generatedFolder), + projectStaticOutput: path.resolve(__dirname, settings.staticOutput), + javaResourceFolder: settings.javaResourceFolder ? path.resolve(__dirname, settings.javaResourceFolder) : '' }; const hasExportedWebComponents = existsSync(path.resolve(frontendFolder, 'web-component.html')); const commercialBannerComponent = path.resolve(frontendFolder, settings.generatedFolder, 'commercial-banner.js'); const hasCommercialBanner = existsSync(commercialBannerComponent); -const target = ['safari15', 'es2022']; +const target = ['es2023']; // Block debug and trace logs. console.trace = () => {}; @@ -183,6 +182,10 @@ function statsExtracterPlugin(): PluginOption { path.resolve(themeOptions.frontendGeneratedFolder, 'flow', 'generated-flow-imports.js'), generatedImportsSet ); + parseImports( + path.resolve(themeOptions.frontendGeneratedFolder, 'app-shell-imports.js'), + generatedImportsSet + ); const generatedImports = Array.from(generatedImportsSet).sort(); const frontendFiles: Record = {}; @@ -303,159 +306,6 @@ function statsExtracterPlugin(): PluginOption { } }; } -function vaadinBundlesPlugin(): PluginOption { - type ExportInfo = - | string - | { - namespace?: string; - source: string; - }; - - type ExposeInfo = { - exports: ExportInfo[]; - }; - - type PackageInfo = { - version: string; - exposes: Record; - }; - - type BundleJson = { - packages: Record; - }; - - const disabledMessage = 'Vaadin component dependency bundles are disabled.'; - - const modulesDirectory = nodeModulesFolder.replace(/\\/g, '/'); - - let vaadinBundleJson: BundleJson; - - function parseModuleId(id: string): { packageName: string; modulePath: string } { - const [scope, scopedPackageName] = id.split('/', 3); - const packageName = scope.startsWith('@') ? `${scope}/${scopedPackageName}` : scope; - const modulePath = `.${id.substring(packageName.length)}`; - return { - packageName, - modulePath - }; - } - - function getExports(id: string): string[] | undefined { - const { packageName, modulePath } = parseModuleId(id); - const packageInfo = vaadinBundleJson.packages[packageName]; - - if (!packageInfo) return; - - const exposeInfo: ExposeInfo = packageInfo.exposes[modulePath]; - if (!exposeInfo) return; - - const exportsSet = new Set(); - for (const e of exposeInfo.exports) { - if (typeof e === 'string') { - exportsSet.add(e); - } else { - const { namespace, source } = e; - if (namespace) { - exportsSet.add(namespace); - } else { - const sourceExports = getExports(source); - if (sourceExports) { - sourceExports.forEach((e) => exportsSet.add(e)); - } - } - } - } - return Array.from(exportsSet); - } - - function getExportBinding(binding: string) { - return binding === 'default' ? '_default as default' : binding; - } - - function getImportAssigment(binding: string) { - return binding === 'default' ? 'default: _default' : binding; - } - - return { - name: 'vaadin:bundles', - enforce: 'pre', - apply(config, { command }) { - if (command !== 'serve') return false; - - try { - const vaadinBundleJsonPath = require.resolve('@vaadin/bundles/vaadin-bundle.json'); - vaadinBundleJson = JSON.parse(readFileSync(vaadinBundleJsonPath, { encoding: 'utf8' })); - } catch (e: unknown) { - if (typeof e === 'object' && (e as { code: string }).code === 'MODULE_NOT_FOUND') { - vaadinBundleJson = { packages: {} }; - console.info(`@vaadin/bundles npm package is not found, ${disabledMessage}`); - return false; - } else { - throw e; - } - } - - const versionMismatches: Array<{ name: string; bundledVersion: string; installedVersion: string }> = []; - for (const [name, packageInfo] of Object.entries(vaadinBundleJson.packages)) { - let installedVersion: string | undefined = undefined; - try { - const { version: bundledVersion } = packageInfo; - const installedPackageJsonFile = path.resolve(modulesDirectory, name, 'package.json'); - const packageJson = JSON.parse(readFileSync(installedPackageJsonFile, { encoding: 'utf8' })); - installedVersion = packageJson.version; - if (installedVersion && installedVersion !== bundledVersion) { - versionMismatches.push({ - name, - bundledVersion, - installedVersion - }); - } - } catch (_) { - // ignore package not found - } - } - if (versionMismatches.length) { - console.info(`@vaadin/bundles has version mismatches with installed packages, ${disabledMessage}`); - console.info(`Packages with version mismatches: ${JSON.stringify(versionMismatches, undefined, 2)}`); - vaadinBundleJson = { packages: {} }; - return false; - } - - return true; - }, - async config(config) { - return mergeConfig( - { - optimizeDeps: { - exclude: [ - // Vaadin bundle - '@vaadin/bundles', - ...Object.keys(vaadinBundleJson.packages), - '@vaadin/vaadin-material-styles' - ] - } - }, - config - ); - }, - load(rawId) { - const [path, params] = rawId.split('?'); - if (!path.startsWith(modulesDirectory)) return; - - const id = path.substring(modulesDirectory.length + 1); - const bindings = getExports(id); - if (bindings === undefined) return; - - const cacheSuffix = params ? `?${params}` : ''; - const bundlePath = `@vaadin/bundles/vaadin.js${cacheSuffix}`; - - return `import { init as VaadinBundleInit, get as VaadinBundleGet } from '${bundlePath}'; -await VaadinBundleInit('default'); -const { ${bindings.map(getImportAssigment).join(', ')} } = (await VaadinBundleGet('./node_modules/${id}'))(); -export { ${bindings.map(getExportBinding).join(', ')} };`; - } - }; -} function themePlugin(opts: { devMode: boolean }): PluginOption { const fullThemeOptions = { ...themeOptions, devMode: opts.devMode }; @@ -563,9 +413,12 @@ function preserveUsageStats() { transform(src: string, id: string) { if (id.includes('vaadin-usage-statistics')) { if (src.includes('vaadin-dev-mode:start')) { - const newSrc = src.replace(DEV_MODE_START_REGEXP, '/*! vaadin-dev-mode:start'); + const expectedComment = '/*! vaadin-dev-mode:start'; + const newSrc = src.replace(DEV_MODE_START_REGEXP, expectedComment); if (newSrc === src) { - console.error('Comment replacement failed to change anything'); + if (!src.includes(expectedComment)) { + console.error('vaadin-dev-mode:start tag not found'); + } } else if (!newSrc.match(DEV_MODE_CODE_REGEXP)) { console.error('New comment fails to match original regexp'); } else { @@ -612,6 +465,9 @@ export const vaadinConfig: UserConfigFn = (env) => { allow: allowedFrontendFolders } }, + esbuild: { + legalComments: 'inline', + }, build: { minify: productionMode, outDir: buildOutputFolder, @@ -668,7 +524,9 @@ export const vaadinConfig: UserConfigFn = (env) => { }, plugins: [ productionMode && brotli(), - devMode && vaadinBundlesPlugin(), + devMode && vaadinBundlesPlugin({ + nodeModulesFolder + }), devMode && showRecompileReason(), settings.offlineEnabled && serviceWorkerPlugin({ srcPath: settings.clientServiceWorkerSource, @@ -714,6 +572,7 @@ export const vaadinConfig: UserConfigFn = (env) => { ].filter(Boolean) } }), + productionMode && vaadinI18n({ cwd: __dirname, meta: { diff --git a/build.gradle.kts b/build.gradle.kts index b478fe7..5e05973 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -29,14 +29,14 @@ tasks.named("jar") { subprojects { apply(plugin = "java") - java.sourceCompatibility = JavaVersion.VERSION_21 - java.targetCompatibility = JavaVersion.VERSION_21 + java.sourceCompatibility = JavaVersion.VERSION_25 + java.targetCompatibility = JavaVersion.VERSION_25 tasks.withType { compilerOptions { - languageVersion = KotlinVersion.KOTLIN_2_2 - apiVersion = KotlinVersion.KOTLIN_2_2 - jvmTarget = JvmTarget.JVM_21 + languageVersion = KotlinVersion.KOTLIN_2_3 + apiVersion = KotlinVersion.KOTLIN_2_3 + jvmTarget = JvmTarget.JVM_25 progressiveMode = true freeCompilerArgs.add("-Xjsr305=strict") } diff --git a/gradle.properties b/gradle.properties index fc9623e..ddc3819 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,16 +4,26 @@ org.gradle.parallel=true org.gradle.caching=true org.gradle.configuration-cache=true # Dependency versions -kotlinVersion=2.2.20 -kspVersion=2.2.20-2.0.3 -vaadinVersion=24.9.4 -springBootVersion=3.5.6 -springCloudVersion=2025.0.0 +kotlinVersion=2.3.0 +kspVersion=2.3.5 +vaadinVersion=25.0.4 +springBootVersion=4.0.2 +springCloudVersion=2025.1.1 springDependencyManagementVersion=1.1.7 +jakartaValidationVersion=3.1.0 +kotlinLoggingVersion=7.0.14 +springContentVersion=3.0.17 +commonsIoVersion=2.21.0 guavaVersion=33.5.0-jre +mjml4jVersion=1.1.5 +tikaVersion=3.2.3 +fuzzywuzzyVersion=1.4.0 +blurhashVersion=0.3.0 # Plugin dependency versions -pf4jVersion=3.13.0 -pf4jKspVersion=2.2.20-1.0.3 +pf4jVersion=3.15.0 +pf4jKspVersion=2.3.0-1.0.4 # Test framework versions -junitVersion=6.0.0 -mockkVersion=1.14.6 \ No newline at end of file +junitVersion=6.0.2 +mockkVersion=1.14.9 +# CI plugins +sonarVersion=7.1.0.6387 \ No newline at end of file diff --git a/plugin-api/build.gradle.kts b/plugin-api/build.gradle.kts index 66c5a15..ae1079d 100644 --- a/plugin-api/build.gradle.kts +++ b/plugin-api/build.gradle.kts @@ -1,4 +1,4 @@ -val jacksonVersion = "2.19.1" +val jacksonVersion = "3.0.4" plugins { kotlin("jvm") @@ -16,8 +16,8 @@ dependencies { implementation("io.github.oshai:kotlin-logging-jvm:7.0.3") // JSON serialization - compileOnly("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion") - implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion") + compileOnly("tools.jackson.core:jackson-databind:$jacksonVersion") + implementation("tools.jackson.module:jackson-module-kotlin:$jacksonVersion") } mavenPublishing { diff --git a/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/wrapper/ConfigurableGameyfinPlugin.kt b/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/wrapper/ConfigurableGameyfinPlugin.kt index 5f4a5e0..0c9920c 100644 --- a/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/wrapper/ConfigurableGameyfinPlugin.kt +++ b/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/wrapper/ConfigurableGameyfinPlugin.kt @@ -132,11 +132,11 @@ abstract class ConfigurableGameyfinPlugin(wrapper: PluginWrapper) : GameyfinPlug // Try to convert common types try { return when (expectedType) { - Int::class.java, Integer::class.java -> value.toString().toInt() - Float::class.java, java.lang.Float::class.java -> value.toString().toFloat() - Double::class.java, java.lang.Double::class.java -> value.toString().toDouble() - Long::class.java, java.lang.Long::class.java -> value.toString().toLong() - Boolean::class.java, java.lang.Boolean::class.java -> value.toString().toBooleanStrict() + Int::class.java -> value.toString().toInt() + Float::class.java -> value.toString().toFloat() + Double::class.java -> value.toString().toDouble() + Long::class.java -> value.toString().toLong() + Boolean::class.java -> value.toString().toBooleanStrict() String::class.java -> value.toString() else -> { // Try valueOf(String) or parse(String) via reflection diff --git a/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/wrapper/GameyfinPlugin.kt b/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/wrapper/GameyfinPlugin.kt index dfa7f5e..a257298 100644 --- a/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/wrapper/GameyfinPlugin.kt +++ b/plugin-api/src/main/kotlin/org/gameyfin/pluginapi/core/wrapper/GameyfinPlugin.kt @@ -1,9 +1,10 @@ package org.gameyfin.pluginapi.core.wrapper -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.kotlin.KotlinModule import org.pf4j.Plugin import org.pf4j.PluginWrapper +import tools.jackson.databind.ObjectMapper +import tools.jackson.module.kotlin.jsonMapper +import tools.jackson.module.kotlin.kotlinModule import java.io.IOException import java.nio.file.Files import java.nio.file.Path @@ -48,7 +49,9 @@ abstract class GameyfinPlugin(wrapper: PluginWrapper) : Plugin(wrapper) { /** * JSON serializer for serializing and deserializing plugin state. */ - val objectMapper: ObjectMapper = ObjectMapper().registerModule(KotlinModule.Builder().build()) + val objectMapper: ObjectMapper = jsonMapper { + addModule(kotlinModule()) + } /** * Checks if the plugin contains a logo file in any supported format. diff --git a/plugins/build.gradle.kts b/plugins/build.gradle.kts index 6bdd4d3..0e8289e 100644 --- a/plugins/build.gradle.kts +++ b/plugins/build.gradle.kts @@ -13,7 +13,7 @@ val keystorePasswordProperty = "gameyfin.keystorePassword" val keystorePath: String = rootProject.file("certs/gameyfin.jks").absolutePath val keystoreAlias = "gameyfin-plugins" -val keystorePasswordProvider = provider { +val keystorePasswordProvider: Provider = provider { (findProperty(keystorePasswordProperty) as String?) ?: System.getenv(keystorePasswordEnvironmentVariable) ?: "" diff --git a/settings.gradle.kts b/settings.gradle.kts index 044590f..b9c8765 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -10,6 +10,7 @@ pluginManagement { id("org.springframework.boot") version extra["springBootVersion"] as String id("io.spring.dependency-management") version extra["springDependencyManagementVersion"] as String id("com.google.devtools.ksp") version extra["kspVersion"] as String + id("org.sonarqube") version extra["sonarVersion"] as String kotlin("jvm") version extra["kotlinVersion"] as String kotlin("plugin.spring") version extra["kotlinVersion"] as String kotlin("plugin.jpa") version extra["kotlinVersion"] as String