diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 51f5061d..a0c5b7ff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,9 +33,9 @@ jobs: working-directory: ./server run: ./mvnw --no-transfer-progress clean compile -DskipTests - - name: Run tests + - name: Run unit tests (skip integration on purpose) working-directory: ./server - run: ./mvnw --no-transfer-progress clean verify -P tests --file pom.xml + run: ./mvnw --no-transfer-progress verify -P tests -Dskip.unit.tests=true -Djacoco.skip=true -Dcheckstyle.skip=true build-frontend: name: Build Frontend diff --git a/AGENTS.md b/AGENTS.md index e05f991e..9776b954 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,7 +18,7 @@ ## Developer workflows (use these first) - Frontend quality gate: `bash tools/check-frontend.sh` (runs `npm ci`, `lint:fix`, `build`, `test:no-watch`). -- Backend quality gate: `bash tools/check-backend.sh` (runs checkstyle, compile, then `clean verify -P tests`). +- Backend quality gate: `bash tools/check-backend.sh` (runs checkstyle, compile, then `clean verify -P tests`). Integration tests (`*IntTest`) use Testcontainers with a `jdbc:tc:postgresql:15.8-bookworm` URL and real Flyway migrations, so Docker must be running; there is no H2 or test-only schema. Equivalent Taskfile tasks: `task test-api`, `task test-api-unit` (no Docker), `task test-api-it`. - Backend dependency freshness check: `bash tools/check-be-deps.sh` (verifies that `failsafe`, `surefire`, `jacoco`, `checkstyle`, and Spring Boot plugin versions in `pom.xml` match the latest releases on Maven Central; must be run from the repo root or `server/`). - Important Maven default: tests/checkstyle/jacoco are skipped unless profile `-P tests` is enabled (`server/pom.xml`). - Dev stack via Docker Compose/Taskfile (`Taskfile.yml`, `docker-compose.dev.yml`): app `5000`, API `8585`, Java debug port `5005`, Postgres `5432`. diff --git a/CHANGELOG.md b/CHANGELOG.md index ff940dab..51e91544 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## 2026-09-16 + +### Changed +- Client deps bumped to latest version in minor and patch targets. +- Server Spring Boot version bumped from 4.0.7 to 4.1.1 + +--- + ## 2026-08-31 ### Fixed diff --git a/README.md b/README.md index acc2b41a..6e8bc866 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,14 @@ task docker-build-web ./tools/check-backend.sh ``` +Integration tests (`*IntTest`) run against a real PostgreSQL instance via Testcontainers, so a running Docker daemon is required. Unit tests alone do not need Docker: + +```bash +task test-api-unit # unit tests only, no Docker +task test-api-it # integration tests only, Docker required +task test-api # full backend gate (same as tools/check-backend.sh) +``` + ## 🤝 Contributing We welcome contributions from the community! This project follows the **Fork & Merge** workflow. diff --git a/Taskfile.yml b/Taskfile.yml index d06ce2bd..bc789e74 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -5,56 +5,111 @@ version: '3' tasks: docker-build-web: desc: Build the tasknote-web prod-ready docker image, tagging it as candidate - cmd: docker build --no-cache --build-arg VITE_BUILD="v999-$(date '+%Y-%m-%d-%H%M%S')" --build-arg SOURCE_PR="v999-123456789-$(date '+%Y-%m-%d-%H%M%S')" -t rmcampos/tasknote-app:latest ./client + cmd: docker build --no-cache --build-arg VITE_BUILD="v999-$(date '+%Y-%m-%d-%H%M%S')" --build-arg SOURCE_PR="v999-123456789-$(date '+%Y-%m-%d-%H%M%S')" -t ghcr.io/thermcampos/tasknote/client:latest ./client docker-build-api: desc: Build the tasknote-api prod-ready docker image, tagging it as candidate - cmd: cd server && ./mvnw -Pnative -DskipTests spring-boot:build-image -Dspring-boot.build-image.imageName=rmcampos/tasknote-api:latest -Dspring-boot.build-image.builder=paketobuildpacks/builder-jammy-tiny:0.0.505 + cmd: cd server && ./mvnw -Pnative -DskipTests spring-boot:build-image -Dspring-boot.build-image.imageName=ghcr.io/thermcampos/tasknote/server:latest -Dspring-boot.build-image.builder=paketobuildpacks/builder-jammy-tiny:0.0.505 - prod-up-web: - desc: Speed up the tasknote-web prod-like image, building it if required - cmd: docker compose -f docker-compose.prod.yml up -d tasknote-web + db-up: + desc: Starts Postgres database container + cmd: docker compose -f compose.dev.yaml up -d tasknote-db - prod-up-api: - desc: Speed up the tasknote-api prod-like image, building it if required - cmd: docker compose -f docker-compose.prod.yml up -d tasknote-api + db-down: + desc: Stops and remove Postgres database container + cmd: docker compose -f compose.dev.yaml down tasknote-db -v --remove-orphans - prod-up-db: - desc: Start the database container - cmd: docker compose -f docker-compose.prod.yml up -d tasknote-db + schemaspy-up: + desc: Starts database, API, SchemaSpy containers and a Python http server + cmd: docker compose -f compose.schemaspy.yaml up -d - prod-up: - desc: Speed up the prod-like images for both tasknote-api and tasknote-web, building them if required - cmd: docker compose -f docker-compose.prod.yml up -d + schemaspy-down: + desc: Stops and remove database, API, SchemaSpy and the Python http server + cmd: docker compose -f compose.schemaspy.yaml down -v --remove-orphans - prod-stop: - desc: Stop prod containers - cmd: docker compose -f docker-compose.prod.yml stop + dev-up: + desc: Starts database, API and web app locally + cmd: doppler run --project tasknote --config dev -- docker compose -f compose.dev.yaml up -d - prod-tier-down: - desc: Stop and remove prod containers - cmd: docker compose -f docker-compose.prod.yml down --remove-orphans + dev-down: + desc: Stops and remove database, API and web app containers + cmd: export NGROK_AUTHTOKEN=1 && docker compose -f compose.dev.yaml down -v --remove-orphans - dev-run-web: - desc: Get the tasknote-web up and running locally inside docker ready for development - cmd: docker compose -f docker-compose.dev.yml up -d tasknote-web + prod-like-up: + desc: Starts Postgres, GraalVM API and Docker web app using prod images + cmd: docker compose -f compose.prod-like.yaml up -d - dev-run-api: - desc: Get the tasknote-api up and running locally inside docker ready for development - cmd: docker compose -f docker-compose.dev.yml up -d tasknote-api + prod-like-down: + desc: Stops and remove Postgres, GraalVM API and Docker web app using prod images + cmd: docker compose -f compose.prod-like.yaml down -v --remove-orphans - dev-run-db: - desc: Get the tasknote-db up and running locally inside docker ready for development - cmd: docker compose -f docker-compose.dev.yml up -d tasknote-db + # prod-up-web: + # desc: Speed up the tasknote-web prod-like image, building it if required + # cmd: docker compose -f docker-compose.prod.yml up -d tasknote-web - dev-run: - desc: Get both tasknote-web and tasknote-api up and running locally inside docker ready for development - cmd: docker compose -f docker-compose.dev.yml up -d + # prod-up-api: + # desc: Speed up the tasknote-api prod-like image, building it if required + # cmd: doppler run --project tasknote --config dev -- docker compose -f docker-compose.prod.yml up -d tasknote-api - dev-stop: - desc: Stop dev containers - cmd: docker compose -f docker-compose.dev.yml stop + # prod-up-db: + # desc: Start the database container + # cmd: doppler run --project tasknote --config dev -- docker compose -f docker-compose.prod.yml up -d tasknote-db - dev-tier-down: - desc: Stop and remove dev containers - cmd: docker compose -f docker-compose.dev.yml down --remove-orphans + # prod-up: + # desc: Speed up the prod-like images for both tasknote-api and tasknote-web, building them if required + # cmd: docker compose -f docker-compose.prod.yml up -d + + # prod-stop: + # desc: Stop prod containers + # cmd: docker compose -f docker-compose.prod.yml stop + + # prod-tier-down: + # desc: Stop and remove prod containers + # cmd: docker compose -f docker-compose.prod.yml down --remove-orphans + + # dev-run-web: + # desc: Get the tasknote-web up and running locally inside docker ready for development + # cmd: docker compose -f docker-compose.dev.yml up -d tasknote-web + + # dev-run-api: + # desc: Get the tasknote-api up and running locally inside docker ready for development + # cmd: docker compose -f docker-compose.dev.yml up -d tasknote-api + + # dev-run-db: + # desc: Get the tasknote-db up and running locally inside docker ready for development + # cmd: doppler run --project tasknote --config dev -- docker compose -f docker-compose.dev.yml up -d tasknote-db + + # dev-run: + # desc: Get both tasknote-web and tasknote-api up and running locally inside docker ready for development + # cmd: docker compose -f docker-compose.dev.yml up -d + + # dev-stop: + # desc: Stop dev containers + # cmd: docker compose -f docker-compose.dev.yml stop + + # dev-tier-down: + # desc: Stop and remove dev containers + # cmd: docker compose -f docker-compose.dev.yml down --remove-orphans + + test-api: + desc: Run all backend checks (checkstyle, build, unit and integration tests). Integration tests run on Testcontainers PostgreSQL, so Docker is required + cmd: bash tools/check-backend.sh + + test-api-unit: + desc: Run backend unit tests only (no Docker required) + cmd: cd server && ./mvnw --no-transfer-progress test -P tests -Dskip.integration.tests=true + + test-api-it: + desc: Run backend integration tests only (Testcontainers PostgreSQL, Docker required) + cmd: cd server && ./mvnw --no-transfer-progress verify -P tests -Dskip.unit.tests=true -Djacoco.skip=true -Dcheckstyle.skip=true + + run-native-tracing: + desc: Buidl and run with native agent to trace reflections + deps: [db-up] + cmds: + - | + cd server && \ + ./mvnw clean package -DskipTests && \ + doppler run --project tasknote --config dev -- java \ + -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image \ + -jar target/tasknote-api.jar diff --git a/client/.prettierrc.js b/client/.prettierrc.cjs similarity index 100% rename from client/.prettierrc.js rename to client/.prettierrc.cjs diff --git a/client/package-lock.json b/client/package-lock.json index 4bdd8319..1b572220 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -10,52 +10,52 @@ "license": "GPL-3.0-only", "dependencies": { "@popperjs/core": "^2.11.8", - "@types/node": "^26.1.2", - "@types/react-dom": "^19.2.5", - "@vitejs/plugin-react": "^6.0.5", + "@types/node": "^26.6.1", + "@types/react-dom": "^19.3.0", + "@vitejs/plugin-react": "^6.1.1", "bootstrap": "^5.3.8", - "dompurify": "^3.4.14", - "i18next": "^26.3.6", - "react": "^19.2.8", + "dompurify": "^3.4.15", + "i18next": "^26.4.2", + "react": "^19.3.0", "react-bootstrap": "^2.10.10", "react-bootstrap-icons": "^1.11.6", "react-charts": "^3.0.0-beta.57", - "react-dom": "^19.2.8", - "react-i18next": "^17.0.12", + "react-dom": "^19.3.0", + "react-i18next": "^17.0.14", "react-markdown": "^10.1.0", - "react-router": "^8.3.0", + "react-router": "^8.4.0", "react-router-bootstrap": "^0.26.3", "remark-gfm": "^4.0.1", "typescript": "^6.0.3", - "vite": "^8.2.2" + "vite": "^8.3.0" }, "devDependencies": { - "@eslint/compat": "^2.1.0", - "@eslint/eslintrc": "^3.3.6", + "@eslint/compat": "^2.1.1", + "@eslint/eslintrc": "^3.3.7", "@eslint/js": "^9.39.5", "@stylistic/eslint-plugin": "^5.10.0", - "@testing-library/jest-dom": "^6.10.0", + "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.3", - "@testing-library/user-event": "^14.6.6", + "@testing-library/user-event": "^14.6.7", "@types/jest": "^30.0.0", - "@types/react": "^19.2.18", + "@types/react": "^19.3.0", "@types/react-router-bootstrap": "^0.26.8", - "@vitest/coverage-v8": "^4.1.11", - "cypress": "^15.20.1", + "@vitest/coverage-v8": "^5.0.1", + "cypress": "^15.21.1", "eslint": "^9.39.5", "eslint-config-prettier": "^10.1.8", "eslint-plugin-import-x": "^4.17.1", - "eslint-plugin-jsdoc": "^63.3.3", - "eslint-plugin-n": "^18.2.2", + "eslint-plugin-jsdoc": "^64.5.2", + "eslint-plugin-n": "^18.3.0", "eslint-plugin-promise": "^7.3.0", "eslint-plugin-react": "^7.37.5", - "globals": "^17.9.0", - "jsdom": "^29.1.1", - "prettier": "^3.9.6", - "sass": "^1.102.0", + "globals": "^17.12.0", + "jsdom": "^30.0.1", + "prettier": "^3.9.7", + "sass": "^1.104.1", "source-map-support": "^0.5.21", - "typescript-eslint": "^8.66.0", - "vitest": "^4.1.11" + "typescript-eslint": "^8.70.0", + "vitest": "^5.0.1" } }, "node_modules/@adobe/css-tools": { @@ -66,56 +66,38 @@ "license": "MIT" }, "node_modules/@asamuzakjp/css-color": { - "version": "5.1.11", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", - "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-6.0.7.tgz", + "integrity": "sha512-vC/bk1Lz7Tn/EfU9/apOTBk80/8dyGyWMowPoV1tJ52muDGsDqt2HPT2klrFUiY60MQmQv9q8yIht15JnBgDGw==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/generational-cache": "^1.0.1", - "@csstools/css-calc": "^3.2.0", - "@csstools/css-color-parser": "^4.1.0", + "@csstools/css-calc": "^3.3.0", + "@csstools/css-color-parser": "^4.1.10", "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0" + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.5.2" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": "^22.13.0 || >=24.0.0" } }, "node_modules/@asamuzakjp/dom-selector": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", - "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-8.3.2.tgz", + "integrity": "sha512-93Z1N+BQNXysodoicpOIyNh2drHfz/CTf9nnT0FEx72GJcIiwgydD7tGAr78j41LsYn3hlRn+LdGPuBLn1Bl8Q==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/generational-cache": "^1.0.1", - "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", "css-tree": "^3.2.1", - "is-potential-custom-element-name": "^1.0.1" + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.5.2" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/@asamuzakjp/generational-cache": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", - "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": "^22.13.0 || >=24.0.0" } }, - "node_modules/@asamuzakjp/nwsapi": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", - "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", - "dev": true, - "license": "MIT" - }, "node_modules/@babel/code-frame": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", @@ -152,13 +134,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", - "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.7" + "@babel/types": "^7.29.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -177,9 +159,9 @@ } }, "node_modules/@babel/types": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "dev": true, "license": "MIT", "dependencies": { @@ -214,9 +196,9 @@ } }, "node_modules/@csstools/color-helpers": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", - "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.1.tgz", + "integrity": "sha512-gLNsunvwf3mCi5u5o46/Z/JcJMnhbHSaZ69rkgPzNM3J4s8hWwpPUQB6/tt0EDFyCiWzxANlx+2LJwpYj4zS1w==", "dev": true, "funding": [ { @@ -234,9 +216,9 @@ } }, "node_modules/@csstools/css-calc": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz", - "integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.4.0.tgz", + "integrity": "sha512-XQKj5B7QiZcHiegCOCAzcAOJdhGgWOHbbu62h5e5mkHnn8lWcfiJhllkqWmxu5zWR9jucPHuo1iTB56P033hcg==", "dev": true, "funding": [ { @@ -258,9 +240,9 @@ } }, "node_modules/@csstools/css-color-parser": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.8.tgz", - "integrity": "sha512-3chWb7PRLijpJpPIKkDxdu6IBeO5MrFACND57On0j8OPpc0wZibcGc3xAHrSEbOx/KDRyMHoIxGn0w1PhXMYHw==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.2.3.tgz", + "integrity": "sha512-y4LpL+lmpuyKDiEFq2PnZUVFdAjsoB/qQJod79yLNokXyW7jewi+/WJ69EfItj8A2unWtxXnGjw6LYXgXu5ZjA==", "dev": true, "funding": [ { @@ -274,8 +256,8 @@ ], "license": "MIT", "dependencies": { - "@csstools/color-helpers": "^6.0.2", - "@csstools/css-calc": "^3.2.1" + "@csstools/color-helpers": "^6.1.1", + "@csstools/css-calc": "^3.4.0" }, "engines": { "node": ">=20.19.0" @@ -309,9 +291,9 @@ } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.5.tgz", - "integrity": "sha512-oNjBvzLq2GPZtJphCjLqXow/cHySHSgtxvKZb7OqSZ/xHgw6NWNhfad+6AB9cLeVm6eA9d/qMll3JdEHjy6M+A==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.14.tgz", + "integrity": "sha512-HpbVXyrofRXpHpgkNIjU/3EWR4WJvOkO3emNK/L6X/mTJU7bGUI3AkkpoTNXznQLp0KRjLHELTGeKI5dIkI9JQ==", "dev": true, "funding": [ { @@ -415,14 +397,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@emnapi/core/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, "node_modules/@emnapi/runtime": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", @@ -434,14 +408,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@emnapi/runtime/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", @@ -453,29 +419,31 @@ "tslib": "^2.4.0" } }, - "node_modules/@emnapi/wasi-threads/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, "node_modules/@es-joy/jsdoccomment": { - "version": "0.91.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.91.0.tgz", - "integrity": "sha512-vgqlMGNNhZxwDYbUNIHj3Hskb4R28iqdXx90ufHyt/NeuTQkeqjTDslAs9I0/GCAfbxP5BpH5WsL1R1fht5Lxg==", + "version": "0.97.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.97.0.tgz", + "integrity": "sha512-EP8uoFfh6+GsdGCduYtmWAW0h7AO+Ayik9Vh5YbA2r/3N6lmJKkCNZX+q3QBXC1K6ixjQ/9igF2b7WVvLm063g==", "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.9", - "@typescript-eslint/types": "^8.65.0", - "comment-parser": "1.4.7", + "@typescript-eslint/types": "^8.69.0", + "comment-parser": "1.4.8", "esquery": "^1.7.0", - "jsdoc-type-pratt-parser": "~8.0.0" + "jsdoc-type-pratt-parser": "~9.2.1" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": "^22.22.2 || >=24.15.0" + } + }, + "node_modules/@es-joy/jsdoccomment/node_modules/comment-parser": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.8.tgz", + "integrity": "sha512-rKZTGo4fzKYna8UcL0isTg5wkBNla7bxTypLwZQXjIdi++IdP1OJ41rI5Mti3/jltkPujbu4i9LIARYA+zpotQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.0.0" } }, "node_modules/@es-joy/resolve.exports": { @@ -489,9 +457,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", "dev": true, "license": "MIT", "dependencies": { @@ -531,9 +499,9 @@ } }, "node_modules/@eslint/compat": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-2.1.0.tgz", - "integrity": "sha512-LgaSCymEpw7tF53xvDw9SNsraPb1IBHxpdABIOM0hW8UAlP8znrjYtuxfR58FSJ3L9BhwD+FaPRFQpZq84Nh6g==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-2.1.1.tgz", + "integrity": "sha512-rMcy8GSrwNzcISX/BlTDY/GLB4eCopEuy9woIls3To+15OLxykZrxxq+WUcylCPCQ6F4MujjBM1DX5V1aqI3Vw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -606,9 +574,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", - "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.7.tgz", + "integrity": "sha512-F42g89Qd5oAWtp0k0nnSrjziAKza7w8SVT4mStc18LZMaRb4J1HQAHLCalEtDCxrTuksx7NU9qsmeLwpOfPqWw==", "dev": true, "license": "MIT", "dependencies": { @@ -618,7 +586,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.3.0", + "js-yaml": "^4.3.2", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, @@ -777,36 +745,36 @@ } }, "node_modules/@internationalized/date": { - "version": "3.12.2", - "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.2.tgz", - "integrity": "sha512-FY1Y+H64NDs+HAF6omlnWxm3mEpfgaCSWtL5l551ZZfImA+kGjPFgrnJrGjH6lfmLL0g8Z/mBu1R3kufeCp6Jw==", + "version": "3.12.4", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.4.tgz", + "integrity": "sha512-M1dEn4c1U1HsSlaVR8upZtSqvXrTkHDfv18H01uCSJyjVLDxnBR38v/fMxecmlwXKR4i9HeZcmgQAPE6A+aGJQ==", "license": "Apache-2.0", "dependencies": { "@swc/helpers": "^0.5.0" } }, "node_modules/@internationalized/number": { - "version": "3.6.7", - "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.7.tgz", - "integrity": "sha512-3ji1fcrT+FPAK86UqEhB/psHixYo6niWPJtt7+qRaYFynt/BaJG8GhAPimtWUpEiVSTq8ZM8L5psMxGquiB/Vg==", + "version": "3.6.8", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.8.tgz", + "integrity": "sha512-8UmMFia46DUt+k97zKd9fKWXcWHR+k8ae3eYzILETuT2KbIvLyOfac7zesw+sJdRAAZ7Q9pM1Mk22aXp2LD0Ig==", "license": "Apache-2.0", "dependencies": { "@swc/helpers": "^0.5.0" } }, "node_modules/@internationalized/string": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.2.9.tgz", - "integrity": "sha512-kzP/M/mbQxODlmOt4bIQZ2SBVUWUSqMLXooXixnX7noche8WHaQcA+nwFN1K2KCF/cp+LDUhcJsCicwkvhD1pg==", + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.2.10.tgz", + "integrity": "sha512-PDx6//vHSpRnHfxqMqto11zQvhsaU74O3mKv2F/0eicGZcl9NLjQmGlbHz/LsJh5tLKp4A4L7ZVTzN1/MmMTvA==", "license": "Apache-2.0", "dependencies": { "@swc/helpers": "^0.5.0" } }, "node_modules/@jest/diff-sequences": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz", - "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.5.0.tgz", + "integrity": "sha512-OsqBjHXCn8cadasoAZBP6nWYvMsRhpMzGXTpxJ5aO04NlbdhIz+FVe3q49l0AwVhsz/cEmIpBes6gAFl1/dWQg==", "dev": true, "license": "MIT", "engines": { @@ -814,22 +782,22 @@ } }, "node_modules/@jest/expect-utils": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz", - "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.5.1.tgz", + "integrity": "sha512-WcRWhHQdTMRDpyWKZ/6MINBmovI7zeD+bL8wFjCncRV3NQOwKy1X45IfyblfHR4k/XciIlNEdFL9QjFO+HNKOg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0" + "@jest/get-type": "30.5.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/get-type": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", - "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.5.0.tgz", + "integrity": "sha512-9/2VUPitAjmBzbvDvqrxmvB7BzWsBW0WmkkojX1ODuxX1NLGxx9gfaZpHB0z8DtJ9uhGNmZG/VXBhf8uO0OV8Q==", "dev": true, "license": "MIT", "engines": { @@ -837,23 +805,39 @@ } }, "node_modules/@jest/pattern": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz", - "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.5.0.tgz", + "integrity": "sha512-HdNQYSdRTEBNrginaqzQtTjG0HRMfrra/z6Ok7uL3S87vSlarIVohEsJsSj5edu3MiHoHjAkvPROz5ZjoKai+w==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", - "jest-regex-util": "30.4.0" + "jest-regex-util": "30.5.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@jest/react-is-18": { + "name": "react-is", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/react-is-19": { + "name": "react-is", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.3.0.tgz", + "integrity": "sha512-UpMYezM4v5/18F28aC66AEsjXIgE02kyEMH6yLdgLXu/UTfa1Ntwck/nNLrbqJsEXW7gPb0coNO9FQse9WTovA==", + "dev": true, + "license": "MIT" + }, "node_modules/@jest/schemas": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", - "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", "dev": true, "license": "MIT", "dependencies": { @@ -864,14 +848,14 @@ } }, "node_modules/@jest/types": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", - "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/pattern": "30.4.0", - "@jest/schemas": "30.4.1", + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", "@types/istanbul-lib-coverage": "^2.0.6", "@types/istanbul-reports": "^3.0.4", "@types/node": "*", @@ -893,9 +877,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", "dev": true, "license": "MIT" }, @@ -911,37 +895,40 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", - "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.4.tgz", + "integrity": "sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==", "dev": true, "license": "MIT", "optional": true, "dependencies": { "@tybys/wasm-util": "^0.10.3" }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, "funding": { "type": "github", "url": "https://github.com/sponsors/Brooooooklyn" }, "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "node_modules/@oxc-project/types": { - "version": "0.147.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.147.0.tgz", - "integrity": "sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==", + "version": "0.150.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.150.0.tgz", + "integrity": "sha512-rDS5/31E9HfPl/CIzGrn0DOlvBbXFseQ5URJ9sYMfstbKLD/c6Gm9vmRzRGDdAXyOIL4zmO37lc9RIwYqVruZw==", "license": "MIT", "funding": { - "url": "https://github.com/sponsors/Boshen" + "url": "https://github.com/sponsors/oxc-project" } }, "node_modules/@parcel/watcher": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", - "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.6.0.tgz", + "integrity": "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==", "hasInstallScript": true, "license": "MIT", "optional": true, @@ -949,7 +936,7 @@ "detect-libc": "^2.0.3", "is-glob": "^4.0.3", "node-addon-api": "^7.0.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">= 10.0.0" @@ -959,25 +946,24 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.6", - "@parcel/watcher-darwin-arm64": "2.5.6", - "@parcel/watcher-darwin-x64": "2.5.6", - "@parcel/watcher-freebsd-x64": "2.5.6", - "@parcel/watcher-linux-arm-glibc": "2.5.6", - "@parcel/watcher-linux-arm-musl": "2.5.6", - "@parcel/watcher-linux-arm64-glibc": "2.5.6", - "@parcel/watcher-linux-arm64-musl": "2.5.6", - "@parcel/watcher-linux-x64-glibc": "2.5.6", - "@parcel/watcher-linux-x64-musl": "2.5.6", - "@parcel/watcher-win32-arm64": "2.5.6", - "@parcel/watcher-win32-ia32": "2.5.6", - "@parcel/watcher-win32-x64": "2.5.6" + "@parcel/watcher-android-arm64": "2.6.0", + "@parcel/watcher-darwin-arm64": "2.6.0", + "@parcel/watcher-darwin-x64": "2.6.0", + "@parcel/watcher-freebsd-x64": "2.6.0", + "@parcel/watcher-linux-arm-glibc": "2.6.0", + "@parcel/watcher-linux-arm-musl": "2.6.0", + "@parcel/watcher-linux-arm64-glibc": "2.6.0", + "@parcel/watcher-linux-arm64-musl": "2.6.0", + "@parcel/watcher-linux-x64-glibc": "2.6.0", + "@parcel/watcher-linux-x64-musl": "2.6.0", + "@parcel/watcher-win32-arm64": "2.6.0", + "@parcel/watcher-win32-x64": "2.6.0" } }, "node_modules/@parcel/watcher-android-arm64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", - "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.6.0.tgz", + "integrity": "sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==", "cpu": [ "arm64" ], @@ -995,9 +981,9 @@ } }, "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", - "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.6.0.tgz", + "integrity": "sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==", "cpu": [ "arm64" ], @@ -1015,9 +1001,9 @@ } }, "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", - "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.6.0.tgz", + "integrity": "sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==", "cpu": [ "x64" ], @@ -1035,9 +1021,9 @@ } }, "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", - "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.6.0.tgz", + "integrity": "sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==", "cpu": [ "x64" ], @@ -1055,9 +1041,9 @@ } }, "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", - "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.6.0.tgz", + "integrity": "sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==", "cpu": [ "arm" ], @@ -1075,9 +1061,9 @@ } }, "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", - "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.6.0.tgz", + "integrity": "sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==", "cpu": [ "arm" ], @@ -1095,9 +1081,9 @@ } }, "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", - "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.6.0.tgz", + "integrity": "sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==", "cpu": [ "arm64" ], @@ -1115,9 +1101,9 @@ } }, "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", - "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.6.0.tgz", + "integrity": "sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==", "cpu": [ "arm64" ], @@ -1135,9 +1121,9 @@ } }, "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", - "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.6.0.tgz", + "integrity": "sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==", "cpu": [ "x64" ], @@ -1155,9 +1141,9 @@ } }, "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", - "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.6.0.tgz", + "integrity": "sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==", "cpu": [ "x64" ], @@ -1175,9 +1161,9 @@ } }, "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", - "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.6.0.tgz", + "integrity": "sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==", "cpu": [ "arm64" ], @@ -1194,30 +1180,10 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", - "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", - "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.6.0.tgz", + "integrity": "sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==", "cpu": [ "x64" ], @@ -1262,14 +1228,20 @@ } }, "node_modules/@react-types/shared": { - "version": "3.36.0", - "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.36.0.tgz", - "integrity": "sha512-DkP/H0C2YjjS7gZWKNqOmU8a16qHPjQNdzMwmTq9SzplM6Iw0kVMTZ0OIoe6FOgGqa+FwMsE2QbPjh/n3g/jXQ==", + "version": "3.36.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.36.1.tgz", + "integrity": "sha512-AzsuD9OfxTOZMMvTRhlN3oHBwOmFN7tDh27LzqmHt4+uOgPhJT7ZM7/kVs/8/o0WxayMUIk3hBmCFRHv1FUoag==", "license": "Apache-2.0", "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, + "node_modules/@remix-run/route-pattern": { + "version": "0.22.1", + "resolved": "https://registry.npmjs.org/@remix-run/route-pattern/-/route-pattern-0.22.1.tgz", + "integrity": "sha512-czdGJWh09Lapvcqt7Vb09KlrU2PhRJ8xQaI+AItTuD9aDJ5MAgxnS38lnys6Ll+6RJEqPiaUqTwIQhVLwFvv+w==", + "license": "MIT" + }, "node_modules/@restart/hooks": { "version": "0.4.16", "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", @@ -1325,9 +1297,9 @@ } }, "node_modules/@rolldown/binding-android-arm-eabi": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.6.tgz", - "integrity": "sha512-b+jTcARdTiFLI6jB4a5XjTm0RWd6KcRfQj/I2356fxUZemiho9zQLxo0RtCuMDAyKcLo6cEltkgbQp6d1+sjjQ==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.9.tgz", + "integrity": "sha512-tNISae1QEf/vkb3xkRcjV5SEdzPE97We5IVaa2Z8jSszQPZ8U60B/YCYpw4QI7VidYsBtKavczXf+DyDs9WGxw==", "cpu": [ "arm" ], @@ -1341,9 +1313,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.6.tgz", - "integrity": "sha512-lkWU8ZJaRk9q3CIEY1Tc7vIFALp3Xw5NfGJo2hQg5oIqNgxWi1zI+IiDEK3r70BF5Dzol1tcXsnzsRc8NLhG+Q==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.9.tgz", + "integrity": "sha512-YC8YsI30o606GTZi0VyzYlsDKFP8W61i/QzayHDkLbNEz/IShqAmTa+hsJRj13xTHA0H+6fk4b2UmGn+Q/cMlg==", "cpu": [ "arm64" ], @@ -1357,9 +1329,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.6.tgz", - "integrity": "sha512-dgR56NYnvAszm7Ob1B2/Vn0e8bUQYZH2UjVaMMtMVOCKFSfjhfLmuA/9+O+F+ajUdG6B/bSssrKW6JJYASa8jA==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.9.tgz", + "integrity": "sha512-IwhlH3qK5urrY8hZiEgGkHKEFN901p/p2bjxCxJlr4GyNnF7wYpUvK+Y43uaRYuC4hpfjzbR3SJC3arX1jGvmw==", "cpu": [ "arm64" ], @@ -1373,9 +1345,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.6.tgz", - "integrity": "sha512-vpVxFvUCFioJqug7OTvqptkc4yb8UX0AwfDmJpaR/0sWz+BUmqSVAf7c8JkUgnN8YLspb4a/N6NhTyMAmdyQ7Q==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.9.tgz", + "integrity": "sha512-XxpJfVzFh+jilRxIXUqcfYAYcunIc/XEzIizsOL1fcJee5Sf7H3mH8WlLmfHfluz5amqR88QQo9izKtmMlavAw==", "cpu": [ "x64" ], @@ -1389,9 +1361,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.6.tgz", - "integrity": "sha512-h1wG6Y6K3JlRswxsI64qQJqBAy4vrLuHgRbc8CZMGSWTOFRY6ghMApM1NKzB2I0n5xV1fjkE18SuVl2QpLeNpA==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.9.tgz", + "integrity": "sha512-kSfvhmgeWyfkbT3p/1s5vSgboogoah2zkm9fX2zjg2hHxSV7T4KhMWRUUaRk4OXNqoD3QAUeRqLcs1aZOK4U1g==", "cpu": [ "x64" ], @@ -1405,9 +1377,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.6.tgz", - "integrity": "sha512-tbCiqub0q2MVWJKgF5PoAlNWCtQydiOYSLIkd8sByqK/6MMYLJRcSXSYodqYtd0O+Fw7QaVmKKlS4oL94YRZ0w==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.9.tgz", + "integrity": "sha512-1RVzG17pxqbTfYLC352JlLt6kKLG+6Hr30n8DlIJqsnV5luUDd2Qdx9Ayw1Cabfyb1K9k0jXEZ7evxkRoT+uiw==", "cpu": [ "arm" ], @@ -1421,9 +1393,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.6.tgz", - "integrity": "sha512-oxK9+baEBPhZG5HB4URY+uU04zJWeZlH6Tb9rB5DK4DF9XR1uXNLXt5Q5ZsugTKayNCNLhkcwz/ye74hRI98dg==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.9.tgz", + "integrity": "sha512-BXqPvZ2drqVD+/Z8UpKwcs4Mp7grM+eGFku4CAEKrEtcbAsUpzREphK1sogCRZGreVPiMkiiBtw0n3TPteuqvw==", "cpu": [ "arm64" ], @@ -1437,9 +1409,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.6.tgz", - "integrity": "sha512-muWCk27FVBEZtv0MsK8gnfSmgczA8KQ0uRVJbTABKhkRfQc38aUrcb7fhi3BNiyseFmgcRsoMfQsSNJ+DbZdSw==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.9.tgz", + "integrity": "sha512-11vWvo8YDwLzukt27J3aYDWU+gg2P7J+ZOmiJ0hkF5BXZDW7pVya7r40MXDy6ya0i9KamoENSVKIugvJNgFXIA==", "cpu": [ "arm64" ], @@ -1453,9 +1425,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.6.tgz", - "integrity": "sha512-eWDoSfU7Co2qj3vgB3Dt4lj1mG6CoWbcJQkRMP3XJplyCMtuaq3LHvPFjS9QIPvMGWVadJC04Xiy0IdcVPtnwQ==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.9.tgz", + "integrity": "sha512-a1tijMkdwsIARtc0F39ApURROkf3NwqinI6TOiSSWCTR7dT96dffNvMUtDHnq64wKNTIZOIlzKrFvvFUznJiyw==", "cpu": [ "ppc64" ], @@ -1469,9 +1441,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.6.tgz", - "integrity": "sha512-2bWNjRSIayvupRKxXUY2tWG9fYdoUlTqWywHRvE8Eq3GvuQ+f2HeIkve697fIt+IQs/PV8yFsdWuhp1aJ1PdnA==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.9.tgz", + "integrity": "sha512-x6SQNdAvv4c3hWqTMaWuawzMX9myaCs/yEmlGsxJzkdClnHW7FbrjQuSiRDhuSYzEYoEMhsaJy9qHG/XNemJPQ==", "cpu": [ "s390x" ], @@ -1485,9 +1457,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.6.tgz", - "integrity": "sha512-KekI0gS0wLxe1UBSQSjenBVwou/JkcQPDzBPICGZjxUv9k3RteHDPBQaiOicZUFKRIH2wKEimGwVpnJsbPzu7w==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.9.tgz", + "integrity": "sha512-9s0AZ8BFK5/n7B/TBoa2yJE3gI3KURrbXcPBlsAsvjU4VeJKgE90y1YtNxyEUIcHPQkg6/yfF3qihUrcM/Kf0Q==", "cpu": [ "x64" ], @@ -1501,9 +1473,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.6.tgz", - "integrity": "sha512-TvtPnfVr+HtyGiDmPK4VWmlNm7QhNNAcK5Q9A7aOXsI8545yCyaoMaicXrFZ72JzeYjaUVk7yT243zT0jzjFKQ==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.9.tgz", + "integrity": "sha512-P7VWAmV+WdJluH7ovnRGoiv2i8To7GAZ+kGzfGup635cyL7SyYl3lSUaA3Gp5THf0n/Co5EyEqb2zbqq+nMOHQ==", "cpu": [ "x64" ], @@ -1517,9 +1489,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.6.tgz", - "integrity": "sha512-iOo0VEay2XFhaCcH0sps5XIimkSuOnNaZrf6+ZkoSOQBJPKNU48RkmJv0/lSpipexu5P+ouFgafe5IGr/DiQfg==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.9.tgz", + "integrity": "sha512-1qixtsE4BK8h+yS3BfmZ09UhA7O/N4IACva6YBr7EBvCJraByTuRcgOTaiA62Tm0vey3UcKXLOaoGHtYmNGEVg==", "cpu": [ "arm64" ], @@ -1533,9 +1505,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.6.tgz", - "integrity": "sha512-y5NTmmasMS455JlOCO4ZM9krIchv3Mvm1crL1iUPGOPgEzSkves9n0SdC5Sjz6+qWDFhd8/JpfWMH8NSWNHe+A==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.9.tgz", + "integrity": "sha512-ok8IQjcEPs1AKZfuEUznVBrJw+gK4soq+bx8b1X2XoMqVClarc1q5JDmVtWXY1xfr6ZuHTAsPXHTgTrqKTZeww==", "cpu": [ "arm64" ], @@ -1549,9 +1521,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.6.tgz", - "integrity": "sha512-np8iZSLfXlAD4kWhiyq/u0Yt8oZDtRQ8lGhQaCXo2rl37KNjeU0GjJuwr4P3oeZ++ROfofsKNBqR5LTO8aXyWQ==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.9.tgz", + "integrity": "sha512-Ip2mXoU0hM0boq3Rf+ekuT653OROSo6aSYcPT1VHE4q52KvyxgFkQgrgb/IEsxOuvQ2fZZbs8khJAyCEPM24/g==", "cpu": [ "x64" ], @@ -1571,9 +1543,9 @@ "license": "MIT" }, "node_modules/@sinclair/typebox": { - "version": "0.34.49", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", - "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", "dev": true, "license": "MIT" }, @@ -1590,13 +1562,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, - "license": "MIT" - }, "node_modules/@stylistic/eslint-plugin": { "version": "5.10.0", "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.10.0.tgz", @@ -1627,16 +1592,10 @@ "tslib": "^2.8.0" } }, - "node_modules/@swc/helpers/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, "node_modules/@testing-library/dom": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", - "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.2.tgz", + "integrity": "sha512-yzr2S9HyAIdhz2/6qHgbs665Q7PKVcDF05vsOlHPxG1mo36gKVesdYVeDLnXgfjJ03CrKRk08knc6+E/9m8v2Q==", "dev": true, "license": "MIT", "peer": true, @@ -1655,10 +1614,9 @@ } }, "node_modules/@testing-library/jest-dom": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.10.0.tgz", - "integrity": "sha512-HQwu0KaB2zyT0iLzBL+8CLyZDL3KlZlZJ+2iyc9uCUnlJVskJU/UlPuVCyIPhtukjPQdT2QNoR5nCP5FqTmmDQ==", - "deprecated": "Incorrect minor release with breaking changes (Node >=22 and required @testing-library/dom peer). Use 6.9.1 for the 6.x line, or upgrade to 7.0.0.", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-7.0.1.tgz", + "integrity": "sha512-oMDTC3oA+6CXSO2JZnvOI7CA6oVub6kij5ggk9ohwye5slmkwxYDXcPOVxgMw/RQlticjtO0C1RZkR97HgrWMw==", "dev": true, "license": "MIT", "dependencies": { @@ -1675,7 +1633,13 @@ "yarn": ">=1" }, "peerDependencies": { - "@testing-library/dom": ">=10 <11" + "@testing-library/dom": ">=10 <11", + "vitest": ">= 0.32" + }, + "peerDependenciesMeta": { + "vitest": { + "optional": true + } } }, "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { @@ -1714,9 +1678,9 @@ } }, "node_modules/@testing-library/user-event": { - "version": "14.6.6", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.6.tgz", - "integrity": "sha512-Jbs9FpkkIDw8FgSc6kOVsOv8JuuqGAL7J4X1oot77JxAoDlkNn2GRkd0aYRVuQ+pVQAiHWVkE4rX/dkF5fBiCw==", + "version": "14.6.7", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.7.tgz", + "integrity": "sha512-MPCpX8bxe8zS+JmmTwLp8jd0dy1rAm60Te/SL8JrQM3qvQJcBOs1d7IefJMyZzqM3EWBrDn/LWDt1BCGu4ASfg==", "dev": true, "license": "MIT", "engines": { @@ -1728,9 +1692,9 @@ } }, "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", - "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.4.tgz", + "integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==", "dev": true, "license": "MIT", "optional": true, @@ -1738,14 +1702,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@tybys/wasm-util/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -1787,9 +1743,9 @@ } }, "node_modules/@types/d3-shape": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", - "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-kVd74ta9eof3eJOvbNd1vGKS/XERRyQbT26Og63hIsvDO84cjD5gEOhsXf26w3FSoNlPVz84DOFcKv/oou+fMw==", "license": "MIT", "dependencies": { "@types/d3-path": "*" @@ -1833,9 +1789,9 @@ } }, "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", "license": "MIT", "dependencies": { "@types/unist": "*" @@ -1893,16 +1849,16 @@ } }, "node_modules/@types/jest/node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -1931,12 +1887,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "26.1.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.2.tgz", - "integrity": "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==", + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.1.tgz", + "integrity": "sha512-VqGJBMCtdhqkBUCcBLvywI0NJ+KLuVzgNnlBUNFOQjqVxzo2lxLUNg1DSey8+u2u6ktswSAxg+s68QLzWHNOuA==", "license": "MIT", "dependencies": { - "undici-types": "~8.3.0" + "undici-types": "~8.9.0" } }, "node_modules/@types/prop-types": { @@ -1952,21 +1908,21 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.2.18", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", - "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", + "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", "license": "MIT", "dependencies": { "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { - "version": "19.2.5", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.5.tgz", - "integrity": "sha512-fMPwH9v7r/pp43yUd2/Mbiex5KouJwwR3dzHkhLREUC6764VyDsqxhAxv6OFEYR1RhjOyD1naqba8ECDBe7ZQg==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==", "license": "MIT", "peerDependencies": { - "@types/react": "^19.2.0" + "@types/react": "^19.3.0" } }, "node_modules/@types/react-router-bootstrap": { @@ -2059,17 +2015,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.66.0.tgz", - "integrity": "sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A==", + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.70.0.tgz", + "integrity": "sha512-/v8HZt6RlyIZxB3ntehELOcUcfxKPVGWXnQdJuHRmzrqgF8nQypcC/oxGW+Ot4VGKDq81XugPKxx0n5PBtf9PA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.66.0", - "@typescript-eslint/type-utils": "8.66.0", - "@typescript-eslint/utils": "8.66.0", - "@typescript-eslint/visitor-keys": "8.66.0", + "@typescript-eslint/scope-manager": "8.70.0", + "@typescript-eslint/type-utils": "8.70.0", + "@typescript-eslint/utils": "8.70.0", + "@typescript-eslint/visitor-keys": "8.70.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -2082,15 +2038,15 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.66.0", + "@typescript-eslint/parser": "^8.70.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", - "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.9.tgz", + "integrity": "sha512-brTTsvFRt5C1gGHtPst/281UjPD5t9fBqbgoMPlVWy11ZLTPfu7HxK4ZYqO9H7o/yC9rSTCI85EaQ4OoY12qYw==", "dev": true, "license": "MIT", "engines": { @@ -2098,16 +2054,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.66.0.tgz", - "integrity": "sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g==", + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.70.0.tgz", + "integrity": "sha512-zYvrmj9Yxd63UGaXw+kdt6A0F0s0qveJyuatIM77bYC2DE4pgmg7a50u8LR7PRtXd0x+h+Tl3eXabGm06SWd3Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.66.0", - "@typescript-eslint/types": "8.66.0", - "@typescript-eslint/typescript-estree": "8.66.0", - "@typescript-eslint/visitor-keys": "8.66.0", + "@typescript-eslint/scope-manager": "8.70.0", + "@typescript-eslint/types": "8.70.0", + "@typescript-eslint/typescript-estree": "8.70.0", + "@typescript-eslint/visitor-keys": "8.70.0", "debug": "^4.4.3" }, "engines": { @@ -2123,14 +2079,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.66.0.tgz", - "integrity": "sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g==", + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.70.0.tgz", + "integrity": "sha512-hFHbTNqhU9G+2eKFXCBVb1tjFT/LceiJ4+HfLO4pTpDI0KHi6iajpcFFkaSQ9gXmCh7n82A0PthaayEdN6mspQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.66.0", - "@typescript-eslint/types": "^8.66.0", + "@typescript-eslint/tsconfig-utils": "^8.70.0", + "@typescript-eslint/types": "^8.70.0", "debug": "^4.4.3" }, "engines": { @@ -2145,14 +2101,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.66.0.tgz", - "integrity": "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g==", + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.70.0.tgz", + "integrity": "sha512-8nP3Kwh5hlgZ4FicGvmznAmJe8UL4sdU8tLukrPaMuQmDuk4Y8xYfzu/aYZW4xT2JCgc7H/TpDI5cGlxcWJSqQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.66.0", - "@typescript-eslint/visitor-keys": "8.66.0" + "@typescript-eslint/types": "8.70.0", + "@typescript-eslint/visitor-keys": "8.70.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2163,9 +2119,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.66.0.tgz", - "integrity": "sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g==", + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.70.0.tgz", + "integrity": "sha512-adnkeeNq9Sq1sUf4+FRVc0KdgYghzsgFpZSQVZVvY0LCuUuN0FnQgyGzCJeC4fW1cdXseBAjU2EOqUIjbNcZUw==", "dev": true, "license": "MIT", "engines": { @@ -2180,15 +2136,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.66.0.tgz", - "integrity": "sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g==", + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.70.0.tgz", + "integrity": "sha512-NUMKIhYVaVIVLnRL9CRt+VVcuLgSHUCpXn4/+K8wql+vdInUzvx8BjUO1oJ7cG9shjFJKtF8F8Hh2kCh3/KBVw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.66.0", - "@typescript-eslint/typescript-estree": "8.66.0", - "@typescript-eslint/utils": "8.66.0", + "@typescript-eslint/types": "8.70.0", + "@typescript-eslint/typescript-estree": "8.70.0", + "@typescript-eslint/utils": "8.70.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -2205,9 +2161,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.66.0.tgz", - "integrity": "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ==", + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.70.0.tgz", + "integrity": "sha512-asTOIYhDg4zdzOScCyaytrsV3cR6B4ecPQlXw/dJIm7J/MZTtCtfVII9JD8Geh4jTCrK/Xe6cg5UevoleMcoJQ==", "dev": true, "license": "MIT", "engines": { @@ -2219,16 +2175,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.66.0.tgz", - "integrity": "sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg==", + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.70.0.tgz", + "integrity": "sha512-d9NmHMPEKQ7QCLLm1jI3zmoQBwT5KwFYjXBJ9ymZfKCUU+5rmTRykKAFvH5Qn/ZCds3CEAFS9OC9M/jkl0X2bA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.66.0", - "@typescript-eslint/tsconfig-utils": "8.66.0", - "@typescript-eslint/types": "8.66.0", - "@typescript-eslint/visitor-keys": "8.66.0", + "@typescript-eslint/project-service": "8.70.0", + "@typescript-eslint/tsconfig-utils": "8.70.0", + "@typescript-eslint/types": "8.70.0", + "@typescript-eslint/visitor-keys": "8.70.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -2257,9 +2213,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2286,16 +2242,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.66.0.tgz", - "integrity": "sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA==", + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.70.0.tgz", + "integrity": "sha512-oZmtKJz/4fufZ2p3+Cn3ijEojcdfR+1zYDH2xKYrEly0dR/Q/1xUPRCOlKGxod78nWlU2UnDe09GZ3TaknBFGA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.66.0", - "@typescript-eslint/types": "8.66.0", - "@typescript-eslint/typescript-estree": "8.66.0" + "@typescript-eslint/scope-manager": "8.70.0", + "@typescript-eslint/types": "8.70.0", + "@typescript-eslint/typescript-estree": "8.70.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2310,13 +2266,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.66.0.tgz", - "integrity": "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==", + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.70.0.tgz", + "integrity": "sha512-BoC8PiO4Hkdo0TVJh9Ntxr5MxPDI7/oFsrygN5ADelFSeXG/qgNuucIGA+L5Z6JpPTE/uRfcTWtscjbUaufepQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/types": "8.70.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -2341,9 +2297,9 @@ } }, "node_modules/@ungap/structured-clone": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.2.tgz", - "integrity": "sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz", + "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", "license": "ISC" }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { @@ -2660,9 +2616,9 @@ ] }, "node_modules/@vitejs/plugin-react": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz", - "integrity": "sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz", + "integrity": "sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==", "license": "MIT", "dependencies": { "@rolldown/pluginutils": "^1.0.1" @@ -2673,6 +2629,7 @@ "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", + "oxc-transform-react": "^0.145.0", "vite": "^8.0.0" }, "peerDependenciesMeta": { @@ -2681,33 +2638,34 @@ }, "babel-plugin-react-compiler": { "optional": true + }, + "oxc-transform-react": { + "optional": true } } }, "node_modules/@vitest/coverage-v8": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.11.tgz", - "integrity": "sha512-8MVGEFnJIcdGjcbfKmeq8z0pZHH0JlVtoVZH9Q/qwUp6wyFnEJUBMrw9DCaj+ra3vShGmhavjalMIhPNxZAUcw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-5.0.1.tgz", + "integrity": "sha512-FRC8ACiudC3dI6MTplzRSYWHDRnIv2IPfbzs4FdoJNsMal/35sWV8hwIfV8ZcqzSPy+uXHeMVONt9CEqtOU17w==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.11", - "ast-v8-to-istanbul": "^1.0.0", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.2.0", - "magicast": "^0.5.2", - "obug": "^2.1.1", - "std-env": "^4.0.0-rc.1", - "tinyrainbow": "^3.1.0" + "@vitest/istanbul-lib-coverage": "^1.0.0", + "@vitest/istanbul-lib-report": "^1.0.0", + "ast-v8-to-istanbul": "^1.0.5", + "magicast": "^0.5.4", + "obug": "^2.1.4", + "std-env": "^4.2.0", + "tinyrainbow": "^3.1.1" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.1.11", - "vitest": "4.1.11" + "@vitest/browser": "5.0.1", + "vitest": "5.0.1" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -2715,34 +2673,40 @@ } } }, - "node_modules/@vitest/expect": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", - "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", + "node_modules/@vitest/istanbul-lib-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@vitest/istanbul-lib-coverage/-/istanbul-lib-coverage-1.0.1.tgz", + "integrity": "sha512-k3DJZ8LhMBK9NS4SclF1ASD3OgXEWDorbIcPTRDK0/Zae6fRvu+fJRxtFdLfHsa9Y24beCdPnoNZ4LviTNstfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + } + }, + "node_modules/@vitest/istanbul-lib-report": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@vitest/istanbul-lib-report/-/istanbul-lib-report-1.0.1.tgz", + "integrity": "sha512-1EOLRfsTMnyAr3+kEAsP4o9dhaDlGPpD7H5iLBBeq//YpNB1VIahkPhB+eRp9N2Dkfw8oySROjE3yf9XDeaIkQ==", "dev": true, "license": "MIT", "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.11", - "@vitest/utils": "4.1.11", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" + "@vitest/istanbul-lib-coverage": "1.0.1" }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": ">=22" } }, "node_modules/@vitest/mocker": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", - "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.1.tgz", + "integrity": "sha512-6K1DoBNAPGvuOcSsGA4D6x+5zEEff/KmOOP3uetT2TrGpVfI+HRHRnJJfKi5ib/g1vx8IYHQD8s0pbJz8WQI7Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.11", + "@jridgewell/trace-mapping": "0.3.31", + "@vitest/spy": "5.0.1", "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" + "magic-string": "^1.2.3" }, "funding": { "url": "https://opencollective.com/vitest" @@ -2760,78 +2724,20 @@ } } }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", - "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", - "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.11", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", - "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.11", - "@vitest/utils": "4.1.11", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, "node_modules/@vitest/spy": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", - "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", - "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.1.tgz", + "integrity": "sha512-rbto/mF/SGERxEgYOek7Xm6B9b+y+mVoo+f4b2LymYO8zM1b7uB5nHuhVMTP2hxdzgxvGiZYGxGIaMvL5y180Q==", "dev": true, "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.11", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/acorn": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", - "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", "dev": true, "license": "MIT", "bin": { @@ -2932,13 +2838,13 @@ "license": "MIT" }, "node_modules/are-docs-informative": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", - "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.1.1.tgz", + "integrity": "sha512-sqRsNQBwbKLRX0jV5Cu5uzmtflf892n4Vukz7T659ebL4pz3mpOqCMU7lxMoBTFwnp10E3YB5ZcyHM41W5bcDA==", "dev": true, "license": "MIT", "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/argparse": { @@ -2960,12 +2866,6 @@ "node": ">=10" } }, - "node_modules/aria-hidden/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, "node_modules/aria-query": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", @@ -2994,18 +2894,18 @@ } }, "node_modules/array-includes": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", - "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.2.0.tgz", + "integrity": "sha512-VXY5eFRarnXcYxwBjJzPmEhH55+rmP79/+ueDhi0F+TuqfHCItagIHqxeUZrmgrOPa31QTh9H85DjX3FfJ0FTg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", + "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.24.0", - "es-object-atoms": "^1.1.1", - "get-intrinsic": "^1.3.0", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "es-shim-unscopables": "^1.1.0", "is-string": "^1.1.1", "math-intrinsics": "^1.1.0" }, @@ -3145,9 +3045,9 @@ } }, "node_modules/ast-v8-to-istanbul": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.4.tgz", - "integrity": "sha512-0bC0/4bTSrnwdhU3IsZDwEdojvuPrSg59OYZfKsLRtJZ0u8VBx9DebfqqG8bRdCC0I7vjgxmPi41P0lpkhJHtA==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.6.tgz", + "integrity": "sha512-fvpl29helSO2w/z7utIbrkNXILdrLwDwAMH2I/zPKlGf5244+gf+B4cyS1sANcrPY2h+hWCGSgC8N61s/+AF9A==", "dev": true, "license": "MIT", "dependencies": { @@ -3272,9 +3172,9 @@ } }, "node_modules/bidi-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", - "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.1.0.tgz", + "integrity": "sha512-fX1Onk0tdVPC7obPWB5EbJ1z7NVhLq4m2xZLq2YXBkxzMXIGRpNMU88n0EPgWseKl12J7zXs7qrDxPK4sRs2fg==", "dev": true, "license": "MIT", "dependencies": { @@ -3315,9 +3215,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -3633,9 +3533,9 @@ } }, "node_modules/cli-truncate/node_modules/string-width": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", - "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", "dev": true, "license": "MIT", "dependencies": { @@ -3730,9 +3630,9 @@ } }, "node_modules/comment-parser": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.7.tgz", - "integrity": "sha512-0h+uSNtQGW3D98eQt3jJ8L06Fves8hncB4V/PKdw/Qb8Hnk19VaKuTr55UNRYiSoVa7WwrFls+rh3ux9agmkeQ==", + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.9.tgz", + "integrity": "sha512-+2AvZKjJaNq9GQljm3tr0utwrig5BL+jgJGvz5rDpGKNIp+ojcRXWUwBbh/sGIi1QCprR6PsKFakub+w1v+4hQ==", "dev": true, "license": "MIT", "engines": { @@ -3756,13 +3656,6 @@ "dev": true, "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", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, "node_modules/cookie": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", @@ -4011,6 +3904,21 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -4066,9 +3974,9 @@ } }, "node_modules/dayjs": { - "version": "1.11.21", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", - "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", + "version": "1.11.23", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.23.tgz", + "integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==", "dev": true, "license": "MIT" }, @@ -4231,9 +4139,9 @@ } }, "node_modules/dompurify": { - "version": "3.4.14", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.14.tgz", - "integrity": "sha512-dVoH9z+MY+C9IilgGCk3YfFqjLi3fChm2OiKJMzh6axrJ5qwxqWaZamgmHrpv22CN/KdbZJuGEGgfQoL00LTdg==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.15.tgz", + "integrity": "sha512-EUBjM+B+lkDE41iE82DDSCfkoPGfXx8IxFxPMjNzm/Uk4xDet77rTN9wqlxlVg71kK7XGuUMv6wUxJUwwv+Xyw==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -4283,9 +4191,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.24.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.1.tgz", - "integrity": "sha512-7DdUaTjmNwMcH2gLr1qycesKII3BK4RLy/mdAb7x10Lq7bR4aNKHt1BR1ZALSv0rPM/hF5wYF0PhGop/rJm8vw==", + "version": "5.25.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.25.1.tgz", + "integrity": "sha512-nGXts5znJzmWPu+mIE9izCOzdg63oJca2mDzGWWTth7sr4aCToKcoyFVBQwN75Ij5Pf6p510EwkTqViTRzDV+w==", "dev": true, "license": "MIT", "dependencies": { @@ -4297,9 +4205,9 @@ } }, "node_modules/entities": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", - "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.1.0.tgz", + "integrity": "sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -4431,9 +4339,9 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.3.tgz", - "integrity": "sha512-0PuBxFi+4uPanB97iDxCLWuHeYud2FALrw5HFZGtAF38UpJDbDC8frwp2cnDyae692CQ0dou60UwWfhgsa4U/g==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.4.0.tgz", + "integrity": "sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==", "dev": true, "license": "MIT", "dependencies": { @@ -4508,13 +4416,14 @@ } }, "node_modules/es-to-primitive": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.1.tgz", - "integrity": "sha512-CxN9N56HYfd2m/acc/NOFrZQsN9kU4eh+2kk6A707Kz1krH8tKmfrs5RnftB8WNX80T0NS7vSQsDOlg23diR2g==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", "dev": true, "license": "MIT", "dependencies": { "es-abstract-get": "^1.0.0", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "is-callable": "^1.2.7", "is-date-object": "^1.1.0", @@ -4544,6 +4453,7 @@ "version": "9.39.5", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", "dependencies": { @@ -4727,26 +4637,26 @@ } }, "node_modules/eslint-plugin-import-x/node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/eslint-plugin-import-x/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.5" + "brace-expansion": "^5.0.8" }, "engines": { "node": "18 || 20 || >=22" @@ -4756,18 +4666,19 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "63.3.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-63.3.3.tgz", - "integrity": "sha512-xI4IeVRzRFA2DGHrPLIxF3U+oJHU3FE+P9Zb27fVs5dPHgfcpoAs0PyCbznVhK7pwR+9BPUztFeXSgpw/CL4Yg==", + "version": "64.5.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-64.5.2.tgz", + "integrity": "sha512-GirLf/jpVQ/HSLVT98ztIrJ8GUlWWrrwExkofqzeIjjOxlqFtyqnpkRs30FLwEKh0xHEpgy35CU0qFn0I/Mh9w==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "@es-joy/jsdoccomment": "~0.91.0", + "@es-joy/jsdoccomment": "~0.97.0", "@es-joy/resolve.exports": "1.2.0", - "are-docs-informative": "^0.0.2", - "comment-parser": "1.4.7", + "@typescript-eslint/utils": "^8.69.0", + "are-docs-informative": "^0.1.1", + "comment-parser": "1.4.8", "debug": "^4.4.3", - "escape-string-regexp": "^4.0.0", + "escape-string-regexp": "^5.0.0", "espree": "^11.2.0", "esquery": "^1.7.0", "html-entities": "^2.6.0", @@ -4778,10 +4689,39 @@ "to-valid-identifier": "^1.0.0" }, "engines": { - "node": "^22.13.0 || >=24" + "node": "^22.22.2 || >=24.15.0" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0" + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/comment-parser": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.8.tgz", + "integrity": "sha512-rKZTGo4fzKYna8UcL0isTg5wkBNla7bxTypLwZQXjIdi++IdP1OJ41rI5Mti3/jltkPujbu4i9LIARYA+zpotQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint-plugin-jsdoc/node_modules/eslint-visitor-keys": { @@ -4816,9 +4756,9 @@ } }, "node_modules/eslint-plugin-n": { - "version": "18.2.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-18.2.2.tgz", - "integrity": "sha512-gOO0lIqwEjZ750kv9/SptCWArUoAZXJoBr0vYWTO2dCBxctHUXlBIigiC8xuxxr/NKqgIT6Ehz1xRcilj8a5cA==", + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-18.3.0.tgz", + "integrity": "sha512-cPVguuDe6DrIPb/qUXHf8P89MaVTUmiYWwpt5gX5AILsvRIiZAxMFXcFR6QHYBksqKJpjfUBlL/RleCJUWcD7w==", "dev": true, "license": "MIT", "dependencies": { @@ -5105,18 +5045,18 @@ } }, "node_modules/expect": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz", - "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.5.1.tgz", + "integrity": "sha512-m8YrYgvKe9+9gEnWEuKz+qCGfHqkrff7PPfyDnOFkjsfYRKqiYyOxDFMFieCGAuhtk/VNm63tvNgKZVzVy+Hvg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "30.4.1", - "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.4.1", - "jest-message-util": "30.4.1", - "jest-mock": "30.4.1", - "jest-util": "30.4.1" + "@jest/expect-utils": "30.5.1", + "@jest/get-type": "30.5.0", + "jest-matcher-utils": "30.5.1", + "jest-message-util": "30.5.1", + "jest-mock": "30.5.1", + "jest-util": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -5231,9 +5171,9 @@ } }, "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", "dev": true, "license": "ISC" }, @@ -5451,9 +5391,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", - "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.3.tgz", + "integrity": "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==", "dev": true, "license": "MIT", "dependencies": { @@ -5503,9 +5443,9 @@ } }, "node_modules/globals": { - "version": "17.9.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.9.0.tgz", - "integrity": "sha512-m/MvAW61QVU5VDNF1Vj8axt016h8w7L5TU1e9zlab7XIttAT2YAlCwl75K1fOqvMM9apmD7lbCIRhpfkhmxhCg==", + "version": "17.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.12.0.tgz", + "integrity": "sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==", "dev": true, "license": "MIT", "engines": { @@ -5740,13 +5680,6 @@ ], "license": "MIT" }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, "node_modules/html-parse-stringify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-4.0.1.tgz", @@ -5792,9 +5725,9 @@ } }, "node_modules/i18next": { - "version": "26.3.6", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.3.6.tgz", - "integrity": "sha512-Bu5Z2nAXgfVyM8xvW3jk9EKRIuX37PudsrBViThNFx7CR7aaYTpP01cxNB/E4c4UUzTDiAZRstEhsRfPOL/8xA==", + "version": "26.4.2", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.4.2.tgz", + "integrity": "sha512-RX+R0VLg13IbvRuJSxnqykUFS9vQZTl8wYpWPCIUDWVrSGjsQywB5Y+pjzrkboxGAuYfJZVH1InFTdgBdxq6ug==", "funding": [ { "type": "individual", @@ -5851,9 +5784,9 @@ } }, "node_modules/immutable": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.7.tgz", - "integrity": "sha512-47Xb+LFbZ/ZIjQMj6Q5J3IfK7PJFuqRdFOC9FpGgRTK6U2dAEVmkR9hp58qU4FpYux5YXpneDwkj2EP6lppzFA==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz", + "integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==", "devOptional": true, "license": "MIT" }, @@ -6498,58 +6431,6 @@ "dev": true, "license": "MIT" }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/iterator.prototype": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", @@ -6569,16 +6450,16 @@ } }, "node_modules/jest-diff": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz", - "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.5.1.tgz", + "integrity": "sha512-e3cNNMpv8Kh20MjjphTXs+3Vz7DQyLM1nft7KJhnh46atFhjVJRa+0Hq0beywuwsACtMQUBihQkFl8zxb7gt1Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/diff-sequences": "30.4.0", - "@jest/get-type": "30.1.0", + "@jest/diff-sequences": "30.5.0", + "@jest/get-type": "30.5.0", "chalk": "^4.1.2", - "pretty-format": "30.4.1" + "pretty-format": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -6598,32 +6479,32 @@ } }, "node_modules/jest-diff/node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz", - "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.5.1.tgz", + "integrity": "sha512-aroZVqwOz/wC2y6pC+obgFWKV9viaQWQTTSB6W5H55+egtUczIfXR/rxExTv92xD/ADYwpqaYfVWx1aqwKg7FA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", + "@jest/get-type": "30.5.0", "chalk": "^4.1.2", - "jest-diff": "30.4.1", - "pretty-format": "30.4.1" + "jest-diff": "30.5.1", + "pretty-format": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -6643,36 +6524,36 @@ } }, "node_modules/jest-matcher-utils/node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-message-util": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz", - "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.5.1.tgz", + "integrity": "sha512-UdQlLdd9wL/Ys7xRErckqwD6wPlSZYueosSWuHc1r2ztGLwlgPvtSJq2+BPEgaEY13WLvfFbmhTj8pba0Sd1jg==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@jest/types": "30.4.1", + "@jest/types": "30.5.1", "@types/stack-utils": "^2.0.3", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "jest-util": "30.4.1", + "jest-util": "30.5.1", "picomatch": "^4.0.3", - "pretty-format": "30.4.1", + "pretty-format": "30.5.1", "slash": "^3.0.0", "stack-utils": "^2.0.6" }, @@ -6694,40 +6575,41 @@ } }, "node_modules/jest-message-util/node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-mock": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz", - "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.5.1.tgz", + "integrity": "sha512-9fVjc3leUpGID2/by/LU4Dvdcp7PFh9LlxS3QRWK3ABm+KtvEVsG/AEGeLY3gKOZsjkBxyfwGltoAVlW7dygHg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.4.1", + "@jest/expect-utils": "30.5.1", + "@jest/types": "30.5.1", "@types/node": "*", - "jest-util": "30.4.1" + "jest-util": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-regex-util": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", - "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.5.0.tgz", + "integrity": "sha512-Mg0WK7A6xRHLSA1udJ8y9f3lM0uUhFTBnLKzwPmqB9AylvpleJ6BLemR8K9dK27DY+cesDryoA7yLZCAHsPG1A==", "dev": true, "license": "MIT", "engines": { @@ -6735,13 +6617,13 @@ } }, "node_modules/jest-util": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz", - "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.5.1.tgz", + "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.4.1", + "@jest/types": "30.5.1", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", @@ -6759,9 +6641,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", - "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, "funding": [ { @@ -6789,49 +6671,53 @@ "license": "MIT" }, "node_modules/jsdoc-type-pratt-parser": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-8.0.0.tgz", - "integrity": "sha512-uQu/fXVqVaMg6gM8/E5G5+eygVcZ1NV0Z51CvqhNa2bDWxvHMl484ETr6vph4oPyC+KUcbP/w2W2pewfCiR9aQ==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-9.2.1.tgz", + "integrity": "sha512-V4Ww4EHnTcTLSOMoB0FsF72JhQvcAsriCm/LWnxJeGWoxIjEL2l9na11abQok5SYShq8m0Gl02el/xAbTCulvQ==", "dev": true, "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.9", + "@types/node": "^26.4.0" + }, "engines": { - "node": ">=20.0.0" + "node": "^22.22.2 || >=24.15.0" } }, "node_modules/jsdom": { - "version": "29.1.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", - "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.0.1.tgz", + "integrity": "sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^5.1.11", - "@asamuzakjp/dom-selector": "^7.1.1", + "@asamuzakjp/css-color": "^6.0.5", + "@asamuzakjp/dom-selector": "^8.3.0", "@bramus/specificity": "^2.4.2", - "@csstools/css-syntax-patches-for-csstree": "^1.1.3", - "@exodus/bytes": "^1.15.0", + "@csstools/css-syntax-patches-for-csstree": "^1.1.7", + "@exodus/bytes": "^1.15.1", "css-tree": "^3.2.1", "data-urls": "^7.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^6.0.0", "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.3.5", + "lru-cache": "^11.5.2", "parse5": "^8.0.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^6.0.1", - "undici": "^7.25.0", + "tough-cookie": "^6.0.2", + "undici": "^8.9.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^8.0.1", "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^16.0.1", + "whatwg-url": "^17.1.0", "xml-name-validator": "^5.0.0" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" }, "peerDependencies": { - "canvas": "^3.0.0" + "canvas": "^3.2.3" }, "peerDependenciesMeta": { "canvas": { @@ -6840,29 +6726,29 @@ } }, "node_modules/jsdom/node_modules/tldts": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.4.tgz", - "integrity": "sha512-kFXFK7O4WPextIUAOk8qtnw9dxR9UIXP9CjuH1cTBVBZMDeQcUPgr/IazGiw1B0Yiw5L75gHLWeW4iD793r90g==", + "version": "7.4.13", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.13.tgz", + "integrity": "sha512-iHtaIWWIbMDkCeJdTBzZFGgbluE5J+oHlb2g7+oAz1S1gpuVpabRZdQyd471Vl8UUkcz2vXSL8xZH2kyCe8tfA==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^7.4.4" + "tldts-core": "^7.4.13" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/jsdom/node_modules/tldts-core": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.4.tgz", - "integrity": "sha512-vwVLJVvvpslm7vqAH7+XNj/neA/Ynq7DT2EEcMuwc5YzN5XaMyRAqxwU+uX3azZ1FQtB2gvrvnLnAEkvYlVdfg==", + "version": "7.4.13", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.13.tgz", + "integrity": "sha512-mbYsrih5FRtGxs3Usvl/PqwJsNpp+jsmrdFviiK02teHDG0/HebBG/pqCylje3kzgXYzuLoHJF/0mz9W53t8Xg==", "dev": true, "license": "MIT" }, "node_modules/jsdom/node_modules/tough-cookie": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", - "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -7370,9 +7256,9 @@ } }, "node_modules/lru-cache": { - "version": "11.5.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", - "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -7391,43 +7277,27 @@ } }, "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.1.tgz", + "integrity": "sha512-8lyCu36ErXR0J9uaGKlKQoiLZKmtI63YGLE8G2o9jyRPdr4X47LusSOwgOJOzcVtp81fTAAjxR7BwKz682Jhow==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "@jridgewell/sourcemap-codec": "^1.6.0" } }, "node_modules/magicast": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz", - "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.5.tgz", + "integrity": "sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.3", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", "source-map-js": "^1.2.1" } }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/markdown-table": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", @@ -7888,9 +7758,9 @@ } }, "node_modules/micromark-extension-gfm-table": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", - "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.2.tgz", + "integrity": "sha512-pRzm4kDTu0MjlmBkxmS9yYhw60nncfcEwu9NNdPFSQEFXS95ZKyIIyTSHu/o3ReBUrLKYEq+7YaXCRn/bPB4MA==", "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -8393,9 +8263,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.17", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.17.tgz", - "integrity": "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==", + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", "funding": [ { "type": "github", @@ -8597,9 +8467,9 @@ } }, "node_modules/obug": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", - "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", + "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", @@ -8662,13 +8532,14 @@ "license": "MIT" }, "node_modules/own-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", - "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.2.tgz", + "integrity": "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.6", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" }, @@ -8806,13 +8677,6 @@ "dev": true, "license": "MIT" }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -8834,9 +8698,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "license": "MIT", "engines": { "node": ">=12" @@ -8866,9 +8730,9 @@ } }, "node_modules/postcss": { - "version": "8.5.26", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", - "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", "funding": [ { "type": "opencollective", @@ -8885,7 +8749,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.17", + "nanoid": "^3.3.18", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -8904,9 +8768,9 @@ } }, "node_modules/prettier": { - "version": "3.9.6", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", - "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.7.tgz", + "integrity": "sha512-T3mF5P7HFzZVLDQuCUNtJj6WK1pcpLMweMNUVwGb01bLNkH9AkKxtZvmInw8K6bnn2O3d6/5RHl6zSHiBAD0Og==", "dev": true, "license": "MIT", "bin": { @@ -9047,9 +8911,9 @@ } }, "node_modules/qs": { - "version": "6.15.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", - "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -9064,28 +8928,28 @@ } }, "node_modules/react": { - "version": "19.2.8", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", - "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz", + "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-aria": { - "version": "3.50.0", - "resolved": "https://registry.npmjs.org/react-aria/-/react-aria-3.50.0.tgz", - "integrity": "sha512-S0Os6QZk33fzUAKu1QLT9afoUaCBt1ZNdoiq0n2YMVgKIdNIQS8zxiZ8O9hYE6QyDkHKjD6q39LQZ+qaSAIgjw==", + "version": "3.52.1", + "resolved": "https://registry.npmjs.org/react-aria/-/react-aria-3.52.1.tgz", + "integrity": "sha512-fdZZruC9/x/joCg0mhKGs5aHpwrXLCSZ4GOJmhhYyiE0ffyEsk9MLFt9LCOCF9tn8ErTD+UDQP4oDUSlZkmpCg==", "license": "Apache-2.0", "dependencies": { - "@internationalized/date": "^3.12.2", - "@internationalized/number": "^3.6.7", - "@internationalized/string": "^3.2.9", - "@react-types/shared": "^3.36.0", + "@internationalized/date": "^3.12.4", + "@internationalized/number": "^3.6.8", + "@internationalized/string": "^3.2.10", + "@react-types/shared": "^3.36.1", "@swc/helpers": "^0.5.0", "aria-hidden": "^1.2.3", "clsx": "^2.0.0", - "react-stately": "3.48.0", + "react-stately": "3.50.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { @@ -9187,21 +9051,21 @@ } }, "node_modules/react-dom": { - "version": "19.2.8", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", - "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==", "license": "MIT", "dependencies": { - "scheduler": "^0.27.0" + "scheduler": "^0.28.0" }, "peerDependencies": { - "react": "^19.2.8" + "react": "^19.3.0" } }, "node_modules/react-i18next": { - "version": "17.0.12", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.12.tgz", - "integrity": "sha512-lFWPEGkxQ6RhusdUkysFBD58VHfSSzvHBzqMgN0SvfVpdQGfwtNkStTqdy08/sJd7s807qqutgx93fRpD0DJ3Q==", + "version": "17.0.14", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.14.tgz", + "integrity": "sha512-ZpMBfJL3BiXPuYHj5QMY1GwvKJNhE1vCxOJQ8BoMBdHP5XONTfi/Qss1nuRlnFdIl72PKo2jPmw+fxuLjRFgaQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.29.7", @@ -9233,22 +9097,6 @@ "license": "MIT", "peer": true }, - "node_modules/react-is-18": { - "name": "react-is", - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/react-is-19": { - "name": "react-is", - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz", - "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", - "dev": true, - "license": "MIT" - }, "node_modules/react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", @@ -9283,11 +9131,12 @@ } }, "node_modules/react-router": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-8.3.0.tgz", - "integrity": "sha512-qyPMvW83jGIct3yiieisxdk9M745anqhpIMKN5m1t6yBMfgVPpt77aHOqs5fUlEJRMCGffg9BaQLH9oPVOL7xQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-8.4.0.tgz", + "integrity": "sha512-JtydAkBvU9UTEe5qNC+POhu+ZBNM7rlKY2IegwXc7Idj7xfRG16x5RZrw3mju+XlqBxfvb2ky9P3jUp9WyWC1g==", "license": "MIT", "dependencies": { + "@remix-run/route-pattern": "^0.22.1", "cookie-es": "^3.1.1" }, "engines": { @@ -9317,13 +9166,13 @@ } }, "node_modules/react-router-dom": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.0.tgz", - "integrity": "sha512-Fi0yY6kgtKae/Th2xibdWK0KSdYZ4B53Gyf6wRtomOKWgpNm7H7+DyfDhncdz9FKbpS+1jmDhg3F4WoGJ+yFOA==", + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.4.tgz", + "integrity": "sha512-yrfmJHIpDG7taCpqKjT1G5B6q3O2K+RN8/fgNf0lTjCwiPbQ0ei6vXX9ZjQR+7ld8Tr7Z5xmyMnZ8YJrphWQUw==", "license": "MIT", "peer": true, "dependencies": { - "react-router": "7.18.0" + "react-router": "7.18.4" }, "engines": { "node": ">=20.0.0" @@ -9334,9 +9183,9 @@ } }, "node_modules/react-router-dom/node_modules/react-router": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.0.tgz", - "integrity": "sha512-pTTGt8J+ji1NOmYnjzT+bAJy/1zD+Jp4ziO6cL7T3ZLvXKtusO7BpFqlRXitqpcPVqllsIXFHRMt+2/k3Xn6HQ==", + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.4.tgz", + "integrity": "sha512-PUPQcMhMGRAslLcvtlPz/kmzBEWPhLdgLFrL7pLNepBL6dX0lWj4WD2cUYVgYCuT3jxvghYFg81cDTj44DhetQ==", "license": "MIT", "peer": true, "dependencies": { @@ -9357,15 +9206,15 @@ } }, "node_modules/react-stately": { - "version": "3.48.0", - "resolved": "https://registry.npmjs.org/react-stately/-/react-stately-3.48.0.tgz", - "integrity": "sha512-ImicSAG+lTotAe5izcs1fz49Zk48w7pDusqYg04WaPhCoej8BJ24soMu3iLXIrsi273s4P1gZrYGrqReMfgEEA==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/react-stately/-/react-stately-3.50.0.tgz", + "integrity": "sha512-TnckvpDQGU0672wEaLZqdzvhuSeXWjgW6vijMWxiKOxc8CosQUfPGISdcZyfHqjX3XMjEtRxj4w9HumvX4h4mw==", "license": "Apache-2.0", "dependencies": { - "@internationalized/date": "^3.12.2", - "@internationalized/number": "^3.6.7", - "@internationalized/string": "^3.2.9", - "@react-types/shared": "^3.36.0", + "@internationalized/date": "^3.12.4", + "@internationalized/number": "^3.6.8", + "@internationalized/string": "^3.2.10", + "@react-types/shared": "^3.36.1", "@swc/helpers": "^0.5.0", "use-sync-external-store": "^1.6.0" }, @@ -9390,9 +9239,9 @@ } }, "node_modules/readdirp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", - "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", "devOptional": true, "license": "MIT", "engines": { @@ -9658,12 +9507,12 @@ "license": "MIT" }, "node_modules/rolldown": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.6.tgz", - "integrity": "sha512-vMM4q3aixf46GiF1Kok8jDPFsEpXgFWGjUHXNkNHNm+Y2adXAG2dbX91jkti3i0ZRsOlcmbuzAz1poObSHCmUA==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.9.tgz", + "integrity": "sha512-hx/Pv0N1haXRb11qkfnK5MXB/iqr7i0yjWQqmO9uHqZpBgQSqzc8UsSnEpalsh+j1I8qQ2CkXAkJC8Br3dKSlg==", "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.147.0", + "@oxc-project/types": "=0.150.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -9673,21 +9522,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.6", - "@rolldown/binding-android-arm64": "1.2.6", - "@rolldown/binding-darwin-arm64": "1.2.6", - "@rolldown/binding-darwin-x64": "1.2.6", - "@rolldown/binding-freebsd-x64": "1.2.6", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.6", - "@rolldown/binding-linux-arm64-gnu": "1.2.6", - "@rolldown/binding-linux-arm64-musl": "1.2.6", - "@rolldown/binding-linux-ppc64-gnu": "1.2.6", - "@rolldown/binding-linux-s390x-gnu": "1.2.6", - "@rolldown/binding-linux-x64-gnu": "1.2.6", - "@rolldown/binding-linux-x64-musl": "1.2.6", - "@rolldown/binding-openharmony-arm64": "1.2.6", - "@rolldown/binding-win32-arm64-msvc": "1.2.6", - "@rolldown/binding-win32-x64-msvc": "1.2.6" + "@rolldown/binding-android-arm-eabi": "1.2.9", + "@rolldown/binding-android-arm64": "1.2.9", + "@rolldown/binding-darwin-arm64": "1.2.9", + "@rolldown/binding-darwin-x64": "1.2.9", + "@rolldown/binding-freebsd-x64": "1.2.9", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.9", + "@rolldown/binding-linux-arm64-gnu": "1.2.9", + "@rolldown/binding-linux-arm64-musl": "1.2.9", + "@rolldown/binding-linux-ppc64-gnu": "1.2.9", + "@rolldown/binding-linux-s390x-gnu": "1.2.9", + "@rolldown/binding-linux-x64-gnu": "1.2.9", + "@rolldown/binding-linux-x64-musl": "1.2.9", + "@rolldown/binding-openharmony-arm64": "1.2.9", + "@rolldown/binding-win32-arm64-msvc": "1.2.9", + "@rolldown/binding-win32-x64-msvc": "1.2.9" } }, "node_modules/safe-array-concat": { @@ -9774,9 +9623,9 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.102.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.102.0.tgz", - "integrity": "sha512-NSOyTnaQF7rTAEOtI2fwb386vL+akyiQLBZu8Na7hXCb+umJy0GAqlcMIaqACZ6Z1VgTBS4K9PG6B3IdjHGJsw==", + "version": "1.104.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.104.1.tgz", + "integrity": "sha512-yDA+1aIG3EHgN4V/BvuhCvu61FF6hEd4e+9DxikUm9U0CAGuvdIZ/UYy7qbOxjhbbWQraoyLlMuzdRGOSV5Bmw==", "devOptional": true, "license": "MIT", "dependencies": { @@ -9808,9 +9657,9 @@ } }, "node_modules/scheduler": { - "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==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", + "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", "license": "MIT" }, "node_modules/semver": { @@ -10226,25 +10075,25 @@ } }, "node_modules/string.prototype.matchall": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", - "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.1.0.tgz", + "integrity": "sha512-tHNHTxInrYLCga9O9YGxWA3G9/nnzQw8UGAyqGx3Ar1pSTTzIuM4woFSq4SowkXCjJIwq5sIiQvEfRI9tCH1qQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.6", + "es-abstract": "^1.24.2", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", + "es-object-atoms": "^1.1.2", + "get-intrinsic": "^1.3.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", - "regexp.prototype.flags": "^1.5.3", + "regexp.prototype.flags": "^1.5.4", "set-function-name": "^2.0.2", - "side-channel": "^1.1.0" + "side-channel": "^1.1.1" }, "engines": { "node": ">= 0.4" @@ -10355,9 +10204,9 @@ } }, "node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", "dev": true, "license": "MIT", "engines": { @@ -10458,9 +10307,9 @@ "license": "MIT" }, "node_modules/systeminformation": { - "version": "5.31.10", - "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.31.10.tgz", - "integrity": "sha512-EiBvQfFxZMkNNcfStjxgyy4dxAH+lzaqon9DXiIv6yHBrrdFGChDN1csbJJCBtaM1LWfuik2ZueeeQbRS/1wcA==", + "version": "5.33.10", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.33.10.tgz", + "integrity": "sha512-/NXbMVASt2UbSVgmWto4aBTIAeuYY7zOELpZybmNxqpsrbo5uYHpuEPf5nkyrPng0uG5YOb+Yv6s0FyKY4mDQg==", "dev": true, "license": "MIT", "os": [ @@ -10477,7 +10326,7 @@ "systeminformation": "lib/cli.js" }, "engines": { - "node": ">=8.0.0" + "node": ">=10.0.0" }, "funding": { "type": "Buy me a coffee", @@ -10509,11 +10358,14 @@ } }, "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.1.4.tgz", + "integrity": "sha512-9APumHG7r4yOk4X4WlkmE71aZcv1gvin1czO3OQ1U9iJcFA5Ja/ygyb0vPOVHTthFozUYs8CLoLUlM8grb2lTQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } }, "node_modules/tinyexec": { "version": "1.3.0", @@ -10673,6 +10525,12 @@ "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==", "license": "Apache-2.0" }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -10808,16 +10666,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.66.0.tgz", - "integrity": "sha512-QlEbBPz/RuJ1XUHj29nm3t0F/O/cSlEnntozqPOYHnnTGAXFamnMBu5i9Vn6vhUPHGAjR+Vl+5J8vPN/BMUrJw==", + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.70.0.tgz", + "integrity": "sha512-P/W5cz70/cQAuKfY3xwQMWWTV7BvJ0mAQmi+9mBcsVPaBUpd6Ohpa+fECv9rBFrQcig86jAiNBFNWUqnTjr4pw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.66.0", - "@typescript-eslint/parser": "8.66.0", - "@typescript-eslint/typescript-estree": "8.66.0", - "@typescript-eslint/utils": "8.66.0" + "@typescript-eslint/eslint-plugin": "8.70.0", + "@typescript-eslint/parser": "8.70.0", + "@typescript-eslint/typescript-estree": "8.70.0", + "@typescript-eslint/utils": "8.70.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10866,19 +10724,19 @@ } }, "node_modules/undici": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", - "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "version": "8.10.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.10.2.tgz", + "integrity": "sha512-/y4/bH9YNU5hi9NIrpOuvGXFcxrj3CMrV+/AYpowAYTpHn8gX/XPFjNy766FPoYY0miQhdW977JFWKGNhBdwyQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=20.18.1" + "node": ">=22.19.0" } }, "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", "license": "MIT" }, "node_modules/unified": { @@ -11037,9 +10895,9 @@ } }, "node_modules/use-sync-external-store": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", - "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.7.0.tgz", + "integrity": "sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==", "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -11089,15 +10947,15 @@ } }, "node_modules/vite": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz", - "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.0.tgz", + "integrity": "sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==", "license": "MIT", "dependencies": { "lightningcss": "^1.33.0", - "picomatch": "^4.0.5", - "postcss": "^8.5.26", - "rolldown": "~1.2.4", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.6", "tinyglobby": "^0.2.17" }, "bin": { @@ -11114,7 +10972,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.4.0 || ^0.5.0", + "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -11166,38 +11024,31 @@ } }, "node_modules/vitest": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz", - "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "4.1.11", - "@vitest/mocker": "4.1.11", - "@vitest/pretty-format": "4.1.11", - "@vitest/runner": "4.1.11", - "@vitest/snapshot": "4.1.11", - "@vitest/spy": "4.1.11", - "@vitest/utils": "4.1.11", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.1.tgz", + "integrity": "sha512-iA95lQbKEkvrtTkdAgnWbXfbipWiiWe/hDl2P5tMi6WFwD76G0NxXAGp/M9EOcYupeGJRr6wppMc7CoA41TQjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/mocker": "5.0.1", + "chai": "^6.2.2", + "es-module-lexer": "^2.3.2", + "expect-type": "^1.4.0", + "magic-string": "^1.2.3", + "obug": "^2.1.4", + "picomatch": "^4.0.7", + "std-env": "^4.2.0", + "tinybench": "6.1.4", + "tinyexec": "1.3.0", + "tinyglobby": "^0.2.17", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^22.12.0 || ^24.0.0 || >=26.0.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -11205,16 +11056,16 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.11", - "@vitest/browser-preview": "4.1.11", - "@vitest/browser-webdriverio": "4.1.11", - "@vitest/coverage-istanbul": "4.1.11", - "@vitest/coverage-v8": "4.1.11", - "@vitest/ui": "4.1.11", + "@types/node": "^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "5.0.1", + "@vitest/browser-preview": "5.0.1", + "@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0", + "@vitest/coverage-istanbul": "5.0.1", + "@vitest/coverage-v8": "5.0.1", + "@vitest/ui": "5.0.1", "happy-dom": "*", "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + "vite": "^6.4.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "@edge-runtime/vm": { @@ -11298,18 +11149,18 @@ } }, "node_modules/whatwg-url": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", - "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "version": "17.1.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-17.1.1.tgz", + "integrity": "sha512-ohjk1mdUebJVadRt3bAhQhx8lSnISq+GDttK79LFl8EHQkAPvzwctoasC4hs8tBt6kLAncBWWyq1N52qEfKvDw==", "dev": true, "license": "MIT", "dependencies": { - "@exodus/bytes": "^1.11.0", + "@exodus/bytes": "^1.15.1", "tr46": "^6.0.0", "webidl-conversions": "^8.0.1" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": "^22.14.0 || >=24.0.0" } }, "node_modules/which": { diff --git a/client/package.json b/client/package.json index 51742541..a49d4d99 100644 --- a/client/package.json +++ b/client/package.json @@ -2,6 +2,7 @@ "name": "task-note", "version": "1.0.0", "license": "GPL-3.0-only", + "type": "module", "description": "TaskNote App - Your daily-basis friend to help you with TODOs and Notes", "keywords": [ "react", @@ -13,24 +14,24 @@ "private": true, "dependencies": { "@popperjs/core": "^2.11.8", - "@types/node": "^26.1.2", - "@types/react-dom": "^19.2.5", - "@vitejs/plugin-react": "^6.0.5", + "@types/node": "^26.6.1", + "@types/react-dom": "^19.3.0", + "@vitejs/plugin-react": "^6.1.1", "bootstrap": "^5.3.8", - "dompurify": "^3.4.14", - "i18next": "^26.3.6", - "react": "^19.2.8", + "dompurify": "^3.4.15", + "i18next": "^26.4.2", + "react": "^19.3.0", "react-bootstrap": "^2.10.10", "react-bootstrap-icons": "^1.11.6", "react-charts": "^3.0.0-beta.57", - "react-dom": "^19.2.8", - "react-i18next": "^17.0.12", + "react-dom": "^19.3.0", + "react-i18next": "^17.0.14", "react-markdown": "^10.1.0", - "react-router": "^8.3.0", + "react-router": "^8.4.0", "react-router-bootstrap": "^0.26.3", "remark-gfm": "^4.0.1", "typescript": "^6.0.3", - "vite": "^8.2.2" + "vite": "^8.3.0" }, "scripts": { "start": "vite --host", @@ -64,31 +65,31 @@ ] }, "devDependencies": { - "@eslint/compat": "^2.1.0", - "@eslint/eslintrc": "^3.3.6", + "@eslint/compat": "^2.1.1", + "@eslint/eslintrc": "^3.3.7", "@eslint/js": "^9.39.5", "@stylistic/eslint-plugin": "^5.10.0", - "@testing-library/jest-dom": "^6.10.0", + "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.3", - "@testing-library/user-event": "^14.6.6", + "@testing-library/user-event": "^14.6.7", "@types/jest": "^30.0.0", - "@types/react": "^19.2.18", + "@types/react": "^19.3.0", "@types/react-router-bootstrap": "^0.26.8", - "@vitest/coverage-v8": "^4.1.11", - "cypress": "^15.20.1", + "@vitest/coverage-v8": "^5.0.1", + "cypress": "^15.21.1", "eslint": "^9.39.5", "eslint-config-prettier": "^10.1.8", "eslint-plugin-import-x": "^4.17.1", - "eslint-plugin-jsdoc": "^63.3.3", - "eslint-plugin-n": "^18.2.2", + "eslint-plugin-jsdoc": "^64.5.2", + "eslint-plugin-n": "^18.3.0", "eslint-plugin-promise": "^7.3.0", "eslint-plugin-react": "^7.37.5", - "globals": "^17.9.0", - "jsdom": "^29.1.1", - "prettier": "^3.9.6", - "sass": "^1.102.0", + "globals": "^17.12.0", + "jsdom": "^30.0.1", + "prettier": "^3.9.7", + "sass": "^1.104.1", "source-map-support": "^0.5.21", - "typescript-eslint": "^8.66.0", - "vitest": "^4.1.11" + "typescript-eslint": "^8.70.0", + "vitest": "^5.0.1" } } diff --git a/client/src/__test__/App.test.tsx b/client/src/__test__/App.test.tsx index 1ecf11e2..c903d34c 100644 --- a/client/src/__test__/App.test.tsx +++ b/client/src/__test__/App.test.tsx @@ -60,6 +60,7 @@ const renderApp = async (authValue: Partial = {}) => { beforeEach(() => { localStorage.clear(); document.body.removeAttribute('data-bs-theme'); + vi.mocked(api.getJSON).mockResolvedValue([]); }); test('Renders the app', async () => { diff --git a/client/vite.config.ts b/client/vite.config.ts index ce57e7c3..05ab8dce 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -1,7 +1,6 @@ import { ConfigEnv, defineConfig, UserConfig } from 'vite'; import react from '@vitejs/plugin-react'; import { fileURLToPath } from 'url'; -import path from 'path'; const proxyConfig = process.env.NGROK ? { @@ -52,7 +51,7 @@ export default defineConfig(({ mode }: ConfigEnv) => { resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)), - '~bootstrap': path.resolve(__dirname, 'node_modules/bootstrap') + '~bootstrap': fileURLToPath(new URL('./node_modules/bootstrap', import.meta.url)) } } }; diff --git a/docker-compose.dev.yml b/compose.dev.yaml similarity index 77% rename from docker-compose.dev.yml rename to compose.dev.yaml index 55d2f29d..20000f94 100644 --- a/docker-compose.dev.yml +++ b/compose.dev.yaml @@ -9,8 +9,10 @@ services: - "5000:5000" entrypoint: sh -c "npm i --no-update-notifier && npm start" environment: - VITE_BACKEND_SERVER: http://localhost:8585 + VITE_BACKEND_SERVER: "/api" VITE_BUILD: nightly + NGROK: 1 + BACKEND_HOST: tasknote-api volumes: - "./client:/app" working_dir: /app @@ -20,22 +22,19 @@ services: timeout: 15s retries: 3 start_period: 10s + depends_on: + tasknote-api: + condition: service_healthy tasknote-api: container_name: tasknote-api - depends_on: - tasknote-db: - condition: service_started user: ${UID:-1000}:${GID:-1000} environment: POSTGRES_DB: tasknote POSTGRES_HOST: tasknote-db POSTGRES_USER: tasknoteuser POSTGRES_PASSWORD: default - POSTGRES_PORT: 5432 - CORS_ALLOWED_ORIGINS: http://localhost:5000, http://tasknote-web:5000 - SERVER_SERVLET_CONTEXT_PATH: / - TARGET_ENV: development + CORS_ALLOWED_ORIGINS: http://localhost:5000, http://tasknote-web:5000, https://flattop-depth-dropper.ngrok-free.dev SECURITY_KEY: this-is-a-very-long-security-key-for-dev MAILGUN_APIKEY: invalid-api-key-only-placeholder ports: @@ -52,20 +51,9 @@ services: timeout: 15s retries: 3 start_period: 10s - - schemaspy: - container_name: schemaspy - profiles: ["schemaspy"] - image: schemaspy/schemaspy:7.0.2 - user: ${UID:-1000}:${GID:-1000} - volumes: - - "./schemaspy/output:/output" - - "./schemaspy/postgres.properties:/schemaspy.properties" depends_on: tasknote-db: condition: service_healthy - tasknote-api: - condition: service_healthy tasknote-db: container_name: tasknote-db @@ -82,3 +70,19 @@ services: timeout: 15s retries: 3 start_period: 10s + + tasknote-ngrok: + container_name: tasknote-ngrok + image: ngrok/ngrok:3-alpine + restart: unless-stopped + environment: + NGROK_AUTHTOKEN: ${NGROK_AUTHTOKEN:?NGROK_AUTHTOKEN must be set} + command: + - http + - --url=https://flattop-depth-dropper.ngrok-free.dev + - tasknote-web:5000 + ports: + - "4040:4040" + depends_on: + tasknote-web: + condition: service_healthy diff --git a/docker-compose.prod.yml b/compose.prod-like.yaml similarity index 74% rename from docker-compose.prod.yml rename to compose.prod-like.yaml index b0fe9dd5..1b178f4f 100644 --- a/docker-compose.prod.yml +++ b/compose.prod-like.yaml @@ -3,35 +3,30 @@ services: tasknote-web: container_name: tasknote-web - depends_on: - tasknote-api: - condition: service_healthy - image: rmcampos/tasknote-app:latest + image: ghcr.io/thermcampos/tasknote/client:latest build: context: ./client dockerfile: Dockerfile ports: ["5000:5000"] networks: - tasknote-network + depends_on: + tasknote-api: + condition: service_healthy tasknote-api: container_name: tasknote-api - depends_on: - tasknote-db: - condition: service_healthy environment: POSTGRES_DB: tasknote POSTGRES_HOST: tasknote-db POSTGRES_USER: tasknoteuser - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - POSTGRES_PORT: 5432 - CORS_ALLOWED_ORIGINS: http://tasknote-web:5000, http://localhost:5000 - SERVER_SERVLET_CONTEXT_PATH: / + POSTGRES_PASSWORD: default + CORS_ALLOWED_ORIGINS: http://tasknote-web:5000, http://localhost:5000, https://flattop-depth-dropper.ngrok-free.dev TARGET_ENV: production - SECURITY_KEY: ${SECURITY_KEY} - MAILGUN_APIKEY: ${MAILGUN_APIKEY} + SECURITY_KEY: this-is-a-very-long-security-key-for-dev + MAILGUN_APIKEY: invalid-api-key-only-placeholder ports: ["8585:8585"] - image: rmcampos/tasknote-api:latest + image: ghcr.io/thermcampos/tasknote/server:latest healthcheck: test: ["CMD", "/layers/local_healthcheck/healthcheck/bin/healthcheck"] interval: 10s @@ -40,14 +35,19 @@ services: start_period: 5s networks: - tasknote-network + depends_on: + tasknote-db: + condition: service_healthy tasknote-db: container_name: tasknote-db - image: postgres:16 + image: postgres:15.8-bookworm environment: POSTGRES_DB: tasknote POSTGRES_USER: tasknoteuser - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_PASSWORD: default + ports: + - "5432:5432" healthcheck: test: psql -q -U $${POSTGRES_USER} -d $${POSTGRES_DB} -c 'SELECT 1' interval: 1m30s @@ -59,4 +59,3 @@ services: networks: tasknote-network: - external: true diff --git a/docker-compose.ngrok.yml b/compose.schemaspy.yaml similarity index 53% rename from docker-compose.ngrok.yml rename to compose.schemaspy.yaml index 2f2d7ebb..8d8dfd8d 100644 --- a/docker-compose.ngrok.yml +++ b/compose.schemaspy.yaml @@ -1,45 +1,34 @@ --- services: - tasknote-web: - container_name: tasknote-web - image: node:22.14-bookworm-slim - user: ${UID:-1000}:${GID:-1000} - ports: - - "5000:5000" - entrypoint: sh -c "npm i --no-update-notifier && export NGROK=1 && npm start" + tasknote-db: + container_name: schemaspy-tasknote-db + image: postgres:15.8-bookworm environment: - VITE_BACKEND_SERVER: "/api" - VITE_BUILD: nightly - NGROK: 1 - BACKEND_HOST: tasknote-api - volumes: - - "./client:/app" - working_dir: /app + POSTGRES_DB: tasknote + POSTGRES_USER: tasknoteuser + POSTGRES_PASSWORD: default + ports: + - "5432:5432" healthcheck: - test: timeout 10s bash -c 'true > /dev/tcp/127.0.0.1/5000' - interval: 1m30s - timeout: 15s - retries: 3 + test: psql -q -U $${POSTGRES_USER} -d $${POSTGRES_DB} -c 'SELECT 1' + interval: 5s + timeout: 5s + retries: 10 start_period: 10s - networks: - - tasknote tasknote-api: - container_name: tasknote-api + container_name: schemaspy-tasknote-api depends_on: tasknote-db: - condition: service_started + condition: service_healthy user: ${UID:-1000}:${GID:-1000} environment: POSTGRES_DB: tasknote POSTGRES_HOST: tasknote-db POSTGRES_USER: tasknoteuser POSTGRES_PASSWORD: default - POSTGRES_PORT: 5432 - CORS_ALLOWED_ORIGINS: http://localhost:5000,http://tasknote-web:5000,https://flattop-depth-dropper.ngrok-free.dev - SERVER_SERVLET_CONTEXT_PATH: / - TARGET_ENV: development + CORS_ALLOWED_ORIGINS: http://localhost:5000, http://tasknote-web:5000, https://flattop-depth-dropper.ngrok-free.dev SECURITY_KEY: this-is-a-very-long-security-key-for-dev MAILGUN_APIKEY: invalid-api-key-only-placeholder ports: @@ -52,44 +41,32 @@ services: - "./server:/app" healthcheck: test: curl -f http://localhost:8585/health | grep '"status":"UP"' - interval: 1m30s - timeout: 15s - retries: 3 - start_period: 10s - networks: - - tasknote + interval: 5s + timeout: 5s + retries: 10 + start_period: 15s schemaspy: - container_name: schemaspy - profiles: ["schemaspy"] + container_name: schemaspy-schemaspy image: schemaspy/schemaspy:7.0.2 user: ${UID:-1000}:${GID:-1000} + restart: "no" volumes: - "./schemaspy/output:/output" - "./schemaspy/postgres.properties:/schemaspy.properties" depends_on: - tasknote-db: - condition: service_healthy tasknote-api: condition: service_healthy - tasknote-db: - container_name: tasknote-db - image: postgres:15.8-bookworm - environment: - POSTGRES_DB: tasknote - POSTGRES_USER: tasknoteuser - POSTGRES_PASSWORD: default + schemaspy-server: + container_name: schemaspy-http-server + image: python:3.13-alpine + working_dir: /output ports: - - "5432:5432" - healthcheck: - test: psql -q -U $${POSTGRES_USER} -d $${POSTGRES_DB} -c 'SELECT 1' - interval: 1m30s - timeout: 15s - retries: 3 - start_period: 10s - networks: - - tasknote - -networks: - tasknote: + - "3000:3000" + command: python3 -m http.server 3000 + volumes: + - "./schemaspy/output:/output" + depends_on: + schemaspy: + condition: service_completed_successfully diff --git a/schemaspy/postgres.properties b/schemaspy/postgres.properties index 8afdb46a..65be64c9 100644 --- a/schemaspy/postgres.properties +++ b/schemaspy/postgres.properties @@ -6,3 +6,4 @@ schemaspy.u=tasknoteuser schemaspy.p=default schemaspy.schemas=tasknote schemaspy.debug=true +schemaspy.noTablePaging=true diff --git a/server/pom.xml b/server/pom.xml index 266da1b6..b29efd34 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 4.0.7 + 4.1.1 @@ -49,12 +49,13 @@ ${project.build.directory}/coverage-reports ${maven.build.timestamp} yyyy-MM-dd HH:mm:ss - 3.5.6 - 3.5.6 + 3.6.0 + 3.6.0 0.8.15 3.6.0 - 4.0.7 + 4.1.1 0.12.6 + 13.5.0 @@ -101,11 +102,17 @@ spring-boot-starter-security + + org.springframework + spring-context + compile + + - org.springframework.boot - spring-boot-starter-data-jpa - + org.springframework.boot + spring-boot-starter-jdbc + org.postgresql postgresql @@ -118,10 +125,12 @@ org.flywaydb flyway-core + ${flyway.version} org.flywaydb flyway-database-postgresql + ${flyway.version} @@ -142,8 +151,9 @@ test - com.h2database - h2 + org.testcontainers + testcontainers-postgresql + ${testcontainers.version} test @@ -168,24 +178,6 @@ - - org.hibernate.orm - hibernate-maven-plugin - ${hibernate.version} - - - enhance - - enhance - - - true - true - false - - - - org.apache.maven.plugins maven-failsafe-plugin @@ -363,6 +355,11 @@ org.graalvm.buildtools native-maven-plugin + + + true + + org.springframework.boot diff --git a/server/src/main/java/br/com/tasknoteapp/server/config/CloudNativeConfig.java b/server/src/main/java/br/com/tasknoteapp/server/config/CloudNativeConfig.java index f685a999..05b45011 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/config/CloudNativeConfig.java +++ b/server/src/main/java/br/com/tasknoteapp/server/config/CloudNativeConfig.java @@ -23,7 +23,6 @@ io.jsonwebtoken.impl.DefaultJwtParserBuilder.class, io.jsonwebtoken.impl.DefaultJwtBuilder.class, io.jsonwebtoken.lang.Supplier.class, - org.flywaydb.core.internal.publishing.PublishingConfigurationExtension.class, }) @ImportRuntimeHints(value = {HttpServletRequestRuntimeHint.class}) public class CloudNativeConfig {} diff --git a/server/src/main/java/br/com/tasknoteapp/server/config/SecurityConfig.java b/server/src/main/java/br/com/tasknoteapp/server/config/SecurityConfig.java index 21a0d911..d59c70ab 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/config/SecurityConfig.java +++ b/server/src/main/java/br/com/tasknoteapp/server/config/SecurityConfig.java @@ -53,6 +53,10 @@ public SecurityFilterChain filterChain(HttpSecurity http) { .permitAll() .requestMatchers("/public/**") .permitAll() + .requestMatchers("/health") + .permitAll() + .requestMatchers("/error") + .permitAll() .requestMatchers("/rest/**") .authenticated() .anyRequest() diff --git a/server/src/main/java/br/com/tasknoteapp/server/controller/UserController.java b/server/src/main/java/br/com/tasknoteapp/server/controller/UserController.java index 7b848828..c98ea7cd 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/controller/UserController.java +++ b/server/src/main/java/br/com/tasknoteapp/server/controller/UserController.java @@ -4,7 +4,6 @@ import br.com.tasknoteapp.server.response.UserResponse; import br.com.tasknoteapp.server.service.AuthService; import jakarta.validation.Valid; -import java.util.List; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -23,16 +22,6 @@ public UserController(AuthService authService) { this.authService = authService; } - /** - * Get all users. - * - * @return List of UserEntity with all found users. - */ - @GetMapping - public List getAllUsers() { - return authService.getAllUsers(); - } - /** * Get the current logged user. * diff --git a/server/src/main/java/br/com/tasknoteapp/server/controller/UserSessionController.java b/server/src/main/java/br/com/tasknoteapp/server/controller/UserSessionController.java index e464becd..10399ecb 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/controller/UserSessionController.java +++ b/server/src/main/java/br/com/tasknoteapp/server/controller/UserSessionController.java @@ -1,6 +1,6 @@ package br.com.tasknoteapp.server.controller; -import br.com.tasknoteapp.server.entity.UserEntity; +import br.com.tasknoteapp.server.entity.User; import br.com.tasknoteapp.server.exception.UserNotFoundException; import br.com.tasknoteapp.server.request.DeleteAccountRequest; import br.com.tasknoteapp.server.response.JwtAuthenticationResponse; @@ -49,7 +49,7 @@ public JwtAuthenticationResponse refresh() { @PostMapping("/delete-account") public ResponseEntity deleteAccount( @RequestBody @Valid DeleteAccountRequest request) { - UserEntity user = authService.getCurrentUser().orElseThrow(UserNotFoundException::new); + User user = authService.getCurrentUser().orElseThrow(UserNotFoundException::new); authService.verifyCurrentPassword(user, request.password()); UserResponse deleted = userSessionService.deleteCurrentUserAccount(); return ResponseEntity.ok(deleted); diff --git a/server/src/main/java/br/com/tasknoteapp/server/entity/Note.java b/server/src/main/java/br/com/tasknoteapp/server/entity/Note.java new file mode 100644 index 00000000..db7446b5 --- /dev/null +++ b/server/src/main/java/br/com/tasknoteapp/server/entity/Note.java @@ -0,0 +1,9 @@ +package br.com.tasknoteapp.server.entity; + +import java.time.LocalDateTime; + +/** This class represents a note in the database. */ +public record Note(Long id, Long userId, String description, String title, + LocalDateTime lastUpdate, Boolean shared, String shareToken, + Boolean archived) {} + diff --git a/server/src/main/java/br/com/tasknoteapp/server/entity/NoteEntity.java b/server/src/main/java/br/com/tasknoteapp/server/entity/NoteEntity.java deleted file mode 100644 index 44a366b7..00000000 --- a/server/src/main/java/br/com/tasknoteapp/server/entity/NoteEntity.java +++ /dev/null @@ -1,163 +0,0 @@ -package br.com.tasknoteapp.server.entity; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.JoinTable; -import jakarta.persistence.ManyToMany; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; -import java.time.LocalDateTime; -import java.util.HashSet; -import java.util.Set; - -/** This class represents a note in the database. */ -@Entity -@Table(name = "notes") -public class NoteEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - private String title; - - @Column(columnDefinition = "TEXT") - private String description; - - @JoinColumn(name = "user_id", referencedColumnName = "id", nullable = false, updatable = false) - @ManyToOne(fetch = FetchType.LAZY) - private UserEntity user; - - @ManyToMany(fetch = FetchType.LAZY) - @JoinTable( - name = "note_tags", - joinColumns = @JoinColumn(name = "note_id"), - inverseJoinColumns = @JoinColumn(name = "tag_id")) - private Set tags = new HashSet<>(); - - @Column(name = "last_update") - private LocalDateTime lastUpdate; - - @Column(name = "shared", nullable = false) - private boolean shared = false; - - @Column(name = "share_token", length = 36) - private String shareToken; - - @Column(name = "archived", nullable = false) - private boolean archived = false; - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public UserEntity getUser() { - return user; - } - - public void setUser(UserEntity user) { - this.user = user; - } - - public Set getTags() { - return tags; - } - - public void setTags(Set tags) { - this.tags = tags; - } - - public LocalDateTime getLastUpdate() { - return lastUpdate; - } - - public void setLastUpdate(LocalDateTime lastUpdate) { - this.lastUpdate = lastUpdate; - } - - public boolean isShared() { - return shared; - } - - public void setShared(boolean shared) { - this.shared = shared; - } - - public String getShareToken() { - return shareToken; - } - - public void setShareToken(String shareToken) { - this.shareToken = shareToken; - } - - public boolean isArchived() { - return archived; - } - - public void setArchived(boolean archived) { - this.archived = archived; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - NoteEntity that = (NoteEntity) o; - return id != null && id.equals(that.id); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public String toString() { - return "NoteEntity{" - + "id=" - + id - + ", title='" - + title - + '\'' - + ", description='" - + description - + '\'' - + ", tags=" - + tags - + ", lastUpdate=" - + lastUpdate - + ", archived=" - + archived - + '}'; - } -} diff --git a/server/src/main/java/br/com/tasknoteapp/server/entity/NoteUrl.java b/server/src/main/java/br/com/tasknoteapp/server/entity/NoteUrl.java new file mode 100644 index 00000000..eebd6609 --- /dev/null +++ b/server/src/main/java/br/com/tasknoteapp/server/entity/NoteUrl.java @@ -0,0 +1,4 @@ +package br.com.tasknoteapp.server.entity; + +/** This class represents a note url in the database. */ +public record NoteUrl(Long id, Long noteId, String url) {} diff --git a/server/src/main/java/br/com/tasknoteapp/server/entity/NoteUrlEntity.java b/server/src/main/java/br/com/tasknoteapp/server/entity/NoteUrlEntity.java deleted file mode 100644 index c32edef3..00000000 --- a/server/src/main/java/br/com/tasknoteapp/server/entity/NoteUrlEntity.java +++ /dev/null @@ -1,72 +0,0 @@ -package br.com.tasknoteapp.server.entity; - -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.OneToOne; -import jakarta.persistence.Table; - -/** This class represents a note url in the database. */ -@Entity -@Table(name = "note_urls") -public class NoteUrlEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - private String url; - - @JoinColumn(name = "note_id", referencedColumnName = "id", nullable = false, updatable = false) - @OneToOne(fetch = FetchType.LAZY) - private NoteEntity note; - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public NoteEntity getNote() { - return note; - } - - public void setNote(NoteEntity note) { - this.note = note; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - NoteUrlEntity that = (NoteUrlEntity) o; - return id != null && id.equals(that.id); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public String toString() { - return "NoteUrlEntity{" + "id=" + id + ", url='" + url + '\'' + '}'; - } -} diff --git a/server/src/main/java/br/com/tasknoteapp/server/entity/Tag.java b/server/src/main/java/br/com/tasknoteapp/server/entity/Tag.java new file mode 100644 index 00000000..b1934733 --- /dev/null +++ b/server/src/main/java/br/com/tasknoteapp/server/entity/Tag.java @@ -0,0 +1,5 @@ +package br.com.tasknoteapp.server.entity; + +/** This class represents a tag in the database. */ +public record Tag(Long id, String name, Long userId) {} + diff --git a/server/src/main/java/br/com/tasknoteapp/server/entity/TagEntity.java b/server/src/main/java/br/com/tasknoteapp/server/entity/TagEntity.java deleted file mode 100644 index fd33b7d5..00000000 --- a/server/src/main/java/br/com/tasknoteapp/server/entity/TagEntity.java +++ /dev/null @@ -1,84 +0,0 @@ -package br.com.tasknoteapp.server.entity; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; - -/** This class represents a tag in the database. */ -@Entity -@Table( - name = "tags", - uniqueConstraints = {@UniqueConstraint(columnNames = {"name", "user_id"})}) -public class TagEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(nullable = false, length = 30) - private String name; - - @JoinColumn(name = "user_id", referencedColumnName = "id", nullable = false, updatable = false) - @ManyToOne(fetch = FetchType.LAZY) - private UserEntity user; - - public TagEntity() {} - - public TagEntity(String name, UserEntity user) { - this.name = name; - this.user = user; - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public UserEntity getUser() { - return user; - } - - public void setUser(UserEntity user) { - this.user = user; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - TagEntity that = (TagEntity) o; - return id != null && id.equals(that.id); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public String toString() { - return "TagEntity{" + "id=" + id + ", name='" + name + '\'' + '}'; - } -} diff --git a/server/src/main/java/br/com/tasknoteapp/server/entity/Task.java b/server/src/main/java/br/com/tasknoteapp/server/entity/Task.java new file mode 100644 index 00000000..0cfe1ed0 --- /dev/null +++ b/server/src/main/java/br/com/tasknoteapp/server/entity/Task.java @@ -0,0 +1,10 @@ +package br.com.tasknoteapp.server.entity; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +/** This class represents a task in the database. */ +public record Task(Long id, Long userId, String description, Boolean completed, + LocalDateTime lastUpdate, LocalDate dueDate, Boolean dueDateNotify, + Boolean dueDateNotifySent, Boolean highPriority) {} + diff --git a/server/src/main/java/br/com/tasknoteapp/server/entity/TaskEntity.java b/server/src/main/java/br/com/tasknoteapp/server/entity/TaskEntity.java deleted file mode 100644 index be916daa..00000000 --- a/server/src/main/java/br/com/tasknoteapp/server/entity/TaskEntity.java +++ /dev/null @@ -1,154 +0,0 @@ -package br.com.tasknoteapp.server.entity; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.JoinTable; -import jakarta.persistence.ManyToMany; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.HashSet; -import java.util.Set; - -/** This class represents a task in the database. */ -@Entity -@Table(name = "tasks") -public class TaskEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(length = 2000) - private String description; - - private Boolean completed; - - @JoinColumn(name = "user_id", referencedColumnName = "id", nullable = false, updatable = false) - @ManyToOne(fetch = FetchType.LAZY) - private UserEntity user; - - @Column(name = "last_update") - private LocalDateTime lastUpdate; - - @Column(name = "due_date") - private LocalDate dueDate; - - @Column(name = "high_priority") - private Boolean highPriority; - - @ManyToMany(fetch = FetchType.LAZY) - @JoinTable( - name = "task_tags", - joinColumns = @JoinColumn(name = "task_id"), - inverseJoinColumns = @JoinColumn(name = "tag_id")) - private Set tags = new HashSet<>(); - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public Boolean getCompleted() { - return completed; - } - - public void setCompleted(Boolean completed) { - this.completed = completed; - } - - public UserEntity getUser() { - return user; - } - - public void setUser(UserEntity user) { - this.user = user; - } - - public LocalDateTime getLastUpdate() { - return lastUpdate; - } - - public void setLastUpdate(LocalDateTime lastUpdate) { - this.lastUpdate = lastUpdate; - } - - public LocalDate getDueDate() { - return dueDate; - } - - public void setDueDate(LocalDate dueDate) { - this.dueDate = dueDate; - } - - public Boolean getHighPriority() { - return highPriority; - } - - public void setHighPriority(Boolean highPriority) { - this.highPriority = highPriority; - } - - public Set getTags() { - return tags; - } - - public void setTags(Set tags) { - this.tags = tags; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - TaskEntity that = (TaskEntity) o; - return id != null && id.equals(that.id); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public String toString() { - return "TaskEntity{" - + "id=" - + id - + ", description='" - + description - + '\'' - + ", completed=" - + completed - + ", lastUpdate=" - + lastUpdate - + ", dueDate=" - + dueDate - + ", highPriority=" - + highPriority - + ", tags=" - + tags - + '}'; - } -} diff --git a/server/src/main/java/br/com/tasknoteapp/server/entity/TaskNoteTag.java b/server/src/main/java/br/com/tasknoteapp/server/entity/TaskNoteTag.java new file mode 100644 index 00000000..3d66842c --- /dev/null +++ b/server/src/main/java/br/com/tasknoteapp/server/entity/TaskNoteTag.java @@ -0,0 +1,11 @@ +package br.com.tasknoteapp.server.entity; + +/** + * This class represents a relationship between a Tag and a Note or Task. + * + * @param tagId The Tag ID. + * @param name The Tag name. + * @param userId The User ID for the tag. + * @param taskNoteId The foreign key to either Note or Task. + */ +public record TaskNoteTag(Long tagId, String name, Long userId, Long taskNoteId) {} diff --git a/server/src/main/java/br/com/tasknoteapp/server/entity/TaskUrl.java b/server/src/main/java/br/com/tasknoteapp/server/entity/TaskUrl.java new file mode 100644 index 00000000..002b0c49 --- /dev/null +++ b/server/src/main/java/br/com/tasknoteapp/server/entity/TaskUrl.java @@ -0,0 +1,4 @@ +package br.com.tasknoteapp.server.entity; + +/** This class represents a task url in the database. */ +public record TaskUrl(TaskUrlPk id) {} diff --git a/server/src/main/java/br/com/tasknoteapp/server/entity/TaskUrlEntity.java b/server/src/main/java/br/com/tasknoteapp/server/entity/TaskUrlEntity.java deleted file mode 100644 index 93dba235..00000000 --- a/server/src/main/java/br/com/tasknoteapp/server/entity/TaskUrlEntity.java +++ /dev/null @@ -1,43 +0,0 @@ -package br.com.tasknoteapp.server.entity; - -import jakarta.persistence.EmbeddedId; -import jakarta.persistence.Entity; -import jakarta.persistence.Table; - -/** This class represents a task url in the database. */ -@Entity -@Table(name = "task_url") -public class TaskUrlEntity { - - @EmbeddedId private TaskUrlEntityPk id; - - public TaskUrlEntityPk getId() { - return id; - } - - public void setId(TaskUrlEntityPk id) { - this.id = id; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - TaskUrlEntity that = (TaskUrlEntity) o; - return id != null && id.equals(that.id); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public String toString() { - return "TaskUrlEntity{" + "id=" + id + '}'; - } -} diff --git a/server/src/main/java/br/com/tasknoteapp/server/entity/TaskUrlEntityPk.java b/server/src/main/java/br/com/tasknoteapp/server/entity/TaskUrlEntityPk.java deleted file mode 100644 index a3547af6..00000000 --- a/server/src/main/java/br/com/tasknoteapp/server/entity/TaskUrlEntityPk.java +++ /dev/null @@ -1,52 +0,0 @@ -package br.com.tasknoteapp.server.entity; - -import jakarta.persistence.Embeddable; - -/** This class represents a UrlTaskEntity primary key. */ -@Embeddable -public class TaskUrlEntityPk { - - private Long taskId; - - private String url; - - public TaskUrlEntityPk() {} - - public TaskUrlEntityPk(Long taskId, String url) { - this.taskId = taskId; - this.url = url; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - // Equals using both fields - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - TaskUrlEntityPk that = (TaskUrlEntityPk) o; - return url.equals(that.url) && taskId.equals(that.taskId); - } - - @Override - public int hashCode() { - int result = taskId != null ? taskId.hashCode() : 0; - result = 31 * result + (url != null ? url.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return "TaskUrlEntityPk{" + "taskId=" + taskId + ", url='" + url + '\'' + '}'; - } -} diff --git a/server/src/main/java/br/com/tasknoteapp/server/entity/TaskUrlPk.java b/server/src/main/java/br/com/tasknoteapp/server/entity/TaskUrlPk.java new file mode 100644 index 00000000..479b7165 --- /dev/null +++ b/server/src/main/java/br/com/tasknoteapp/server/entity/TaskUrlPk.java @@ -0,0 +1,4 @@ +package br.com.tasknoteapp.server.entity; + +/** This class represents a UrlTaskEntity primary key. */ +public record TaskUrlPk(Long taskId, String url) {} diff --git a/server/src/main/java/br/com/tasknoteapp/server/entity/UserEntity.java b/server/src/main/java/br/com/tasknoteapp/server/entity/User.java similarity index 77% rename from server/src/main/java/br/com/tasknoteapp/server/entity/UserEntity.java rename to server/src/main/java/br/com/tasknoteapp/server/entity/User.java index 902c9de8..5eace2ee 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/entity/UserEntity.java +++ b/server/src/main/java/br/com/tasknoteapp/server/entity/User.java @@ -1,11 +1,5 @@ package br.com.tasknoteapp.server.entity; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.Table; import java.time.LocalDateTime; import java.util.Collection; import java.util.List; @@ -14,54 +8,22 @@ import org.springframework.security.core.userdetails.UserDetails; /** This class represents a User in the database. */ -@Entity -@Table(name = "users") -public class UserEntity implements UserDetails { +public class User implements UserDetails { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - - @Column(unique = true, nullable = false) private String email; - - @Column(nullable = false) private String password; - - @Column(nullable = false) private Boolean admin; - - @Column(name = "created_at", nullable = false) private LocalDateTime createdAt; - - @Column(name = "inactivated_at") private LocalDateTime inactivatedAt; - - @Column(name = "name", length = 20) private String name; - - @Column(name = "email_confirmed_at") private LocalDateTime emailConfirmedAt; - - @Column(name = "email_uuid", columnDefinition = "uuid", unique = true) private UUID emailUuid; - - @Column(name = "reset_password_expiration") private LocalDateTime resetPasswordExpiration; - - @Column(name = "reset_token", length = 35) private String resetToken; - - @Column(name = "lang", length = 6) private String lang; - - @Column(name = "theme", length = 10) private String theme; - - @Column(name = "last_password_change", nullable = false) private LocalDateTime lastPasswordChange; - - @Column(name = "last_login") private LocalDateTime lastLogin; @Override diff --git a/server/src/main/java/br/com/tasknoteapp/server/entity/UserPwdLimit.java b/server/src/main/java/br/com/tasknoteapp/server/entity/UserPwdLimit.java new file mode 100644 index 00000000..d5e3659e --- /dev/null +++ b/server/src/main/java/br/com/tasknoteapp/server/entity/UserPwdLimit.java @@ -0,0 +1,7 @@ +package br.com.tasknoteapp.server.entity; + +import java.time.LocalDateTime; + +/** This class represents a User Password Limit in the database. */ +public record UserPwdLimit(Long id, LocalDateTime whenHappened, Long userId) {} + diff --git a/server/src/main/java/br/com/tasknoteapp/server/entity/UserPwdLimitEntity.java b/server/src/main/java/br/com/tasknoteapp/server/entity/UserPwdLimitEntity.java deleted file mode 100644 index 7244b496..00000000 --- a/server/src/main/java/br/com/tasknoteapp/server/entity/UserPwdLimitEntity.java +++ /dev/null @@ -1,82 +0,0 @@ -package br.com.tasknoteapp.server.entity; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; -import java.time.LocalDateTime; - -/** This class represents a User Password Limit in the database. */ -@Entity -@Table(name = "user_pwd_limits") -public class UserPwdLimitEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(name = "when_happened", nullable = false) - private LocalDateTime whenHappened; - - @JoinColumn(name = "user_id", referencedColumnName = "id", nullable = false, updatable = false) - @ManyToOne(fetch = FetchType.LAZY) - private UserEntity user; - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public LocalDateTime getWhenHappened() { - return whenHappened; - } - - public void setWhenHappened(LocalDateTime whenHappened) { - this.whenHappened = whenHappened; - } - - public UserEntity getUser() { - return user; - } - - public void setUser(UserEntity user) { - this.user = user; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - UserPwdLimitEntity that = (UserPwdLimitEntity) o; - return id != null && id.equals(that.id); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public String toString() { - return "UserPwdLimitEntity{" - + "id=" - + id - + ", whenHappened=" - + whenHappened - + ", user=" - + (user != null ? user.getId() : null) - + '}'; - } -} diff --git a/server/src/main/java/br/com/tasknoteapp/server/exception/NoteNotArchivedException.java b/server/src/main/java/br/com/tasknoteapp/server/exception/NoteNotArchivedException.java new file mode 100644 index 00000000..dcdf19c3 --- /dev/null +++ b/server/src/main/java/br/com/tasknoteapp/server/exception/NoteNotArchivedException.java @@ -0,0 +1,9 @@ +package br.com.tasknoteapp.server.exception; + +/** Represents an exception when trying to delete a not archived note. */ +public class NoteNotArchivedException extends BaseBadRequestException { + + public NoteNotArchivedException() { + super("note", "Note must be archived"); + } +} diff --git a/server/src/main/java/br/com/tasknoteapp/server/hint/JjwtRuntimeHints.java b/server/src/main/java/br/com/tasknoteapp/server/hint/JjwtRuntimeHints.java index 2949248e..265dee1f 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/hint/JjwtRuntimeHints.java +++ b/server/src/main/java/br/com/tasknoteapp/server/hint/JjwtRuntimeHints.java @@ -19,7 +19,6 @@ public class JjwtRuntimeHints { static class JjwtHintsRegistrar implements RuntimeHintsRegistrar { @Override public void registerHints(RuntimeHints hints, ClassLoader classLoader) { - // Register JJWT exceptions for reflection hints .reflection() .registerType(SignatureException.class) diff --git a/server/src/main/java/br/com/tasknoteapp/server/repository/NoteRepository.java b/server/src/main/java/br/com/tasknoteapp/server/repository/NoteRepository.java index 4231590c..4a05019c 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/repository/NoteRepository.java +++ b/server/src/main/java/br/com/tasknoteapp/server/repository/NoteRepository.java @@ -1,32 +1,233 @@ package br.com.tasknoteapp.server.repository; -import br.com.tasknoteapp.server.entity.NoteEntity; +import br.com.tasknoteapp.server.entity.Note; import java.util.List; import java.util.Optional; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Repository; /** This interface represents a note repository, for database access. */ -public interface NoteRepository extends JpaRepository { - - List findAllByUser_id(Long userId); - - Optional findByShareToken(String shareToken); - - Optional findByIdAndUser_id(Long id, Long userId); - - @Query( - """ - select distinct n - from NoteEntity n - left join n.tags tg - where ( - upper(n.title) like upper(concat('%', :searchTerm, '%')) or - upper(n.description) like upper(concat('%', :searchTerm, '%')) or - upper(tg.name) like upper(concat('%', :searchTerm, '%')) - ) and n.user.id = :userId - """) - List findAllBySearchTerm( - @Param("searchTerm") String searchTerm, @Param("userId") Long userId); +@Repository +public class NoteRepository { + + private final NamedParameterJdbcTemplate jdbcTemplate; + + public NoteRepository(NamedParameterJdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + /** + * Saves a note to the database and returns the saved note with its generated ID. + * + * @param note the note to be saved. + * @return the saved note with its generated ID. + */ + public Note save(Note note) { + return note.id() == null + ? create(note) + : update(note); + } + + private Note create(Note note) { + String sql = + """ + INSERT INTO tasknote.notes ( + user_id, + description, + title, + last_update, + shared, + share_token, + archived + ) VALUES ( + :userId, + :description, + :title, + :lastUpdate, + :shared, + :shareToken, + :archived + ) + RETURNING id, user_id, description, title, last_update, shared, share_token, archived + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("userId", note.userId()) + .addValue("description", note.description()) + .addValue("title", note.title()) + .addValue("lastUpdate", note.lastUpdate()) + .addValue("shared", note.shared()) + .addValue("shareToken", note.shareToken()) + .addValue("archived", note.archived()); + + return jdbcTemplate.queryForObject(sql, params, new NoteRowMapper()); + } + + private Note update(Note note) { + String sql = + """ + UPDATE tasknote.notes + SET description = :description, + title = :title, + last_update = :lastUpdate, + shared = :shared, + share_token = :shareToken, + archived = :archived + WHERE id = :id + AND user_id = :userId + RETURNING id, user_id, description, title, last_update, shared, share_token, archived + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("description", note.description()) + .addValue("title", note.title()) + .addValue("lastUpdate", note.lastUpdate()) + .addValue("shared", note.shared()) + .addValue("shareToken", note.shareToken()) + .addValue("archived", note.archived()) + .addValue("id", note.id()) + .addValue("userId", note.userId()); + + return jdbcTemplate.queryForObject(sql, params, new NoteRowMapper()); + } + + /** + * Deletes a note from the database. + * + * @param note the note to be deleted. + * @return the number of rows affected by the delete operation. + */ + public int delete(Note note) { + String sql = "DELETE FROM tasknote.notes WHERE id = :id"; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("id", note.id()); + + return jdbcTemplate.update(sql, params); + } + + /** + * Finds a note by its ID. + * + * @param id the ID of the note to find. + * @return the note with the specified ID, or empty if not found. + */ + public Optional findById(Long id) { + String sql = + """ + SELECT id, user_id, description, title, last_update, shared, share_token, archived + FROM tasknote.notes + WHERE id = :id + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("id", id); + + return jdbcTemplate.query(sql, params, new NoteRowMapper()).stream().findFirst(); + } + + /** + * Find all notes by user id. + * + * @param userId the User ID to find the notes. + * @return List of Notes found. + */ + public List findAllByUserId(Long userId) { + String sql = + """ + SELECT id, user_id, description, title, last_update, shared, share_token, archived + FROM tasknote.notes + WHERE user_id = :userId + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("userId", userId); + + return jdbcTemplate.query(sql, params, new NoteRowMapper()); + } + + /** + * Find a note by the share token. + * + * @param shareToken the token created when sharing the note. + * @return Optional of Note, could be empty. + */ + public Optional findByShareToken(String shareToken) { + String sql = + """ + SELECT id, user_id, description, title, last_update, shared, share_token, archived + FROM tasknote.notes + WHERE share_token = :shareToken + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("shareToken", shareToken); + + return jdbcTemplate.query(sql, params, new NoteRowMapper()).stream().findFirst(); + } + + /** + * Find a note by User ID and Note ID. + * + * @param id The Note ID + * @param userId The User ID + * @return Optional of Note, can be empty. + */ + public Optional findByIdAndUserId(Long id, Long userId) { + String sql = + """ + SELECT id, user_id, description, title, last_update, shared, share_token, archived + FROM tasknote.notes + WHERE id = :id AND user_id = :userId + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("id", id) + .addValue("userId", userId); + + return jdbcTemplate.query(sql, params, new NoteRowMapper()).stream().findFirst(); + } + + /** + * Find all notes by search term. + * + * @param userId The User ID. + * @param searchTerm The term to search the notes + */ + public List findAllBySearchTerm(Long userId, String searchTerm) { + String sql = + """ + SELECT id, user_id, description, title, last_update, shared, share_token, archived + FROM tasknote.notes + WHERE user_id = :userId + AND ( + upper(title) like upper(concat('%', :searchTerm, '%')) + OR upper(description) like upper(concat('%', :searchTerm, '%')) + ) + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("userId", userId) + .addValue("searchTerm", searchTerm); + + return jdbcTemplate.query(sql, params, new NoteRowMapper()); + } + + class NoteRowMapper implements org.springframework.jdbc.core.RowMapper { + @Override + public Note mapRow(java.sql.ResultSet rs, int rowNum) throws java.sql.SQLException { + return new Note( + rs.getLong("id"), + rs.getLong("user_id"), + rs.getString("description"), + rs.getString("title"), + rs.getObject("last_update", java.time.LocalDateTime.class), + rs.getBoolean("shared"), + rs.getString("share_token"), + rs.getBoolean("archived") + ); + } + } } + diff --git a/server/src/main/java/br/com/tasknoteapp/server/repository/NoteUrlRepository.java b/server/src/main/java/br/com/tasknoteapp/server/repository/NoteUrlRepository.java index 98a5e9b0..bdd860cd 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/repository/NoteUrlRepository.java +++ b/server/src/main/java/br/com/tasknoteapp/server/repository/NoteUrlRepository.java @@ -1,16 +1,136 @@ package br.com.tasknoteapp.server.repository; -import br.com.tasknoteapp.server.entity.NoteUrlEntity; +import br.com.tasknoteapp.server.entity.NoteUrl; import java.util.List; import java.util.Optional; -import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Repository; /** This interface represents a note url repository, for database access. */ -public interface NoteUrlRepository extends JpaRepository { +@Repository +public class NoteUrlRepository { - Optional findByNote_id(Long noteId); + private final NamedParameterJdbcTemplate jdbcTemplate; - List findAllByNote_idIn(List noteIds); + public NoteUrlRepository(NamedParameterJdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } - void deleteByNote_id(Long noteId); + /** + * Save a Note URL (Insert or Update based on id in the object instance). + * + * @param noteUrl A Note URL instance with all props. + * @return Created or Updated note url instance. + */ + public NoteUrl save(NoteUrl noteUrl) { + return noteUrl.id() == null + ? create(noteUrl) + : update(noteUrl); + } + + private NoteUrl create(NoteUrl noteUrl) { + String sql = + """ + INSERT INTO tasknote.note_urls (note_id, url) + VALUES (:noteId, :url) + RETURNING id, note_id, url + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("noteId", noteUrl.noteId()) + .addValue("url", noteUrl.url()); + + return jdbcTemplate.queryForObject(sql, params, new NoteUrlRowMapper()); + } + + private NoteUrl update(NoteUrl noteUrl) { + String sql = + """ + UPDATE tasknote.note_urls + SET url = :url + WHERE id = :id + AND note_id = :noteId + RETURNING id, note_id, url + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("id", noteUrl.id()) + .addValue("noteId", noteUrl.noteId()) + .addValue("url", noteUrl.url()); + + return jdbcTemplate.queryForObject(sql, params, new NoteUrlRowMapper()); + } + + /** + * Find a Note URL by Note ID. + * + * @param noteId The Note ID to find for. + * @return Optional of NoteUrl. Can be empty. + */ + public Optional findByNoteId(Long noteId) { + String sql = + """ + SELECT id, note_id, url + FROM tasknote.note_urls + WHERE note_id = :noteId + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("noteId", noteId); + + List list = jdbcTemplate.query(sql, params, new NoteUrlRowMapper()); + return list.isEmpty() ? Optional.empty() : Optional.of(list.getFirst()); + } + + /** + * Find all Note URLs given a list of Notes ID. + * + * @param noteIds List of Note IDs to search with. + * @return List of NoteUrl with results. + */ + public List findAllByNoteIdList(List noteIds) { + String sql = + """ + SELECT id, note_id, url + FROM tasknote.note_urls + WHERE note_id IN (:noteIds) + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("noteIds", noteIds); + + return jdbcTemplate.query(sql, params, new NoteUrlRowMapper()); + } + + /** + * Deletes all Note URLs given a Note ID. + * + * @param noteId The Note ID to delete for. + * @return Number of deleted records. + */ + public int deleteByNoteId(Long noteId) { + String sql = + """ + DELETE FROM tasknote.note_urls + WHERE note_id = :noteId + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("noteId", noteId); + + return jdbcTemplate.update(sql, params); + } + + class NoteUrlRowMapper implements org.springframework.jdbc.core.RowMapper { + @Override + public NoteUrl mapRow(java.sql.ResultSet rs, int rowNum) throws java.sql.SQLException { + return new NoteUrl( + rs.getLong("id"), + rs.getLong("note_id"), + rs.getString("url") + ); + } + } } + diff --git a/server/src/main/java/br/com/tasknoteapp/server/repository/TagRepository.java b/server/src/main/java/br/com/tasknoteapp/server/repository/TagRepository.java index a999d41d..d312193b 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/repository/TagRepository.java +++ b/server/src/main/java/br/com/tasknoteapp/server/repository/TagRepository.java @@ -1,31 +1,364 @@ package br.com.tasknoteapp.server.repository; -import br.com.tasknoteapp.server.entity.TagEntity; +import br.com.tasknoteapp.server.entity.Tag; +import br.com.tasknoteapp.server.entity.TaskNoteTag; +import java.sql.ResultSet; import java.util.List; import java.util.Optional; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Repository; /** This interface represents a tag repository, for database access. */ -public interface TagRepository extends JpaRepository { +@Repository +public class TagRepository { - Optional findByNameAndUser_id(String name, Long userId); + private final NamedParameterJdbcTemplate jdbcTemplate; - List findAllByUser_idOrderByNameAsc(Long userId); + public TagRepository(NamedParameterJdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } - @Modifying - @Query( - """ - delete from TagEntity t - where t.user.id = :userId - and not exists (select 1 from TaskEntity tk where t member of tk.tags) - and not exists (select 1 from NoteEntity n where t member of n.tags) - """) - void deleteOrphanedTags(@Param("userId") Long userId); + /** + * Saves a Tag (create or update) given an existing Tag ID in the instance. + * + * @param tag The Tag instance to be created or updated. + * @return Tag created or updated. + */ + public Tag save(Tag tag, String source, Long id) { + return tag.id() == null + ? create(tag, source, id) + : update(tag); + } - @Modifying(flushAutomatically = true, clearAutomatically = true) - @Query("delete from TagEntity t where t.user.id = :userId") - void deleteAllForUser(@Param("userId") Long userId); + private Tag create(Tag tag, String source, Long id) { + String sql = + """ + INSERT INTO tasknote.tags (name, user_id) + VALUES (:name, :userId) + RETURNING id, name, user_id + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("name", tag.name()) + .addValue("userId", tag.userId()); + + final Tag tagCreated = jdbcTemplate.queryForObject(sql, params, new TagRowMapper()); + + StringBuilder sb = new StringBuilder(); + sb.append("INSERT INTO tasknote."); + if ("tasks".equals(source)) { + sb.append("task_tags (task_id"); + } else if ("notes".equals(source)) { + sb.append("note_tags (note_id"); + } + sb.append(", tag_id) VALUES (:outerId, :tagId)"); + + MapSqlParameterSource relationParams = new MapSqlParameterSource() + .addValue("outerId", id) + .addValue("tagId", tagCreated.id()); + + jdbcTemplate.update(sb.toString(), relationParams); + + return tagCreated; + } + + private Tag update(Tag tag) { + String sql = + """ + UPDATE tasknote.tags + SET name = :name + WHERE id = :id + and user_id = :userId + RETURNING id, name, user_id + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("name", tag.name()) + .addValue("id", tag.id()) + .addValue("userId", tag.userId()); + + return jdbcTemplate.queryForObject(sql, params, new TagRowMapper()); + } + + /** + * Update the relationship for a tag and a task. + * + * @param tag The Tag instance to be updated. + * @param taskId The Task ID to be updated. + */ + public void updateTagForTask(Tag tag, Long taskId) { + String sql = + """ + INSERT INTO tasknote.task_tags (task_id, tag_id) + VALUES (:taskId, :tagId) + ON CONFLICT DO NOTHING; + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("taskId", taskId) + .addValue("tagId", tag.id()); + + jdbcTemplate.update(sql, params); + } + + /** + * Update the relationship for a tag and a note. + * + * @param tag The Tag instance to be updated. + * @param noteId The Note ID to be updated. + */ + public void updateTagForNote(Tag tag, Long noteId) { + String sql = + """ + INSERT INTO tasknote.note_tags (note_id, tag_id) + VALUES (:noteId, :tagId) + ON CONFLICT DO NOTHING; + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("noteId", noteId) + .addValue("tagId", tag.id()); + + jdbcTemplate.update(sql, params); + } + + /** + * Delete the tag x task relashionship for a tag ID lists. + * + * @param tagIds List of Tag IDs. + * @param taskId Task ID. + * @return Number of affected rows. + */ + public int deleteTagFromTask(List tagIds, Long taskId) { + String sql = + """ + DELETE FROM tasknote.task_tags + WHERE task_id = :taskId + AND tag_id IN (:tagIds) + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("taskId", taskId) + .addValue("tagIds", tagIds); + + return jdbcTemplate.update(sql, params); + } + + /** + * Delete the tag x note relashionship for a tag ID lists. + * + * @param tagIds List of Tag IDs. + * @param noteId Note ID. + * @return Number of affected rows. + */ + public int deleteTagFromNote(List tagIds, Long noteId) { + String sql = + """ + DELETE FROM tasknote.note_tags + WHERE note_id = :noteId + AND tag_id IN (:tagIds) + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("noteId", noteId) + .addValue("tagIds", tagIds); + + return jdbcTemplate.update(sql, params); + } + + /** + * Find all Tags given a User ID and a Task ID. + * + * @param userId The User ID to search by. + * @param taskId The Note ID to search by. + * @return List of Tags found. + */ + public List findAllByUserIdAndTaskId(Long userId, Long taskId) { + String sql = + """ + SELECT ta.id, ta.name, ta.user_id + FROM tasknote.tags ta + JOIN tasknote.task_tags nt ON nt.tag_id = ta.id + JOIN tasknote.task t ON t.id = nt.note_id + WHERE ta.user_id = :userId + AND t.id = :noteId + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("userId", userId) + .addValue("taskId", taskId); + + return jdbcTemplate.query(sql, params, new TagRowMapper()); + } + + /** + * Query all notes and tasks tags relationship looking for tags. + * + * @param userId The User ID to fetch by. + * @return true if user has any tags, false otherwise. + */ + public boolean userHasAnyTags(Long userId) { + String sql = + """ + SELECT DISTINCT ta.id, ta.name, ta.user_id, tt.tag_id AS taskNoteId + FROM tasknote.tags ta + JOIN tasknote.task_tags tt ON tt.tag_id = ta.id + JOIN tasknote.note_tags nt ON nt.tag_id = ta.id + WHERE ta.user_id = :userId + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("userId", userId); + + List allTags = jdbcTemplate.query(sql, params, (ResultSet rs, int rowNum) -> { + return new TaskNoteTag( + rs.getLong("id"), + rs.getString("name"), + rs.getLong("user_id"), + rs.getLong("taskNoteId") + ); + }); + + return !allTags.isEmpty(); + } + + /** + * Find all Tags given a User ID and a list of Task IDs. + * + * @param userId The User ID to find by. + * @param taskIdList The List of Task ID. + * @return List of found tags. + */ + public List findAllByUserIdAndTaskIdInList(Long userId, List taskIdList) { + String sql = + """ + SELECT DISTINCT ta.id, ta.name, ta.user_id, tt.task_id AS taskNoteId + FROM tasknote.tags ta + JOIN tasknote.task_tags tt ON tt.tag_id = ta.id + WHERE ta.user_id = :userId + AND tt.task_id IN (:taskIdList) + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("userId", userId) + .addValue("taskIdList", taskIdList); + + return jdbcTemplate.query(sql, params, (ResultSet rs, int rowNum) -> { + return new TaskNoteTag( + rs.getLong("id"), + rs.getString("name"), + rs.getLong("user_id"), + rs.getLong("taskNoteId") + ); + }); + } + + /** + * Find all Tags given a User ID and a list of Task IDs. + * + * @param userId The User ID to find by. + * @param noteIdList The List of Task ID. + * @return List of found tags. + */ + public List findAllByUserIdAndNoteIdInList(Long userId, List noteIdList) { + String sql = + """ + SELECT DISTINCT ta.id, ta.name, ta.user_id, nt.note_id AS taskNoteId + FROM tasknote.tags ta + JOIN tasknote.note_tags nt ON nt.tag_id = ta.id + WHERE ta.user_id = :userId + AND nt.note_id IN (:noteIdList) + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("userId", userId) + .addValue("noteIdList", noteIdList); + + return jdbcTemplate.query(sql, params, (ResultSet rs, int rowNum) -> { + return new TaskNoteTag( + rs.getLong("id"), + rs.getString("name"), + rs.getLong("user_id"), + rs.getLong("taskNoteId") + ); + }); + } + + /** + * Find a Tag given a User ID and the Tag name. + * + * @param userId The User ID to search by. + * @param name The tag name to search by. + * @return Optional of Tag. + */ + public Optional findByUserIdAndName(Long userId, String name) { + String sql = + """ + SELECT id, name, user_id + FROM tasknote.tags + WHERE user_id = :userId + AND name = :name + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("userId", userId) + .addValue("name", name); + + List tags = jdbcTemplate.query(sql, params, new TagRowMapper()); + + return tags.stream().findFirst(); + } + + /** + * Deletes all orphaned tags given a User ID (deleting from + * both Notes and Tasks). + * + * @param userId The User ID to search by. + * @return Number of deleted rows. + */ + public int deleteOrphanedTags(Long userId) { + String sql = + """ + DELETE FROM tasknote.tags t + WHERE t.user_id = :userId + AND NOT EXISTS (SELECT 1 FROM tasknote.task_tags tk WHERE tk.tag_id = t.id) + AND NOT EXISTS (SELECT 1 FROM tasknote.note_tags nt WHERE nt.tag_id = t.id) + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("userId", userId); + + return jdbcTemplate.update(sql, params); + } + + /** + * Deletes all Tags given a User ID. + * + * @param userId The User ID to search by. + * @return Number of deleted rows. + */ + public int deleteAllForUser(Long userId) { + String sql = + """ + DELETE FROM tasknote.tags t + WHERE t.user_id = :userId + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("userId", userId); + + return jdbcTemplate.update(sql, params); + } + + class TagRowMapper implements org.springframework.jdbc.core.RowMapper { + @Override + public Tag mapRow(ResultSet rs, int rowNum) throws java.sql.SQLException { + return new Tag( + rs.getLong("id"), + rs.getString("name"), + rs.getLong("user_id") + ); + } + } } diff --git a/server/src/main/java/br/com/tasknoteapp/server/repository/TaskRepository.java b/server/src/main/java/br/com/tasknoteapp/server/repository/TaskRepository.java index b3fffea9..2260d979 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/repository/TaskRepository.java +++ b/server/src/main/java/br/com/tasknoteapp/server/repository/TaskRepository.java @@ -1,31 +1,241 @@ package br.com.tasknoteapp.server.repository; -import br.com.tasknoteapp.server.entity.TaskEntity; +import br.com.tasknoteapp.server.entity.Task; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Repository; /** This interface represents a task repository, for database access. */ -public interface TaskRepository extends JpaRepository { - - List findAllByUser_id(Long userId); - - Optional findByIdAndUser_id(Long id, Long userId); - - @Query( - """ - select distinct t - from TaskEntity t - left join TaskUrlEntity tu on tu.id.taskId = t.id - left join t.tags tg - where ( - upper(t.description) like upper(concat('%', :searchTerm, '%')) or - upper(tg.name) like upper(concat('%', :searchTerm, '%')) or - upper(tu.id.url) like upper(concat('%', :searchTerm, '%')) - ) and t.user.id = :userId and t.completed = false - """) - List findAllBySearchTerm( - @Param("searchTerm") String searchTerm, @Param("userId") Long userId); +@Repository +public class TaskRepository { + + private final NamedParameterJdbcTemplate jdbcTemplate; + + public TaskRepository(NamedParameterJdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + /** + * Save a task (create or update). + * + * @param task The task instance to be created or updated. + * @return The new task instance. + */ + public Task save(Task task) { + return task.id() == null + ? create(task) + : update(task); + } + + private Task create(Task task) { + String sql = + """ + INSERT INTO tasknote.tasks ( + user_id, + description, + completed, + last_update, + due_date, + due_date_notify, + due_date_notify_sent, + high_priority + ) VALUES ( + :userId, + :description, + :completed, + :lastUpdate, + :dueDate, + :dueDateNotify, + :dueDateNotifySent, + :highPriority + ) + RETURNING id, + user_id, + description, + completed, + last_update, + due_date, + due_date_notify, + due_date_notify_sent, + high_priority + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("userId", task.userId()) + .addValue("description", task.description()) + .addValue("completed", task.completed()) + .addValue("lastUpdate", task.lastUpdate()) + .addValue("dueDate", task.dueDate()) + .addValue("dueDateNotify", task.dueDateNotify()) + .addValue("dueDateNotifySent", task.dueDateNotifySent()) + .addValue("highPriority", task.highPriority()); + + return jdbcTemplate.queryForObject(sql, params, new TaskRowMapper()); + } + + private Task update(Task task) { + String sql = + """ + UPDATE tasknote.tasks + SET + user_id = :userId, + description = :description, + completed = :completed, + last_update = :lastUpdate, + due_date = :dueDate, + due_date_notify = :dueDateNotify, + due_date_notify_sent = :dueDateNotifySent, + high_priority = :highPriority + WHERE id = :id + RETURNING id, + user_id, + description, + completed, + last_update, + due_date, + due_date_notify, + due_date_notify_sent, + high_priority + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("userId", task.userId()) + .addValue("description", task.description()) + .addValue("completed", task.completed()) + .addValue("lastUpdate", task.lastUpdate()) + .addValue("dueDate", task.dueDate()) + .addValue("dueDateNotify", task.dueDateNotify()) + .addValue("dueDateNotifySent", task.dueDateNotifySent()) + .addValue("highPriority", task.highPriority()) + .addValue("id", task.id()); + + return jdbcTemplate.queryForObject(sql, params, new TaskRowMapper()); + } + + /** + * Delete a task given a Task instance (needs the ID). + * + * @param task The Task instance to be deleted containing the ID. + * @return The number of affected rows. + */ + public int delete(Task task) { + String sql = "DELETE FROM tasknote.tasks WHERE id = :id"; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("id", task.id()); + + return jdbcTemplate.update(sql, params); + } + + /** + * Find all Tasks given a User ID. + * + * @param userId The User ID to fetch by. + * @return The list of Tasks found. + */ + public List findAllByUserId(Long userId) { + String sql = + """ + SELECT id, + user_id, + description, + completed, + last_update, + due_date, + due_date_notify, + due_date_notify_sent, + high_priority + FROM tasknote.tasks + WHERE user_id = :userId + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("userId", userId); + + return jdbcTemplate.query(sql, params, new TaskRowMapper()); + } + + /** + * Find all Tasks given a Task ID and a User ID. + * + * @param id The Task ID to find by. + * @param userId The User ID to find by + * @return Optional of a Task instance. + */ + public Optional findByIdAndUserId(Long id, Long userId) { + String sql = + """ + SELECT id, + user_id, + description, + completed, + last_update, + due_date, + due_date_notify, + due_date_notify_sent, + high_priority + FROM tasknote.tasks + WHERE user_id = :userId + AND id = :id + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("userId", userId) + .addValue("id", id); + + return jdbcTemplate.query(sql, params, new TaskRowMapper()).stream().findFirst(); + } + + /** + * Find all Tasks given a User ID and a Search text. + * + * @param searchTerm The Text to be searched by. + * @param userId The User ID to be search by. + * @return List of Tasks. + */ + public List findAllBySearchTerm(String searchTerm, Long userId) { + String sql = + """ + SELECT DISTINCT t.* + FROM tasknote.tasks t + LEFT JOIN tasknote.task_url tu ON tu.task_id = t.id + LEFT JOIN tasknote.task_tags tt ON tt.task_id = t.id + LEFT JOIN tasknote.tags tg ON tg.id = tt.tag_id + WHERE ( + UPPER(t.description) LIKE UPPER(CONCAT('%', :searchTerm, '%')) + OR UPPER(tg.name) LIKE UPPER(CONCAT('%', :searchTerm, '%')) + OR UPPER(tu.url) LIKE UPPER(CONCAT('%', :searchTerm, '%')) + ) + AND t.user_id = :userId + AND t.completed = false + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("userId", userId) + .addValue("searchTerm", searchTerm); + + return jdbcTemplate.query(sql, params, new TaskRowMapper()); + } + + class TaskRowMapper implements org.springframework.jdbc.core.RowMapper { + @Override + public Task mapRow(java.sql.ResultSet rs, int rowNum) throws java.sql.SQLException { + return new Task( + rs.getLong("id"), + rs.getLong("user_id"), + rs.getString("description"), + rs.getBoolean("completed"), + rs.getObject("last_update", LocalDateTime.class), + rs.getObject("due_date", LocalDate.class), + rs.getBoolean("due_date_notify"), + rs.getBoolean("due_date_notify_sent"), + rs.getBoolean("high_priority") + ); + } + } } + diff --git a/server/src/main/java/br/com/tasknoteapp/server/repository/TaskUrlRepository.java b/server/src/main/java/br/com/tasknoteapp/server/repository/TaskUrlRepository.java index ad3b04d4..7db22a7b 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/repository/TaskUrlRepository.java +++ b/server/src/main/java/br/com/tasknoteapp/server/repository/TaskUrlRepository.java @@ -1,14 +1,111 @@ package br.com.tasknoteapp.server.repository; -import br.com.tasknoteapp.server.entity.TaskUrlEntity; -import br.com.tasknoteapp.server.entity.TaskUrlEntityPk; +import br.com.tasknoteapp.server.entity.TaskUrl; +import br.com.tasknoteapp.server.entity.TaskUrlPk; +import java.util.Arrays; import java.util.List; -import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Repository; /** This interface represents a task url repository, for database access. */ -public interface TaskUrlRepository extends JpaRepository { +@Repository +public class TaskUrlRepository { - void deleteAllById_taskId(Long taskId); + private final NamedParameterJdbcTemplate jdbcTemplate; - List findAllById_taskId(Long taskId); + public TaskUrlRepository(NamedParameterJdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + /** + * Save all Task URLs (insert). + * + * @param taskUrls List of TaskUrl instances to be created. + * @return Number of created records. + */ + public int saveAll(List taskUrls) { + String sql = + """ + INSERT INTO tasknote.task_url (task_id, url) + VALUES (:taskId, :url) + """; + + MapSqlParameterSource[] params = taskUrls.stream() + .map(taskUrl -> new MapSqlParameterSource() + .addValue("taskId", taskUrl.id().taskId()) + .addValue("url", taskUrl.id().url())) + .toArray(MapSqlParameterSource[]::new); + + return Arrays.stream(jdbcTemplate.batchUpdate(sql, params)).sum(); + } + + /** + * Delete all Task URLs given a Task ID. + * + * @param taskId The Task ID to delete for. + * @return Number of deleted records. + */ + public int deleteAllById_taskId(Long taskId) { + String sql = + """ + DELETE FROM tasknote.task_url + WHERE task_id = :taskId + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("taskId", taskId); + + return jdbcTemplate.update(sql, params); + } + + /** + * Delete a TaskUrl by its ID (Task ID and URL). + * + * @param taskUrlId The ID with Task ID and URL. + * @return Number of affected rows. + */ + public int deleteById(TaskUrlPk taskUrlId) { + String sql = + """ + DELETE FROM tasknote.task_url + WHERE task_id = :taskId + AND url = :url + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("taskId", taskUrlId.taskId()) + .addValue("url", taskUrlId.url()); + + return jdbcTemplate.update(sql, params); + } + + /** + * Find all Task URLs given a Task ID. + * + * @param taskId The Task ID to search by. + * @return List of TaskUrl found. + */ + public List findAllById_taskId(Long taskId) { + String sql = + """ + SELECT task_id, url + FROM tasknote.task_url + WHERE task_id = :taskId + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("taskId", taskId); + + return jdbcTemplate.query(sql, params, new TaskUrlRowMapper()); + } + + class TaskUrlRowMapper implements org.springframework.jdbc.core.RowMapper { + @Override + public TaskUrl mapRow(java.sql.ResultSet rs, int rowNum) throws java.sql.SQLException { + TaskUrlPk pk = new TaskUrlPk(rs.getLong("task_id"), rs.getString("url")); + return new TaskUrl(pk); + } + } } + diff --git a/server/src/main/java/br/com/tasknoteapp/server/repository/UserPwdLimitRepository.java b/server/src/main/java/br/com/tasknoteapp/server/repository/UserPwdLimitRepository.java index 5d596b10..0ee11d8f 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/repository/UserPwdLimitRepository.java +++ b/server/src/main/java/br/com/tasknoteapp/server/repository/UserPwdLimitRepository.java @@ -1,17 +1,114 @@ package br.com.tasknoteapp.server.repository; -import br.com.tasknoteapp.server.entity.UserPwdLimitEntity; +import br.com.tasknoteapp.server.entity.UserPwdLimit; +import java.time.LocalDateTime; import java.util.List; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Repository; /** This interface represents a user password limit repository, for database access. */ -public interface UserPwdLimitRepository extends JpaRepository { +@Repository +public class UserPwdLimitRepository { - List findTop3ByUser_idOrderByWhenHappenedDesc(Long userId); + private final NamedParameterJdbcTemplate jdbcTemplate; - @Modifying - @Query("delete UserPwdLimitEntity u where u.user.id = ?1") - void deleteAllForUser(Long userId); + public UserPwdLimitRepository(NamedParameterJdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + /** + * Save a User Password Limit (create or update). + * + * @param userPwdLimit The UserPwdLimit instance to be created or updated. + * @return The created or updated UserPwdLimit instance. + */ + public UserPwdLimit save(UserPwdLimit userPwdLimit) { + return userPwdLimit.id() == null + ? create(userPwdLimit) + : update(userPwdLimit); + } + + private UserPwdLimit create(UserPwdLimit userPwdLimit) { + String sql = + """ + INSERT INTO tasknote.user_pwd_limits (user_id, when_happened) + VALUES (:userId, :whenHappened) + RETURNING id, user_id, when_happened + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("userId", userPwdLimit.userId()) + .addValue("whenHappened", userPwdLimit.whenHappened()); + + return jdbcTemplate.queryForObject(sql, params, new UserPwdLimitRowMapper()); + } + + private UserPwdLimit update(UserPwdLimit userPwdLimit) { + String sql = + """ + UPDATE tasknote.user_pwd_limits + SET when_happened = :whenHappened + WHERE id = :id + RETURNING id, user_id, when_happened + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("whenHappened", userPwdLimit.whenHappened()) + .addValue("id", userPwdLimit.id()); + + return jdbcTemplate.queryForObject(sql, params, new UserPwdLimitRowMapper()); + } + + /** + * Find the last 3 User Password Limit records given a User ID, most recent first. + * + * @param userId The User ID to search by. + * @return List of up to 3 UserPwdLimit found. + */ + public List findTop3ByUser_idOrderByWhenHappenedDesc(Long userId) { + String sql = + """ + SELECT id, user_id, when_happened + FROM tasknote.user_pwd_limits + WHERE user_id = :userId + ORDER BY when_happened DESC + LIMIT 3 + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("userId", userId); + + return jdbcTemplate.query(sql, params, new UserPwdLimitRowMapper()); + } + + /** + * Delete all User Password Limit records given a User ID. + * + * @param userId The User ID to delete for. + */ + public void deleteAllForUser(Long userId) { + String sql = + """ + DELETE FROM tasknote.user_pwd_limits + WHERE user_id = :userId + """; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("userId", userId); + + jdbcTemplate.update(sql, params); + } + + class UserPwdLimitRowMapper implements org.springframework.jdbc.core.RowMapper { + @Override + public UserPwdLimit mapRow(java.sql.ResultSet rs, int rowNum) throws java.sql.SQLException { + return new UserPwdLimit( + rs.getLong("id"), + rs.getObject("when_happened", LocalDateTime.class), + rs.getLong("user_id") + ); + } + } } + diff --git a/server/src/main/java/br/com/tasknoteapp/server/repository/UserRepository.java b/server/src/main/java/br/com/tasknoteapp/server/repository/UserRepository.java index 291cfabc..2a195ce5 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/repository/UserRepository.java +++ b/server/src/main/java/br/com/tasknoteapp/server/repository/UserRepository.java @@ -1,16 +1,200 @@ package br.com.tasknoteapp.server.repository; -import br.com.tasknoteapp.server.entity.UserEntity; +import br.com.tasknoteapp.server.entity.User; +import java.time.LocalDateTime; +import java.util.List; import java.util.Optional; import java.util.UUID; -import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Repository; /** This interface contains methods to access the user table in the database. */ -public interface UserRepository extends JpaRepository { +@Repository +public class UserRepository { - Optional findByEmail(String email); + private static final String COLUMNS = """ + id, email, password, admin, created_at, inactivated_at, name, email_confirmed_at, \ + email_uuid, reset_password_expiration, reset_token, lang, theme, last_password_change, \ + last_login"""; - Optional findByEmailUuid(UUID uuid); + private final NamedParameterJdbcTemplate jdbcTemplate; - Optional findByResetToken(String token); + public UserRepository(NamedParameterJdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + /** + * Save a User (create or update). + * + * @param user The User instance to be created or updated. + * @return The created or updated User instance. + */ + public User save(User user) { + return user.getId() == null + ? create(user) + : update(user); + } + + private User create(User user) { + String sql = """ + INSERT INTO tasknote.users ( + email, password, admin, created_at, inactivated_at, name, email_confirmed_at, email_uuid, + reset_password_expiration, reset_token, lang, theme, last_password_change, last_login) + VALUES ( + :email, :password, :admin, :createdAt, :inactivatedAt, :name, :emailConfirmedAt, :emailUuid, + :resetPasswordExpiration, :resetToken, :lang, :theme, :lastPasswordChange, :lastLogin) + RETURNING\s""" + COLUMNS; + + MapSqlParameterSource params = userParams(user); + + return jdbcTemplate.queryForObject(sql, params, new UserRowMapper()); + } + + private User update(User user) { + String sql = """ + UPDATE tasknote.users + SET email = :email, + password = :password, + admin = :admin, + created_at = :createdAt, + inactivated_at = :inactivatedAt, + name = :name, + email_confirmed_at = :emailConfirmedAt, + email_uuid = :emailUuid, + reset_password_expiration = :resetPasswordExpiration, + reset_token = :resetToken, + lang = :lang, + theme = :theme, + last_password_change = :lastPasswordChange, + last_login = :lastLogin + WHERE id = :id + RETURNING\s""" + COLUMNS; + + MapSqlParameterSource params = userParams(user) + .addValue("id", user.getId()); + + return jdbcTemplate.queryForObject(sql, params, new UserRowMapper()); + } + + /** + * Delete a User from the database. + * + * @param user The User instance to be deleted (needs the ID). + */ + public void delete(User user) { + String sql = "DELETE FROM tasknote.users WHERE id = :id"; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("id", user.getId()); + + jdbcTemplate.update(sql, params); + } + + /** + * Find a User by ID. + * + * @param id The User ID to search by. + * @return Optional of User. Can be empty. + */ + public Optional findById(Long id) { + String sql = "SELECT " + COLUMNS + " FROM tasknote.users WHERE id = :id"; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("id", id); + + return findOne(sql, params); + } + + /** + * Find a User by email. + * + * @param email The email to search by. + * @return Optional of User. Can be empty. + */ + public Optional findByEmail(String email) { + String sql = "SELECT " + COLUMNS + " FROM tasknote.users WHERE email = :email"; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("email", email); + + return findOne(sql, params); + } + + /** + * Find a User by email UUID (email confirmation). + * + * @param uuid The email UUID to search by. + * @return Optional of User. Can be empty. + */ + public Optional findByEmailUuid(UUID uuid) { + String sql = "SELECT " + COLUMNS + " FROM tasknote.users WHERE email_uuid = :uuid"; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("uuid", uuid); + + return findOne(sql, params); + } + + /** + * Find a User by reset token (password reset). + * + * @param token The reset token to search by. + * @return Optional of User. Can be empty. + */ + public Optional findByResetToken(String token) { + String sql = "SELECT " + COLUMNS + " FROM tasknote.users WHERE reset_token = :token"; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("token", token); + + return findOne(sql, params); + } + + private Optional findOne(String sql, MapSqlParameterSource params) { + List users = jdbcTemplate.query(sql, params, new UserRowMapper()); + return users.stream().findFirst(); + } + + private MapSqlParameterSource userParams(User user) { + return new MapSqlParameterSource() + .addValue("email", user.getEmail()) + .addValue("password", user.getPassword()) + .addValue("admin", user.getAdmin()) + .addValue("createdAt", user.getCreatedAt()) + .addValue("inactivatedAt", user.getInactivatedAt()) + .addValue("name", user.getName()) + .addValue("emailConfirmedAt", user.getEmailConfirmedAt()) + .addValue("emailUuid", user.getEmailUuid()) + .addValue("resetPasswordExpiration", user.getResetPasswordExpiration()) + .addValue("resetToken", user.getResetToken()) + .addValue("lang", user.getLang()) + .addValue("theme", user.getTheme()) + .addValue("lastPasswordChange", user.getLastPasswordChange()) + .addValue("lastLogin", user.getLastLogin()); + } + + class UserRowMapper implements org.springframework.jdbc.core.RowMapper { + @Override + public User mapRow(java.sql.ResultSet rs, int rowNum) throws java.sql.SQLException { + User user = new User(); + user.setId(rs.getLong("id")); + user.setEmail(rs.getString("email")); + user.setPassword(rs.getString("password")); + user.setAdmin(rs.getBoolean("admin")); + user.setCreatedAt(rs.getObject("created_at", LocalDateTime.class)); + user.setInactivatedAt(rs.getObject("inactivated_at", LocalDateTime.class)); + user.setName(rs.getString("name")); + user.setEmailConfirmedAt(rs.getObject("email_confirmed_at", LocalDateTime.class)); + user.setEmailUuid(rs.getObject("email_uuid", UUID.class)); + user.setResetPasswordExpiration( + rs.getObject("reset_password_expiration", LocalDateTime.class)); + user.setResetToken(rs.getString("reset_token")); + user.setLang(rs.getString("lang")); + user.setTheme(rs.getString("theme")); + user.setLastPasswordChange(rs.getObject("last_password_change", LocalDateTime.class)); + user.setLastLogin(rs.getObject("last_login", LocalDateTime.class)); + return user; + } + } } diff --git a/server/src/main/java/br/com/tasknoteapp/server/response/NoteResponse.java b/server/src/main/java/br/com/tasknoteapp/server/response/NoteResponse.java index 09027658..cc19c659 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/response/NoteResponse.java +++ b/server/src/main/java/br/com/tasknoteapp/server/response/NoteResponse.java @@ -1,7 +1,7 @@ package br.com.tasknoteapp.server.response; -import br.com.tasknoteapp.server.entity.NoteEntity; -import br.com.tasknoteapp.server.entity.TagEntity; +import br.com.tasknoteapp.server.entity.Note; +import br.com.tasknoteapp.server.entity.Tag; import br.com.tasknoteapp.server.util.TimeAgoUtil; import java.util.List; @@ -18,24 +18,24 @@ public record NoteResponse( boolean archived) { /** - * Creates a NoteResponse given a NoteEntity and its Urals. + * Creates a NoteResponse given a Note and its Urals. * - * @param entity The NoteEntity source data. + * @param entity The Note source data. * @param url The URL associated with the note. * @return NoteResponse instance with all note data and URLs, if any. */ - public static NoteResponse fromEntity(NoteEntity entity, String url) { - String timeAgoFmt = TimeAgoUtil.format(entity.getLastUpdate()); + public static NoteResponse fromEntity(Note entity, String url, List tags) { + String timeAgoFmt = TimeAgoUtil.format(entity.lastUpdate()); return new NoteResponse( - entity.getId(), - entity.getTitle(), - entity.getDescription(), + entity.id(), + entity.title(), + entity.description(), url, timeAgoFmt, - entity.getTags().stream().map(TagEntity::getName).toList(), - entity.isShared(), - entity.getShareToken(), - entity.isArchived()); + tags.stream().map((t) -> t.name()).toList(), + entity.shared(), + entity.shareToken(), + entity.archived()); } } diff --git a/server/src/main/java/br/com/tasknoteapp/server/response/TaskResponse.java b/server/src/main/java/br/com/tasknoteapp/server/response/TaskResponse.java index 251f4479..9710b498 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/response/TaskResponse.java +++ b/server/src/main/java/br/com/tasknoteapp/server/response/TaskResponse.java @@ -1,7 +1,7 @@ package br.com.tasknoteapp.server.response; -import br.com.tasknoteapp.server.entity.TagEntity; -import br.com.tasknoteapp.server.entity.TaskEntity; +import br.com.tasknoteapp.server.entity.Tag; +import br.com.tasknoteapp.server.entity.Task; import br.com.tasknoteapp.server.util.TimeAgoUtil; import java.time.LocalDate; import java.util.List; @@ -25,19 +25,19 @@ public record TaskResponse( * @param urls The URLs associated with the task. * @return TaskResponse instance with all task data and URLs, if any. */ - public static TaskResponse fromEntity(TaskEntity entity, List urls) { - String timeAgoFmt = TimeAgoUtil.format(entity.getLastUpdate()); - String dueDateFmt = TimeAgoUtil.formatDueDate(entity.getDueDate()); + public static TaskResponse fromEntity(Task entity, List urls, List tags) { + String timeAgoFmt = TimeAgoUtil.format(entity.lastUpdate()); + String dueDateFmt = TimeAgoUtil.formatDueDate(entity.dueDate()); return new TaskResponse( - entity.getId(), - entity.getCompleted(), - entity.getDescription(), - entity.getHighPriority(), - entity.getDueDate(), + entity.id(), + entity.completed(), + entity.description(), + entity.highPriority(), + entity.dueDate(), dueDateFmt, timeAgoFmt, - entity.getTags().stream().map(TagEntity::getName).toList(), + tags.stream().map((t) -> t.name()).toList(), urls); } } diff --git a/server/src/main/java/br/com/tasknoteapp/server/response/UserResponse.java b/server/src/main/java/br/com/tasknoteapp/server/response/UserResponse.java index 53a10245..6e34929f 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/response/UserResponse.java +++ b/server/src/main/java/br/com/tasknoteapp/server/response/UserResponse.java @@ -1,6 +1,6 @@ package br.com.tasknoteapp.server.response; -import br.com.tasknoteapp.server.entity.UserEntity; +import br.com.tasknoteapp.server.entity.User; import java.time.LocalDateTime; /** This record represents a User Response object. */ @@ -16,12 +16,12 @@ public record UserResponse( String theme) { /** - * Create a {@link UserResponse} instance from a {@link UserEntity}. + * Create a {@link UserResponse} instance from a {@link User}. * * @param user The user entity instance with user info to be used as source. * @return UserResponse instance. */ - public static UserResponse fromEntity(UserEntity user, String gravatarUrl) { + public static UserResponse fromEntity(User user, String gravatarUrl) { String theme = user.getTheme() == null ? "light" : user.getTheme(); return new UserResponse( user.getId(), diff --git a/server/src/main/java/br/com/tasknoteapp/server/response/UserResponseWithToken.java b/server/src/main/java/br/com/tasknoteapp/server/response/UserResponseWithToken.java index f335d266..563cd692 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/response/UserResponseWithToken.java +++ b/server/src/main/java/br/com/tasknoteapp/server/response/UserResponseWithToken.java @@ -1,6 +1,6 @@ package br.com.tasknoteapp.server.response; -import br.com.tasknoteapp.server.entity.UserEntity; +import br.com.tasknoteapp.server.entity.User; import java.time.LocalDateTime; /** This record represents a User Response object. */ @@ -17,14 +17,14 @@ public record UserResponseWithToken( String lang) { /** - * Create a {@link UserResponseWithToken} instance from a {@link UserEntity}. + * Create a {@link UserResponseWithToken} instance from a {@link User}. * * @param user The user entity instance with user info to be used as source. * @param token The token created upon registration or login. * @return UserResponse instance. */ public static UserResponseWithToken fromEntity( - UserEntity user, String token, String gravatarUrl) { + User user, String token, String gravatarUrl) { return new UserResponseWithToken( user.getId(), user.getName(), diff --git a/server/src/main/java/br/com/tasknoteapp/server/service/AppVersionService.java b/server/src/main/java/br/com/tasknoteapp/server/service/AppVersionService.java index 257ce5bf..db6b5d0a 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/service/AppVersionService.java +++ b/server/src/main/java/br/com/tasknoteapp/server/service/AppVersionService.java @@ -12,7 +12,7 @@ @Service public class AppVersionService { - private static final Logger logger = LoggerFactory.getLogger(AppVersionService.class); + private final Logger logger = LoggerFactory.getLogger(getClass()); private final BuildProperties buildProperties; diff --git a/server/src/main/java/br/com/tasknoteapp/server/service/AuthService.java b/server/src/main/java/br/com/tasknoteapp/server/service/AuthService.java index 6df98d05..3c202ed0 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/service/AuthService.java +++ b/server/src/main/java/br/com/tasknoteapp/server/service/AuthService.java @@ -1,7 +1,7 @@ package br.com.tasknoteapp.server.service; -import br.com.tasknoteapp.server.entity.UserEntity; -import br.com.tasknoteapp.server.entity.UserPwdLimitEntity; +import br.com.tasknoteapp.server.entity.User; +import br.com.tasknoteapp.server.entity.UserPwdLimit; import br.com.tasknoteapp.server.exception.BadLanguageException; import br.com.tasknoteapp.server.exception.BadPasswordException; import br.com.tasknoteapp.server.exception.BadThemeException; @@ -11,7 +11,6 @@ import br.com.tasknoteapp.server.exception.InvalidCredentialsException; import br.com.tasknoteapp.server.exception.MaxLoginLimitAttemptException; import br.com.tasknoteapp.server.exception.ResetExpiredException; -import br.com.tasknoteapp.server.exception.UserForbiddenException; import br.com.tasknoteapp.server.exception.UserNotFoundException; import br.com.tasknoteapp.server.repository.UserPwdLimitRepository; import br.com.tasknoteapp.server.repository.UserRepository; @@ -24,7 +23,6 @@ import br.com.tasknoteapp.server.util.SecurityUtil; import br.com.tasknoteapp.server.util.TokenUtil; import br.com.tasknoteapp.server.util.UuidUtil; -import jakarta.transaction.Transactional; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -44,15 +42,15 @@ import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.userdetails.User; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; /** This class contains the implementation for the Auth Service class. */ @Service public class AuthService { - private static final Logger logger = LoggerFactory.getLogger(AuthService.class); + private final Logger logger = LoggerFactory.getLogger(getClass()); private final UserRepository userRepository; @@ -107,7 +105,7 @@ public AuthService( * @param newUser User details with email and password. * @return Token */ - @Transactional + @Transactional public UserResponseWithToken signUpNewUser(LoginRequest newUser) { logger.info("Signing up new user: {}", SecurityUtil.redactEmail(newUser.email())); @@ -132,7 +130,7 @@ public UserResponseWithToken signUpNewUser(LoginRequest newUser) { UUID emailUuid = new UuidUtil().generateEmailUuid(newUser.email()); - UserEntity user = new UserEntity(); + User user = new User(); user.setEmail(newUser.email()); user.setPassword(passwordEncoder.encode(newUser.password())); user.setAdmin(false); @@ -140,7 +138,7 @@ public UserResponseWithToken signUpNewUser(LoginRequest newUser) { user.setLastPasswordChange(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS)); user.setEmailUuid(emailUuid); user.setLang(newUser.lang()); - userRepository.save(user); + user = userRepository.save(user); if (hasValidMailgunApiKey()) { mailgunEmailService.sendNewUser(user); @@ -157,7 +155,7 @@ public UserResponseWithToken signUpNewUser(LoginRequest newUser) { * @param email The user email. * @return Optional of a UserEntity instance. */ - public Optional findByEmail(String email) { + public Optional findByEmail(String email) { return userRepository.findByEmail(email); } @@ -165,15 +163,18 @@ public Optional findByEmail(String email) { * Load a user from the database given his email. * * @param email The user email. - * @return User with found record. + * @return User with found record (from org.springframework.security.core.userdetails.User). */ - public User loadUserByUsername(String email) { - Optional user = userRepository.findByEmail(email); + public org.springframework.security.core.userdetails.User loadUserByUsername(String email) { + Optional user = userRepository.findByEmail(email); if (user.isEmpty()) { throw new UserNotFoundException(); } - return new User(user.get().getEmail(), user.get().getPassword(), new ArrayList<>()); + org.springframework.security.core.userdetails.User springUser = + new org.springframework.security.core.userdetails.User( + user.get().getEmail(), user.get().getPassword(), new ArrayList<>()); + return springUser; } /** @@ -186,14 +187,14 @@ public User loadUserByUsername(String email) { public UserResponseWithToken signInUser(LoginRequest login) { logger.info("Signing in user: {}", SecurityUtil.redactEmail(login.email())); - Optional userOptional = findByEmail(login.email()); + Optional userOptional = findByEmail(login.email()); if (userOptional.isEmpty()) { throw new InvalidCredentialsException(); } checkLoginAttemptLimit(userOptional.get().getId()); - UserEntity user = userOptional.get(); + User user = userOptional.get(); if (Objects.isNull(user.getEmailConfirmedAt())) { logger.warn("User {} tried to login but email is not confirmed", user.getId()); @@ -232,53 +233,13 @@ public UserResponseWithToken signInUser(LoginRequest login) { logger.error( "BadCredentialsException when logging in user {}: {}", user.getId(), e.getMessage()); - // store attempt - UserPwdLimitEntity pwdLimit = new UserPwdLimitEntity(); - pwdLimit.setWhenHappened(LocalDateTime.now()); - pwdLimit.setUser(user); + UserPwdLimit pwdLimit = new UserPwdLimit(null, LocalDateTime.now(), user.getId()); userPwdLimitRepository.save(pwdLimit); return null; } } - /** - * Get all registered users. Only allowed for admin users. - * - * @return List of UserEntity. - * @throws UserForbiddenException when the user has no permissions. - */ - public List getAllUsers() { - Optional currentUserEmail = authUtil.getCurrentUserEmail(); - if (currentUserEmail.isEmpty()) { - logger.error("Unable to get current user from the request"); - throw new UserNotFoundException(); - } - - Optional currentUserOpt = findByEmail(currentUserEmail.get()); - if (currentUserOpt.isEmpty()) { - logger.error("Unable to find user by email with value: {}", currentUserEmail.get()); - throw new UserNotFoundException(); - } - - UserEntity currentUser = currentUserOpt.get(); - if (!currentUser.getAdmin()) { - logger.warn("User {} not allowed to list users.", currentUser.getId()); - throw new UserForbiddenException(); - } - - logger.info("Getting all users to user {}", currentUser.getId()); - List users = userRepository.findAll(); - List usersResponse = new ArrayList<>(users.size()); - users.forEach( - u -> - usersResponse.add( - UserResponse.fromEntity(u, getGravatarImageUrl(u.getEmail()).orElse(null)))); - logger.info("{} user(s) found!", usersResponse.size()); - - return usersResponse; - } - /** * Refresh the current user token. * @@ -287,7 +248,7 @@ public List getAllUsers() { public String refreshCurrentUserToken() { Optional currentUserEmail = authUtil.getCurrentUserEmail(); String email = currentUserEmail.orElseThrow(); - UserEntity currentUser = findByEmail(email).orElseThrow(); + User currentUser = findByEmail(email).orElseThrow(); logger.info("Refreshing current session to user {}", currentUser.getId()); @@ -301,20 +262,18 @@ public String refreshCurrentUserToken() { * Verify the given password against the current user, recording a failed attempt for rate * limiting when it does not match. * - * @param user The {@link UserEntity} to check the password against. + * @param user The {@link User} to check the password against. * @param password The plain text password to verify. * @throws MaxLoginLimitAttemptException when too many failed attempts happened recently. * @throws InvalidCredentialsException when the password does not match. */ - public void verifyCurrentPassword(UserEntity user, String password) { + public void verifyCurrentPassword(User user, String password) { checkLoginAttemptLimit(user.getId()); if (Objects.isNull(password) || !passwordEncoder.matches(password, user.getPassword())) { logger.warn("Password verification failed for user {}", user.getId()); - UserPwdLimitEntity pwdLimit = new UserPwdLimitEntity(); - pwdLimit.setWhenHappened(LocalDateTime.now()); - pwdLimit.setUser(user); + UserPwdLimit pwdLimit = new UserPwdLimit(null, LocalDateTime.now(), user.getId()); userPwdLimitRepository.save(pwdLimit); throw new InvalidCredentialsException(); @@ -329,7 +288,7 @@ public void verifyCurrentPassword(UserEntity user, String password) { public UserResponse deleteUserAccount() { Optional currentUserEmail = authUtil.getCurrentUserEmail(); String email = currentUserEmail.orElseThrow(); - UserEntity currentUser = findByEmail(email).orElseThrow(); + User currentUser = findByEmail(email).orElseThrow(); logger.info("Deleting account for user {}", currentUser.getId()); @@ -350,7 +309,7 @@ public UserResponse deleteUserAccount() { public UserResponse patchUserInfo(UserPatchRequest patchRequest) { Optional currentUserEmail = authUtil.getCurrentUserEmail(); String email = currentUserEmail.orElseThrow(); - UserEntity currentUser = findByEmail(email).orElseThrow(); + User currentUser = findByEmail(email).orElseThrow(); boolean shouldUpdate = false; boolean emailChanged = false; @@ -416,7 +375,6 @@ public UserResponse patchUserInfo(UserPatchRequest patchRequest) { } if (emailChanged && hasValidMailgunApiKey()) { - // send email to older and new account logger.info( "Email changed from {} to {}", email, SecurityUtil.redactEmail(patchRequest.email())); mailgunEmailService.sendEmailChangedNotification(currentUser, email); @@ -428,10 +386,10 @@ public UserResponse patchUserInfo(UserPatchRequest patchRequest) { /** * Get the current logged user (based in the JWT Authentication). * - * @return An instance of {@link UserEntity} with the current user. + * @return An instance of {@link User} with the current user. * @throws UserNotFoundException when the user was not found */ - public Optional getCurrentUser() { + public Optional getCurrentUser() { Optional currentUserEmail = authUtil.getCurrentUserEmail(); if (currentUserEmail.isEmpty()) { throw new UserNotFoundException(); @@ -447,7 +405,7 @@ public Optional getCurrentUser() { * @throws UserNotFoundException when the user was not found */ public UserResponse getCurrentUserResponse() { - UserEntity user = getCurrentUser().orElseThrow(UserNotFoundException::new); + User user = getCurrentUser().orElseThrow(UserNotFoundException::new); return UserResponse.fromEntity(user, getGravatarImageUrl(user.getEmail()).orElse(null)); } @@ -468,12 +426,12 @@ public void confirmUserAccount(String identification) { throw new BadUuidException(); } - Optional userOptional = userRepository.findByEmailUuid(uuid); + Optional userOptional = userRepository.findByEmailUuid(uuid); if (userOptional.isEmpty()) { throw new UserNotFoundException(); } - UserEntity user = userOptional.get(); + User user = userOptional.get(); user.setEmailConfirmedAt(LocalDateTime.now()); userRepository.save(user); @@ -488,12 +446,12 @@ public void confirmUserAccount(String identification) { public void resendEmailConfirmation(String email) { logger.info("Re-sending the confirmation email"); - Optional userOptional = userRepository.findByEmail(email); + Optional userOptional = userRepository.findByEmail(email); if (userOptional.isEmpty()) { throw new UserNotFoundException(); } - UserEntity user = userOptional.get(); + User user = userOptional.get(); if (hasValidMailgunApiKey()) { mailgunEmailService.sendNewUser(user); @@ -511,7 +469,7 @@ public void resendEmailConfirmation(String email) { public void resetPasswordForUser(String email) { logger.info("Requesting password reset for email {}", email); - Optional userOptional = userRepository.findByEmail(email); + Optional userOptional = userRepository.findByEmail(email); if (userOptional.isEmpty()) { logger.info("No user found with email {}", email); return; @@ -519,7 +477,7 @@ public void resetPasswordForUser(String email) { String resetToken = new TokenUtil().generateToken(); - UserEntity user = userOptional.get(); + User user = userOptional.get(); user.setResetToken(resetToken); user.setResetPasswordExpiration( LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS).plusHours(2L)); @@ -541,7 +499,7 @@ public void resetPasswordForUser(String email) { public void confirmResetPasswordForUser(PasswordResetRequest request) { logger.info("Saving new password for token {}", request.token()); - Optional userOptional = userRepository.findByResetToken(request.token()); + Optional userOptional = userRepository.findByResetToken(request.token()); if (userOptional.isEmpty()) { throw new UserNotFoundException(); } @@ -562,7 +520,7 @@ public void confirmResetPasswordForUser(PasswordResetRequest request) { throw new BadPasswordException("The passwords should match"); } - UserEntity user = userOptional.get(); + User user = userOptional.get(); user.setResetToken(null); user.setResetPasswordExpiration(null); user.setPassword(passwordEncoder.encode(request.password())); @@ -603,16 +561,16 @@ private Optional getGravatarImageUrl(String email) { private void checkLoginAttemptLimit(Long userId) { // Fetch only the 3 most recent failed attempts to avoid loading unbounded rows for // targeted/brute-forced accounts. - List userPwdList = + List userPwdList = userPwdLimitRepository.findTop3ByUser_idOrderByWhenHappenedDesc(userId); logger.warn("login count attempt for user {}: {}", userId, userPwdList.size()); // if it's more than 3 times in the last 10 minutes, raise timer of 3 hours. if (userPwdList.size() >= 3) { - UserPwdLimitEntity oldest = userPwdList.getLast(); - logger.warn("Oldest failed attempt: {}", oldest.getWhenHappened()); - Duration duration = Duration.between(oldest.getWhenHappened(), LocalDateTime.now()); + UserPwdLimit oldest = userPwdList.getLast(); + logger.warn("Oldest failed attempt: {}", oldest.whenHappened()); + Duration duration = Duration.between(oldest.whenHappened(), LocalDateTime.now()); if (duration.toMinutes() <= 3L) { logger.warn("Account locked, minutes remaining: {}", 3L - duration.toMinutes()); throw new MaxLoginLimitAttemptException(); diff --git a/server/src/main/java/br/com/tasknoteapp/server/service/HomeService.java b/server/src/main/java/br/com/tasknoteapp/server/service/HomeService.java index caadfbff..138e1311 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/service/HomeService.java +++ b/server/src/main/java/br/com/tasknoteapp/server/service/HomeService.java @@ -1,13 +1,14 @@ package br.com.tasknoteapp.server.service; -import br.com.tasknoteapp.server.entity.TagEntity; -import br.com.tasknoteapp.server.entity.UserEntity; +import br.com.tasknoteapp.server.entity.User; import br.com.tasknoteapp.server.repository.TagRepository; import br.com.tasknoteapp.server.response.NoteResponse; import br.com.tasknoteapp.server.response.TaskResponse; import br.com.tasknoteapp.server.util.AuthUtil; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -22,8 +23,6 @@ public class HomeService { private final NoteService noteService; - private final TagRepository tagRepository; - private final AuthService authService; private final AuthUtil authUtil; @@ -45,7 +44,6 @@ public HomeService( AuthUtil authUtil) { this.taskService = taskService; this.noteService = noteService; - this.tagRepository = tagRepository; this.authService = authService; this.authUtil = authUtil; } @@ -56,33 +54,35 @@ public HomeService( * @return List of String with the tags. */ public List getTopTasksTag() { - UserEntity user = getCurrentUser(); + User user = getCurrentUser(); logger.info("Getting all tags for user ID {}", user.getId()); - List tags = - tagRepository.findAllByUser_idOrderByNameAsc(user.getId()).stream() - .map(TagEntity::getName) - .toList(); - List tasks = taskService.getTasksByFilter("all"); List notes = noteService.getAllNotes(); + Set tagSet = new HashSet<>(); + tasks.forEach((t) -> tagSet.addAll(t.tags())); + notes.forEach((n) -> tagSet.addAll(n.tags())); + boolean hasUntagged = tasks.stream().anyMatch(task -> task.tags().isEmpty()) || notes.stream().anyMatch(note -> note.tags().isEmpty()); if (hasUntagged) { - tags = new java.util.ArrayList<>(tags); - tags.add("untagged"); - tags = tags.stream().sorted().toList(); + tagSet.add("untagged"); } + List tags = tagSet + .stream() + .sorted() + .toList(); + logger.info("Found {} tags", tags.size()); return tags; } - private UserEntity getCurrentUser() { + private User getCurrentUser() { Optional currentUserEmail = authUtil.getCurrentUserEmail(); String email = currentUserEmail.orElseThrow(); return authService.findByEmail(email).orElseThrow(); diff --git a/server/src/main/java/br/com/tasknoteapp/server/service/JwtService.java b/server/src/main/java/br/com/tasknoteapp/server/service/JwtService.java index 6c38e77d..0a5dc506 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/service/JwtService.java +++ b/server/src/main/java/br/com/tasknoteapp/server/service/JwtService.java @@ -1,6 +1,6 @@ package br.com.tasknoteapp.server.service; -import br.com.tasknoteapp.server.entity.UserEntity; +import br.com.tasknoteapp.server.entity.User; import java.time.LocalDateTime; import java.util.Map; import org.springframework.security.core.userdetails.UserDetails; @@ -12,7 +12,7 @@ public interface JwtService { LocalDateTime extractExpiration(String token); - String generateToken(UserEntity user); + String generateToken(User user); String createToken(Map claims, String email); diff --git a/server/src/main/java/br/com/tasknoteapp/server/service/MailgunEmailService.java b/server/src/main/java/br/com/tasknoteapp/server/service/MailgunEmailService.java index 83885601..21f56480 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/service/MailgunEmailService.java +++ b/server/src/main/java/br/com/tasknoteapp/server/service/MailgunEmailService.java @@ -1,6 +1,6 @@ package br.com.tasknoteapp.server.service; -import br.com.tasknoteapp.server.entity.UserEntity; +import br.com.tasknoteapp.server.entity.User; import br.com.tasknoteapp.server.templates.MailgunTemplate; import br.com.tasknoteapp.server.templates.MailgunTemplateEmailChanged; import br.com.tasknoteapp.server.templates.MailgunTemplateResetPwd; @@ -75,7 +75,7 @@ public MailgunEmailService( * * @param user The user that should be addressed the message. */ - public void sendNewUser(UserEntity user) { + public void sendNewUser(User user) { logger.info("Sending message confirming user email address."); String to = user.getEmail(); @@ -95,7 +95,7 @@ public void sendNewUser(UserEntity user) { * * @param user The user that should be addressed the message. */ - public void sendResetPassword(UserEntity user) { + public void sendResetPassword(User user) { logger.info("Sending message with password reset link"); String to = user.getEmail(); @@ -115,7 +115,7 @@ public void sendResetPassword(UserEntity user) { * * @param user The user that should be addressed the message. */ - public void sendPasswordResetConfirmation(UserEntity user) { + public void sendPasswordResetConfirmation(User user) { logger.info("Sending message with password reset confirmation"); String to = user.getEmail(); @@ -132,7 +132,7 @@ public void sendPasswordResetConfirmation(UserEntity user) { * @param user The user that should be addressed the message. * @param oldEmail The user previous email */ - public void sendEmailChangedNotification(UserEntity user, String oldEmail) { + public void sendEmailChangedNotification(User user, String oldEmail) { logger.info("Sending message with changed email notification"); MailgunTemplateEmailChanged emailChanged = new MailgunTemplateEmailChanged(); diff --git a/server/src/main/java/br/com/tasknoteapp/server/service/NoteService.java b/server/src/main/java/br/com/tasknoteapp/server/service/NoteService.java index 0feaa4bb..48cfe0a9 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/service/NoteService.java +++ b/server/src/main/java/br/com/tasknoteapp/server/service/NoteService.java @@ -1,10 +1,12 @@ package br.com.tasknoteapp.server.service; -import br.com.tasknoteapp.server.entity.NoteEntity; -import br.com.tasknoteapp.server.entity.NoteUrlEntity; -import br.com.tasknoteapp.server.entity.TagEntity; -import br.com.tasknoteapp.server.entity.UserEntity; +import br.com.tasknoteapp.server.entity.Note; +import br.com.tasknoteapp.server.entity.NoteUrl; +import br.com.tasknoteapp.server.entity.Tag; +import br.com.tasknoteapp.server.entity.TaskNoteTag; +import br.com.tasknoteapp.server.entity.User; import br.com.tasknoteapp.server.exception.NoteArchivedException; +import br.com.tasknoteapp.server.exception.NoteNotArchivedException; import br.com.tasknoteapp.server.exception.NoteNotFoundException; import br.com.tasknoteapp.server.repository.NoteRepository; import br.com.tasknoteapp.server.repository.NoteUrlRepository; @@ -13,8 +15,9 @@ import br.com.tasknoteapp.server.request.NoteRequest; import br.com.tasknoteapp.server.response.NoteResponse; import br.com.tasknoteapp.server.util.AuthUtil; -import jakarta.transaction.Transactional; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -26,12 +29,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; /** This class implements the NoteService interface methods. */ @Service public class NoteService { - private static final Logger logger = LoggerFactory.getLogger(NoteService.class); + private final Logger logger = LoggerFactory.getLogger(getClass()); private final NoteRepository noteRepository; @@ -70,16 +74,16 @@ public NoteService( * * @return {@link List} of {@link NoteResponse} with all notes or an empty list. */ - @Transactional + @Transactional public List getAllNotes() { - UserEntity user = getCurrentUser(); + User user = getCurrentUser(); logger.info("Get all notes to user ID {}", user.getId()); - List notes = noteRepository.findAllByUser_id(user.getId()); + List notes = noteRepository.findAllByUserId(user.getId()); logger.info("{} notes found!", notes.size()); - return getNotesUrl(notes); + return buildNoteResponse(notes, user.getId()); } /** @@ -90,20 +94,22 @@ public List getAllNotes() { */ @Transactional public NoteResponse getNoteById(Long noteId) { - UserEntity user = getCurrentUser(); + User user = getCurrentUser(); logger.info("Get note ID {} to user ID {}", noteId, user.getId()); - Optional note = noteRepository.findById(noteId); + Optional note = noteRepository.findByIdAndUserId(noteId, user.getId()); if (note.isEmpty()) { throw new NoteNotFoundException(); } - if (!note.get().getUser().getId().equals(user.getId())) { - throw new NoteNotFoundException(); + logger.info("Note found! ID {}", noteId); + + List responseList = buildNoteResponse(List.of(note.get()), user.getId()); + if (responseList.isEmpty()) { + throw new RuntimeException("Issues during NoteResponse build in getNoteById"); } - logger.info("Note found! ID {}", noteId); - return NoteResponse.fromEntity(note.get(), getNoteUrl(noteId)); + return responseList.getFirst(); } /** @@ -114,32 +120,41 @@ public NoteResponse getNoteById(Long noteId) { */ @Transactional public NoteResponse createNote(NoteRequest noteRequest) { - UserEntity user = getCurrentUser(); + User user = getCurrentUser(); logger.info("Creating note to user ID {}", user.getId()); - NoteEntity note = new NoteEntity(); - note.setTitle(noteRequest.title()); - note.setDescription(noteRequest.description()); + Note note = new Note( + null, + user.getId(), + noteRequest.description(), + noteRequest.title(), + LocalDateTime.now(), + Boolean.FALSE, + null, + Boolean.FALSE + ); + Note created = noteRepository.save(note); + + logger.info("Note created! ID {}", created.id()); + if (!Objects.isNull(noteRequest.tags())) { - note.setTags(getOrCreateTags(noteRequest.tags(), user)); + getOrCreateTags(noteRequest.tags(), user, created.id()); } - note.setLastUpdate(LocalDateTime.now()); - note.setUser(user); - NoteEntity created = noteRepository.save(note); - - logger.info("Note created! ID {}", created.getId()); - String savedUrl = null; if (!Objects.isNull(noteRequest.url()) && !noteRequest.url().isEmpty()) { - NoteUrlEntity urlEntity = saveUrl(created, noteRequest.url()); - savedUrl = urlEntity.getUrl(); + saveUrl(created, noteRequest.url()); } - tagRepository.deleteOrphanedTags(user.getId()); + int deleted = tagRepository.deleteOrphanedTags(user.getId()); + logger.info("Deleted {} tags for user", deleted); + + List responseList = buildNoteResponse(List.of(created), user.getId()); + if (responseList.isEmpty()) { + throw new RuntimeException("Issues during NoteResponse build in createNote"); + } - logger.info("Finished note creation!"); - return NoteResponse.fromEntity(created, savedUrl); + return responseList.getFirst(); } /** @@ -151,49 +166,54 @@ public NoteResponse createNote(NoteRequest noteRequest) { */ @Transactional public NoteResponse patchNote(Long noteId, NotePatchRequest patch) { - UserEntity user = getCurrentUser(); + User user = getCurrentUser(); - logger.info("Patching task ID {} to user ID {}", noteId, user.getId()); + logger.info("Patching note ID {} to user ID {}", noteId, user.getId()); - Optional note = noteRepository.findByIdAndUser_id(noteId, user.getId()); + Optional note = noteRepository.findByIdAndUserId(noteId, user.getId()); if (note.isEmpty()) { throw new NoteNotFoundException(); } - NoteEntity noteEntity = note.get(); - if (noteEntity.isArchived()) { + Note noteEntity = note.get(); + if (noteEntity.archived()) { throw new NoteArchivedException(); } + var title = noteEntity.title(); if (!Objects.isNull(patch.title()) && !patch.title().isBlank()) { - noteEntity.setTitle(patch.title().trim()); + title = patch.title().trim(); } + var description = noteEntity.description(); if (!Objects.isNull(patch.description()) && !patch.description().isBlank()) { - noteEntity.setDescription(patch.description()); - } - if (!Objects.isNull(patch.tags())) { - noteEntity.setTags(getOrCreateTags(patch.tags(), user)); + description = patch.description(); } - noteEntity.setLastUpdate(LocalDateTime.now()); - noteUrlRepository.deleteByNote_id(noteId); - noteUrlRepository.flush(); - logger.info("URL deleted from note ID {} during patch", noteId); + Note noteToPatch = new Note( + noteId, + user.getId(), + description, + title, + LocalDateTime.now(), + noteEntity.shared(), + noteEntity.shareToken(), + Boolean.FALSE + ); - if (!Objects.isNull(patch.url()) && !patch.url().isBlank()) { - saveUrl(noteEntity, patch.url()); - } else { - logger.info("No URLs to patch for note ID {}", noteId); - } + Note patchedNote = noteRepository.save(noteToPatch); - NoteEntity patchedNote = noteRepository.save(noteEntity); - noteRepository.flush(); + logger.info("Note patched! ID {}", patchedNote.id()); - tagRepository.deleteOrphanedTags(user.getId()); + patchNoteUrl(patchedNote, patch, user); - logger.info("Note patched! ID {}", patchedNote.getId()); + patchNoteTags(patchedNote, patch, user); - return NoteResponse.fromEntity(patchedNote, getNoteUrl(patchedNote.getId())); + List responseList = buildNoteResponse(List.of(patchedNote), user.getId()); + if (responseList.isEmpty()) { + throw new RuntimeException("Issues during NoteResponse build in patchNote"); + } + + return responseList.getFirst(); } /** @@ -204,13 +224,13 @@ public NoteResponse patchNote(Long noteId, NotePatchRequest patch) { */ @Transactional public void deleteAllNotesForCurrentUser() { - UserEntity user = getCurrentUser(); + User user = getCurrentUser(); logger.info("Deleting all notes for user ID {}", user.getId()); - List notes = noteRepository.findAllByUser_id(user.getId()); - for (NoteEntity note : notes) { - noteUrlRepository.deleteByNote_id(note.getId()); + List notes = noteRepository.findAllByUserId(user.getId()); + for (Note note : notes) { + noteUrlRepository.deleteByNoteId(note.id()); noteRepository.delete(note); } @@ -226,21 +246,21 @@ public void deleteAllNotesForCurrentUser() { */ @Transactional public void deleteNote(Long noteId) { - UserEntity user = getCurrentUser(); + User user = getCurrentUser(); logger.info("Deleting note ID {} to user ID {}", noteId, user.getId()); - Optional note = noteRepository.findByIdAndUser_id(noteId, user.getId()); + Optional note = noteRepository.findByIdAndUserId(noteId, user.getId()); if (note.isEmpty()) { throw new NoteNotFoundException(); } - NoteEntity noteEntity = note.get(); - if (!noteEntity.isArchived()) { - throw new NoteArchivedException(); + Note noteEntity = note.get(); + if (!noteEntity.archived()) { + throw new NoteNotArchivedException(); } - noteUrlRepository.deleteByNote_id(noteId); + noteUrlRepository.deleteByNoteId(noteId); logger.info("URL deleted from note ID {}", noteId); noteRepository.delete(noteEntity); @@ -258,14 +278,15 @@ public void deleteNote(Long noteId) { */ @Transactional public List searchNotes(String searchTerm) { - UserEntity user = getCurrentUser(); + User user = getCurrentUser(); logger.info("Searching notes for user ID {}", user.getId()); - List notes = - noteRepository.findAllBySearchTerm(searchTerm.toUpperCase(), user.getId()); - logger.info("{} tasks found!", notes.size()); - return getNotesUrl(notes); + List notes = + noteRepository.findAllBySearchTerm(user.getId(), searchTerm.toUpperCase()); + logger.info("{} notes found!", notes.size()); + + return buildNoteResponse(notes, user.getId()); } /** @@ -276,28 +297,44 @@ public List searchNotes(String searchTerm) { */ @Transactional public NoteResponse shareNote(Long noteId) { - UserEntity user = getCurrentUser(); + User user = getCurrentUser(); logger.info("Sharing note ID {} for user ID {}", noteId, user.getId()); - Optional noteOpt = noteRepository.findByIdAndUser_id(noteId, user.getId()); + Optional noteOpt = noteRepository.findByIdAndUserId(noteId, user.getId()); if (noteOpt.isEmpty()) { throw new NoteNotFoundException(); } - NoteEntity noteEntity = noteOpt.get(); + Note noteEntity = noteOpt.get(); - if (noteEntity.isArchived()) { + if (noteEntity.archived()) { throw new NoteArchivedException(); } - if (!noteEntity.isShared()) { - noteEntity.setShared(true); - noteEntity.setShareToken(UUID.randomUUID().toString()); - noteRepository.save(noteEntity); - logger.info("Note ID {} shared with token {}", noteId, noteEntity.getShareToken()); + if (Boolean.TRUE.equals(noteEntity.shared())) { + // throw new NoteAlreadySharedException + throw new RuntimeException("Note " + noteId + " already shared!"); } - return NoteResponse.fromEntity(noteEntity, getNoteUrl(noteEntity.getId())); + Note noteToShare = new Note( + noteId, + user.getId(), + noteEntity.description(), + noteEntity.title(), + LocalDateTime.now(), + Boolean.TRUE, + UUID.randomUUID().toString(), + Boolean.FALSE + ); + Note sharedNote = noteRepository.save(noteToShare); + logger.info("Note ID {} shared with token {}", noteId, noteToShare.shareToken()); + + List responseList = buildNoteResponse(List.of(sharedNote), user.getId()); + if (responseList.isEmpty()) { + throw new RuntimeException("Issues during NoteResponse build in shareNote"); + } + + return responseList.getFirst(); } /** @@ -308,26 +345,40 @@ public NoteResponse shareNote(Long noteId) { */ @Transactional public NoteResponse unshareNote(Long noteId) { - UserEntity user = getCurrentUser(); + User user = getCurrentUser(); logger.info("Unsharing note ID {} for user ID {}", noteId, user.getId()); - Optional noteOpt = noteRepository.findByIdAndUser_id(noteId, user.getId()); + Optional noteOpt = noteRepository.findByIdAndUserId(noteId, user.getId()); if (noteOpt.isEmpty()) { throw new NoteNotFoundException(); } - NoteEntity noteEntity = noteOpt.get(); + Note noteEntity = noteOpt.get(); - if (noteEntity.isArchived()) { + if (noteEntity.archived()) { throw new NoteArchivedException(); } - noteEntity.setShared(false); - noteEntity.setShareToken(null); - noteRepository.save(noteEntity); + Note noteToUnShare = new Note( + noteId, + user.getId(), + noteEntity.description(), + noteEntity.title(), + LocalDateTime.now(), + Boolean.FALSE, + null, + Boolean.FALSE + ); + + Note unsharedNote = noteRepository.save(noteToUnShare); logger.info("Note ID {} unshared", noteId); - return NoteResponse.fromEntity(noteEntity, getNoteUrl(noteEntity.getId())); + List responseList = buildNoteResponse(List.of(unsharedNote), user.getId()); + if (responseList.isEmpty()) { + throw new RuntimeException("Issues during NoteResponse build in unshareNote"); + } + + return responseList.getFirst(); } /** @@ -340,15 +391,21 @@ public NoteResponse unshareNote(Long noteId) { public NoteResponse getSharedNote(String shareToken) { logger.info("Fetching shared note with token {}", shareToken); - Optional noteOpt = noteRepository.findByShareToken(shareToken); - if (noteOpt.isEmpty() || !noteOpt.get().isShared() || noteOpt.get().isArchived()) { + Optional noteOpt = noteRepository.findByShareToken(shareToken); + if (noteOpt.isEmpty() || !noteOpt.get().shared() || noteOpt.get().archived()) { throw new NoteNotFoundException(); } - return NoteResponse.fromEntity(noteOpt.get(), getNoteUrl(noteOpt.get().getId())); + List responseList = buildNoteResponse(List.of(noteOpt.get()), + noteOpt.get().userId()); + if (responseList.isEmpty()) { + throw new RuntimeException("Issues during NoteResponse build in shareNote"); + } + + return responseList.getFirst(); } - private Set getOrCreateTags(List tagNames, UserEntity user) { + private Set getOrCreateTags(List tagNames, User user, Long noteId) { if (Objects.isNull(tagNames) || tagNames.isEmpty()) { return new HashSet<>(); } @@ -359,37 +416,35 @@ private Set getOrCreateTags(List tagNames, UserEntity user) { .map(name -> name.trim().toLowerCase()) .collect(Collectors.toSet()); - Set tags = new HashSet<>(); + logger.info("Handling {} tags: {}", normalizedNames.size(), normalizedNames); + + Set tags = new HashSet<>(); for (String name : normalizedNames) { - TagEntity tag = - tagRepository - .findByNameAndUser_id(name, user.getId()) - .orElseGet(() -> tagRepository.save(new TagEntity(name, user))); - tags.add(tag); + Optional tagOp = tagRepository.findByUserIdAndName(user.getId(), name); + + if (tagOp.isEmpty()) { + Tag newTag = tagRepository.save(new Tag(null, name, user.getId()), "notes", noteId); + tags.add(newTag); + logger.info("Saved new tag {} for note {}", name, noteId); + } else { + tagRepository.updateTagForNote(tagOp.get(), noteId); + tags.add(tagOp.get()); + logger.info("Kept existing tag {} for note {}", name, noteId); + } } + return tags; } - private UserEntity getCurrentUser() { + private User getCurrentUser() { Optional currentUserEmail = authUtil.getCurrentUserEmail(); String email = currentUserEmail.orElseThrow(); return authService.findByEmail(email).orElseThrow(); } private String getNoteUrl(Long noteId) { - return noteUrlRepository.findByNote_id(noteId).map(NoteUrlEntity::getUrl).orElse(null); - } - - private List getNotesUrl(List notes) { - List noteIds = notes.stream().map(NoteEntity::getId).toList(); - if (noteIds.isEmpty()) { - return notes.stream().map(n -> NoteResponse.fromEntity(n, null)).toList(); - } - List urls = noteUrlRepository.findAllByNote_idIn(noteIds); - Map noteUrls = - urls.stream().collect(Collectors.toMap(nu -> nu.getNote().getId(), NoteUrlEntity::getUrl)); - - return notes.stream().map(n -> NoteResponse.fromEntity(n, noteUrls.get(n.getId()))).toList(); + Optional noteUrl = noteUrlRepository.findByNoteId(noteId); + return noteUrl.isPresent() ? noteUrl.get().url() : null; } /** @@ -400,27 +455,39 @@ private List getNotesUrl(List notes) { */ @Transactional public NoteResponse archiveNote(Long noteId) { - UserEntity user = getCurrentUser(); + User user = getCurrentUser(); logger.info("Archiving note ID {} for user ID {}", noteId, user.getId()); - Optional noteOpt = noteRepository.findByIdAndUser_id(noteId, user.getId()); + Optional noteOpt = noteRepository.findByIdAndUserId(noteId, user.getId()); if (noteOpt.isEmpty()) { throw new NoteNotFoundException(); } - NoteEntity noteEntity = noteOpt.get(); - if (noteEntity.isArchived()) { + Note noteEntity = noteOpt.get(); + if (noteEntity.archived()) { throw new NoteArchivedException(); } - noteEntity.setArchived(true); - noteEntity.setShared(false); - noteEntity.setShareToken(null); - noteEntity.setLastUpdate(LocalDateTime.now()); - noteRepository.save(noteEntity); + Note noteToArchive = new Note( + noteId, + user.getId(), + noteEntity.description(), + noteEntity.title(), + LocalDateTime.now(), + Boolean.FALSE, + null, + Boolean.TRUE + ); + + Note archivedNote = noteRepository.save(noteToArchive); logger.info("Note ID {} archived", noteId); - return NoteResponse.fromEntity(noteEntity, getNoteUrl(noteEntity.getId())); + List responseList = buildNoteResponse(List.of(archivedNote), user.getId()); + if (responseList.isEmpty()) { + throw new RuntimeException("Issues during NoteResponse build in archiveNote"); + } + + return responseList.getFirst(); } /** @@ -431,35 +498,162 @@ public NoteResponse archiveNote(Long noteId) { */ @Transactional public NoteResponse restoreNote(Long noteId) { - UserEntity user = getCurrentUser(); + User user = getCurrentUser(); logger.info("Restoring note ID {} for user ID {}", noteId, user.getId()); - Optional noteOpt = noteRepository.findByIdAndUser_id(noteId, user.getId()); + Optional noteOpt = noteRepository.findByIdAndUserId(noteId, user.getId()); if (noteOpt.isEmpty()) { throw new NoteNotFoundException(); } - NoteEntity noteEntity = noteOpt.get(); - if (!noteEntity.isArchived()) { + Note noteEntity = noteOpt.get(); + if (!noteEntity.archived()) { throw new NoteArchivedException(); } - noteEntity.setArchived(false); - noteEntity.setLastUpdate(LocalDateTime.now()); - noteRepository.save(noteEntity); + Note noteToRestore = new Note( + noteId, + user.getId(), + noteEntity.description(), + noteEntity.title(), + LocalDateTime.now(), + Boolean.FALSE, + null, + Boolean.FALSE + ); + + Note restoredNote = noteRepository.save(noteToRestore); logger.info("Note ID {} restored", noteId); - return NoteResponse.fromEntity(noteEntity, getNoteUrl(noteEntity.getId())); + List responseList = buildNoteResponse(List.of(restoredNote), user.getId()); + if (responseList.isEmpty()) { + throw new RuntimeException("Issues during NoteResponse build in restoreNote"); + } + + return responseList.getFirst(); } - private NoteUrlEntity saveUrl(NoteEntity noteEntity, String url) { - NoteUrlEntity noteUrl = new NoteUrlEntity(); - noteUrl.setUrl(url); - noteUrl.setNote(noteEntity); + private void patchNoteTags(Note note, NotePatchRequest patch, User user) { + if (!Objects.isNull(patch.tags()) && !patch.tags().isEmpty()) { + getOrCreateTags(patch.tags(), user, note.id()); + } + + List noteTags = tagRepository + .findAllByUserIdAndNoteIdInList(user.getId(), List.of(note.id())); - NoteUrlEntity savedUrl = noteUrlRepository.save(noteUrl); - logger.info("URL saved to note ID {}", noteEntity.getId()); + logger.info("Found {} tags for note {}", noteTags.size(), note.id()); + + List toDelete = new ArrayList<>(); + + for (TaskNoteTag tnt : noteTags) { + if (!Objects.isNull(patch.tags()) && !patch.tags().contains(tnt.name())) { + logger.info("Flagged tag {} to be deleted for note {}", tnt, note.id()); + toDelete.add(tnt); + } + } + + List tagsIds = toDelete.stream().map((t) -> t.tagId()).toList(); + + if (!tagsIds.isEmpty()) { + int deleted = tagRepository.deleteTagFromNote(tagsIds, note.id()); + logger.info("Deleted {} tags from note id {}", deleted, note.id()); + } + + int deletedOrphan = tagRepository.deleteOrphanedTags(user.getId()); + logger.info("Deleted {} orphaned tags from note id {}", deletedOrphan, note.id()); + } + + private void patchNoteUrl(Note note, NotePatchRequest patch, User user) { + if (!Objects.isNull(patch.url()) && !patch.url().isBlank()) { + getOrCreateUrls(List.of(patch.url()), user, note.id()); + } + + List noteUrls = noteUrlRepository.findAllByNoteIdList(List.of(note.id())); + + logger.info("Found {} urls for note {}", noteUrls.size(), note.id()); + + int deletedCount = 0; + + for (NoteUrl nu : noteUrls) { + if (!Objects.isNull(patch.url()) && !patch.url().contains(nu.url())) { + noteUrlRepository.deleteByNoteId(note.id()); + deletedCount++; + } + } + + if (deletedCount > 0) { + logger.info("Deleted {} url from note id {}", deletedCount, note.id()); + } + + int deletedOrphan = tagRepository.deleteOrphanedTags(user.getId()); + logger.info("Deleted {} orphaned urls from note id {}", deletedOrphan, note.id()); + } + + private Set getOrCreateUrls(List urls, User user, Long noteId) { + if (Objects.isNull(urls) || urls.isEmpty()) { + return new HashSet<>(); + } + + Set normalizedUrls = + urls.stream() + .filter(name -> !Objects.isNull(name) && !name.isBlank()) + .map(name -> name.trim().toLowerCase()) + .collect(Collectors.toSet()); + + logger.info("Handling {} urls: {}", normalizedUrls.size(), normalizedUrls); + + List currentNoteUrls = noteUrlRepository.findAllByNoteIdList(List.of(noteId)); + + logger.info("Found {} urls: {} for note {}", currentNoteUrls.size(), currentNoteUrls, noteId); + + Set noteUrls = new HashSet<>(); + + for (String url : normalizedUrls) { + Optional noteUrlOp = currentNoteUrls + .stream() + .filter((tu) -> tu.url().equals(url)) + .findFirst(); + + if (noteUrlOp.isEmpty()) { + NoteUrl newNotekUrl = new NoteUrl(null, noteId, url); + NoteUrl added = noteUrlRepository.save(newNotekUrl); + noteUrls.add(added); + } else { + noteUrls.add(noteUrlOp.get()); + } + } + + return noteUrls; + } + + private List buildNoteResponse(List noteList, Long userId) { + if (noteList.isEmpty()) { + return List.of(); + } + + List allNotesIds = noteList.stream().map((t) -> t.id()).toList(); + List notesTags = tagRepository.findAllByUserIdAndNoteIdInList(userId, allNotesIds); + Map> tagMap = new HashMap<>(); + for (TaskNoteTag noteTag : notesTags) { + tagMap.putIfAbsent(noteTag.taskNoteId(), new ArrayList<>()); + tagMap.get(noteTag.taskNoteId()) + .add(new Tag(noteTag.tagId(), noteTag.name(), noteTag.userId())); + } + + List responseList = new ArrayList<>(); + for (Note n : noteList) { + List tagsFromMap = tagMap.getOrDefault(n.id(), new ArrayList<>()); + NoteResponse tr = NoteResponse.fromEntity(n, getNoteUrl(n.id()), tagsFromMap); + responseList.add(tr); + } + + return responseList; + } + private NoteUrl saveUrl(Note noteEntity, String url) { + NoteUrl noteUrl = new NoteUrl(null, noteEntity.id(), url); + NoteUrl savedUrl = noteUrlRepository.save(noteUrl); + logger.info("URL saved to note ID {}", noteEntity.id()); return savedUrl; } } diff --git a/server/src/main/java/br/com/tasknoteapp/server/service/TaskService.java b/server/src/main/java/br/com/tasknoteapp/server/service/TaskService.java index 136c8dbf..6e32d597 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/service/TaskService.java +++ b/server/src/main/java/br/com/tasknoteapp/server/service/TaskService.java @@ -1,10 +1,11 @@ package br.com.tasknoteapp.server.service; -import br.com.tasknoteapp.server.entity.TagEntity; -import br.com.tasknoteapp.server.entity.TaskEntity; -import br.com.tasknoteapp.server.entity.TaskUrlEntity; -import br.com.tasknoteapp.server.entity.TaskUrlEntityPk; -import br.com.tasknoteapp.server.entity.UserEntity; +import br.com.tasknoteapp.server.entity.Tag; +import br.com.tasknoteapp.server.entity.Task; +import br.com.tasknoteapp.server.entity.TaskNoteTag; +import br.com.tasknoteapp.server.entity.TaskUrl; +import br.com.tasknoteapp.server.entity.TaskUrlPk; +import br.com.tasknoteapp.server.entity.User; import br.com.tasknoteapp.server.exception.TaskNotFoundException; import br.com.tasknoteapp.server.repository.TagRepository; import br.com.tasknoteapp.server.repository.TaskRepository; @@ -13,13 +14,14 @@ import br.com.tasknoteapp.server.request.TaskRequest; import br.com.tasknoteapp.server.response.TaskResponse; import br.com.tasknoteapp.server.util.AuthUtil; -import jakarta.transaction.Transactional; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeParseException; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -27,6 +29,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; /** This class contains the implementation for the Task Service class. */ @Service @@ -71,17 +74,15 @@ public TaskService( * * @return {@link List} of {@link TaskResponse} with all Tasks found or an empty list. */ - @Transactional + @Transactional public List getAllTasks() { - UserEntity user = getCurrentUser(); + User user = getCurrentUser(); logger.info("Get all tasks to user ID {}", user.getId()); - List tasks = taskRepository.findAllByUser_id(user.getId()); + List tasks = taskRepository.findAllByUserId(user.getId()); logger.info("{} tasks found in getAllTasks!", tasks.size()); - return tasks.stream() - .map((TaskEntity tr) -> TaskResponse.fromEntity(tr, getAllTasksUrls(tr.getId()))) - .toList(); + return buildTaskResponse(tasks, user.getId()); } /** @@ -92,16 +93,22 @@ public List getAllTasks() { */ @Transactional public TaskResponse getTaskById(Long taskId) { - UserEntity user = getCurrentUser(); + User user = getCurrentUser(); logger.info("Get task ID {} to user ID {}", taskId, user.getId()); - Optional task = taskRepository.findByIdAndUser_id(taskId, user.getId()); + Optional task = taskRepository.findByIdAndUserId(taskId, user.getId()); if (task.isEmpty()) { throw new TaskNotFoundException(); } logger.info("Task found! ID {}", taskId); - return TaskResponse.fromEntity(task.get(), getAllTasksUrls(taskId)); + + List responseList = buildTaskResponse(List.of(task.get()), user.getId()); + if (responseList.isEmpty()) { + throw new RuntimeException("Issues during TaskResponse build in getTaskById"); + } + + return responseList.getFirst(); } /** @@ -111,80 +118,110 @@ public TaskResponse getTaskById(Long taskId) { */ @Transactional public TaskResponse createTask(TaskRequest taskRequest) { - UserEntity user = getCurrentUser(); + User user = getCurrentUser(); logger.info("Creating task to user ID {}", user.getId()); - TaskEntity task = new TaskEntity(); - task.setDescription(taskRequest.description()); - task.setCompleted(false); - task.setUser(user); - task.setLastUpdate(LocalDateTime.now()); + LocalDate dueDate = null; if (!Objects.isNull(taskRequest.dueDate()) && !taskRequest.dueDate().isBlank()) { - task.setDueDate(LocalDate.parse(taskRequest.dueDate())); + dueDate = LocalDate.parse(taskRequest.dueDate()); } - task.setHighPriority(taskRequest.highPriority()); + + Task task = new Task( + null, + user.getId(), + taskRequest.description(), + Boolean.FALSE, + LocalDateTime.now(), + dueDate, + Boolean.FALSE, + Boolean.FALSE, + taskRequest.highPriority() + ); + + Task created = taskRepository.save(task); + + logger.info("Task created! ID {}", created.id()); + if (!Objects.isNull(taskRequest.tags())) { - task.setTags(getOrCreateTags(taskRequest.tags(), user)); + getOrCreateTags(taskRequest.tags(), user, created.id()); } - TaskEntity created = taskRepository.save(task); if (!Objects.isNull(taskRequest.urls()) && !taskRequest.urls().isEmpty()) { - saveUrls(task, taskRequest.urls()); + saveUrls(created, taskRequest.urls()); } - tagRepository.deleteOrphanedTags(user.getId()); + int deleted = tagRepository.deleteOrphanedTags(user.getId()); + logger.info("Deleted {} tags for user", deleted); + + List responseList = buildTaskResponse(List.of(created), user.getId()); + if (responseList.isEmpty()) { + throw new RuntimeException("Issues during TaskResponse build in createTask"); + } - logger.info("Task created! ID {}", created.getId()); - return TaskResponse.fromEntity(created, getAllTasksUrls(created.getId())); + return responseList.getFirst(); } /** * Patch a task for the current user. * * @param taskId The task id from the database. - * @param patch An instance of {@link TaskPatchRequest} with the content to be patched. + * @param patchRequest An instance of {@link TaskPatchRequest} with the content to be patched. * @return {@link TaskResponse} with the updated content. */ @Transactional - public TaskResponse patchTask(Long taskId, TaskPatchRequest patch) { - UserEntity user = getCurrentUser(); + public TaskResponse patchTask(Long taskId, TaskPatchRequest patchRequest) { + User user = getCurrentUser(); logger.info("Patching task ID {} to user ID {}", taskId, user.getId()); - Optional task = taskRepository.findByIdAndUser_id(taskId, user.getId()); + Optional task = taskRepository.findByIdAndUserId(taskId, user.getId()); if (task.isEmpty()) { throw new TaskNotFoundException(); } - TaskEntity taskEntity = task.get(); - if (!Objects.isNull(patch.description()) && !patch.description().isBlank()) { - taskEntity.setDescription(patch.description().trim()); + Task taskEntity = task.get(); + + var description = taskEntity.description(); + if (!Objects.isNull(patchRequest.description()) && !patchRequest.description().isBlank()) { + description = patchRequest.description().trim(); } - if (!Objects.isNull(patch.completed())) { - taskEntity.setCompleted(patch.completed()); + var completed = taskEntity.completed(); + if (!Objects.isNull(patchRequest.completed())) { + completed = patchRequest.completed(); } - patchDueDate(taskEntity, patch); - - if (!Objects.isNull(patch.highPriority())) { - taskEntity.setHighPriority(patch.highPriority()); - } - if (!Objects.isNull(patch.tags())) { - taskEntity.setTags(getOrCreateTags(patch.tags(), user)); + var highPriority = taskEntity.highPriority(); + if (!Objects.isNull(patchRequest.highPriority())) { + highPriority = patchRequest.highPriority(); } - taskEntity.setLastUpdate(LocalDateTime.now()); + Task taskToPatch = new Task( + taskId, + user.getId(), + description, + completed, + LocalDateTime.now(), + resolvDueDate(taskEntity, patchRequest), + Boolean.FALSE, + Boolean.FALSE, + highPriority + ); - patchTaskUrl(taskEntity, patch); + Task patchedTask = taskRepository.save(taskToPatch); - TaskEntity patchedTask = taskRepository.save(taskEntity); + logger.info("Task patched! ID {}", taskId); - tagRepository.deleteOrphanedTags(user.getId()); + patchTaskUrl(patchedTask, patchRequest, user); - logger.info("Task patched! ID {}", patchedTask.getId()); + patchTaskTags(patchedTask, patchRequest, user); - return TaskResponse.fromEntity(patchedTask, getAllTasksUrls(taskId)); + List responseList = buildTaskResponse(List.of(patchedTask), user.getId()); + if (responseList.isEmpty()) { + throw new RuntimeException("Issues during TaskResponse build in patchTask"); + } + + return responseList.getFirst(); } /** @@ -194,18 +231,18 @@ public TaskResponse patchTask(Long taskId, TaskPatchRequest patch) { */ @Transactional public void deleteTask(Long taskId) { - UserEntity user = getCurrentUser(); + User user = getCurrentUser(); logger.info("Deleting task ID {} to user ID {}", taskId, user.getId()); - Optional task = taskRepository.findByIdAndUser_id(taskId, user.getId()); + Optional task = taskRepository.findByIdAndUserId(taskId, user.getId()); if (task.isEmpty()) { throw new TaskNotFoundException(); } - TaskEntity taskEntity = task.get(); + Task taskEntity = task.get(); - List urlsToDelete = taskUrlRepository.findAllById_taskId(taskEntity.getId()); + List urlsToDelete = taskUrlRepository.findAllById_taskId(taskEntity.id()); if (!urlsToDelete.isEmpty()) { taskUrlRepository.deleteAllById_taskId(taskId); logger.info("Deleted {} URLs from task ID {} in deleteTask", urlsToDelete.size(), taskId); @@ -228,7 +265,7 @@ public void deleteTask(Long taskId) { */ @Transactional public List searchTasks(String searchTerm) { - UserEntity user = getCurrentUser(); + User user = getCurrentUser(); logger.info("Searching tasks to user ID {}", user.getId()); @@ -236,13 +273,11 @@ public List searchTasks(String searchTerm) { return List.of(); } - List tasks = + List tasks = taskRepository.findAllBySearchTerm(searchTerm.toUpperCase(), user.getId()); logger.info("{} tasks found!", tasks.size()); - return tasks.stream() - .map((TaskEntity tr) -> TaskResponse.fromEntity(tr, getAllTasksUrls(tr.getId()))) - .toList(); + return buildTaskResponse(tasks, user.getId()); } /** @@ -253,40 +288,61 @@ public List searchTasks(String searchTerm) { */ @Transactional public List getTasksByFilter(String filter) { - UserEntity user = getCurrentUser(); + User user = getCurrentUser(); - List allTasks = - taskRepository.findAllByUser_id(user.getId()).stream() - .filter(t -> t.getCompleted().equals(Boolean.FALSE)) + List allTasks = + taskRepository.findAllByUserId(user.getId()).stream() + .filter(t -> t.completed().equals(Boolean.FALSE)) .toList(); - if (allTasks.isEmpty()) { - return List.of(); - } + + List responseList = buildTaskResponse(allTasks, user.getId()); return switch (filter) { - case "all" -> - allTasks.stream() - .map((TaskEntity tr) -> TaskResponse.fromEntity(tr, getAllTasksUrls(tr.getId()))) - .toList(); + case "all" -> responseList; case "high" -> - allTasks.stream() - .filter(TaskEntity::getHighPriority) - .map((TaskEntity tr) -> TaskResponse.fromEntity(tr, getAllTasksUrls(tr.getId()))) + responseList + .stream() + .filter(((tr) -> tr.highPriority().equals(Boolean.TRUE))) .toList(); case "untagged" -> - allTasks.stream() - .filter(t -> t.getTags().isEmpty()) - .map((TaskEntity tr) -> TaskResponse.fromEntity(tr, getAllTasksUrls(tr.getId()))) + responseList + .stream() + .filter((tr) -> tr.tags().isEmpty()) .toList(); default -> - allTasks.stream() - .filter(t -> t.getTags().stream().anyMatch(tag -> tag.getName().equals(filter))) - .map((TaskEntity tr) -> TaskResponse.fromEntity(tr, getAllTasksUrls(tr.getId()))) + responseList + .stream() + .filter((tr) -> tr.tags().stream().anyMatch((tag) -> tag.equals(filter))) .toList(); }; } - private Set getOrCreateTags(List tagNames, UserEntity user) { + private List buildTaskResponse(List taskList, Long userId) { + if (taskList.isEmpty()) { + return List.of(); + } + + List allTaskIds = taskList.stream().map((t) -> t.id()).toList(); + List taskTags = tagRepository.findAllByUserIdAndTaskIdInList(userId, allTaskIds); + + Map> tagMap = new HashMap<>(); + for (TaskNoteTag taskTag : taskTags) { + tagMap.putIfAbsent(taskTag.taskNoteId(), new ArrayList<>()); + tagMap.get(taskTag.taskNoteId()) + .add(new Tag(taskTag.tagId(), taskTag.name(), taskTag.userId())); + } + + List responseList = new ArrayList<>(); + for (Task t : taskList) { + List tagsFromMap = tagMap.getOrDefault(t.id(), new ArrayList<>()); + TaskResponse tr = TaskResponse.fromEntity(t, getAllTasksUrls(t.id()), tagsFromMap); + responseList.add(tr); + } + + return responseList; + } + + private Set getOrCreateTags(List tagNames, User user, Long taskId) { if (Objects.isNull(tagNames) || tagNames.isEmpty()) { return new HashSet<>(); } @@ -297,68 +353,149 @@ private Set getOrCreateTags(List tagNames, UserEntity user) { .map(name -> name.trim().toLowerCase()) .collect(Collectors.toSet()); - Set tags = new HashSet<>(); + logger.info("Handling {} tags: {}", normalizedNames.size(), normalizedNames); + + Set tags = new HashSet<>(); for (String name : normalizedNames) { - TagEntity tag = - tagRepository - .findByNameAndUser_id(name, user.getId()) - .orElseGet(() -> tagRepository.save(new TagEntity(name, user))); - tags.add(tag); + Optional tagOp = tagRepository.findByUserIdAndName(user.getId(), name); + + if (tagOp.isEmpty()) { + Tag newTag = tagRepository.save(new Tag(null, name, user.getId()), "tasks", taskId); + tags.add(newTag); + } else { + tagRepository.updateTagForTask(tagOp.get(), taskId); + tags.add(tagOp.get()); + } } + return tags; } - private UserEntity getCurrentUser() { + private Set getOrCreateUrls(List urls, User user, Long taskId) { + if (Objects.isNull(urls) || urls.isEmpty()) { + return new HashSet<>(); + } + + Set normalizedUrls = + urls.stream() + .filter(name -> !Objects.isNull(name) && !name.isBlank()) + .map(name -> name.trim().toLowerCase()) + .collect(Collectors.toSet()); + + logger.info("Handling {} urls: {}", normalizedUrls.size(), normalizedUrls); + + List currentTaskUrls = taskUrlRepository.findAllById_taskId(taskId); + + logger.info("Found {} urls: {} for task {}", currentTaskUrls.size(), currentTaskUrls, taskId); + + Set taskUrls = new HashSet<>(); + + List taskUrlsToInsert = new ArrayList<>(); + for (String url : normalizedUrls) { + Optional taskUrlOp = currentTaskUrls + .stream() + .filter((tu) -> tu.id().url().equals(url)) + .findFirst(); + + if (taskUrlOp.isEmpty()) { + TaskUrl newTaskUrl = new TaskUrl(new TaskUrlPk(taskId, url)); + taskUrlsToInsert.add(newTaskUrl); + taskUrls.add(newTaskUrl); + } else { + taskUrls.add(taskUrlOp.get()); + } + } + + return taskUrls; + } + + private User getCurrentUser() { Optional currentUserEmail = authUtil.getCurrentUserEmail(); String email = currentUserEmail.orElseThrow(); return authService.findByEmail(email).orElseThrow(); } private List getAllTasksUrls(Long taskId) { - List urls = taskUrlRepository.findAllById_taskId(taskId); - return urls.stream().map(TaskUrlEntity::getId).map(TaskUrlEntityPk::getUrl).toList(); + List urls = taskUrlRepository.findAllById_taskId(taskId); + return urls.stream().map((tu) -> tu.id()).map((pk) -> pk.url()).toList(); } - private void saveUrls(TaskEntity taskEntity, List urls) { - List tasksUrl = new ArrayList<>(); + private void saveUrls(Task taskEntity, List urls) { + List tasksUrl = new ArrayList<>(); for (String url : urls) { - TaskUrlEntity taskUrl = new TaskUrlEntity(); - TaskUrlEntityPk pk = new TaskUrlEntityPk(taskEntity.getId(), url); - taskUrl.setId(pk); + TaskUrlPk pk = new TaskUrlPk(taskEntity.id(), url); + TaskUrl taskUrl = new TaskUrl(pk); tasksUrl.add(taskUrl); } taskUrlRepository.saveAll(tasksUrl); - logger.info("Added {} URLs from task ID {}", tasksUrl.size(), taskEntity.getId()); + logger.info("Added {} URLs from task ID {}", tasksUrl.size(), taskEntity.id()); } - private void patchDueDate(TaskEntity taskEntity, TaskPatchRequest patch) { - taskEntity.setDueDate(null); + private LocalDate resolvDueDate(Task taskEntity, TaskPatchRequest patch) { + LocalDate dueDate = null; if (!Objects.isNull(patch.dueDate()) && !patch.dueDate().isBlank()) { try { - taskEntity.setDueDate(LocalDate.parse(patch.dueDate())); + dueDate = LocalDate.parse(patch.dueDate()); } catch (DateTimeParseException e) { logger.error("Unable to parse the provided date: {}: {}", patch.dueDate(), e.getMessage()); } } + return dueDate; } - private void patchTaskUrl(TaskEntity taskEntity, TaskPatchRequest patch) { - Long taskId = taskEntity.getId(); - List urlsToDelete = taskUrlRepository.findAllById_taskId(taskId); - if (!urlsToDelete.isEmpty()) { - taskUrlRepository.deleteAllById_taskId(taskId); - logger.info("Deleted {} URLs from task ID {}", urlsToDelete.size(), taskId); - } else { - logger.info("No URLs to delete for task ID {}", taskId); + private void patchTaskTags(Task task, TaskPatchRequest patch, User user) { + if (!Objects.isNull(patch.tags()) && !patch.tags().isEmpty()) { + getOrCreateTags(patch.tags(), user, task.id()); } - if (!Objects.isNull(patch.urls())) { - List urlListToAdd = - patch.urls().stream().filter(u -> !u.isBlank()).map(String::trim).toList(); - saveUrls(taskEntity, urlListToAdd); - } else { - logger.info("No URLs to add for task ID {}", taskId); + List taskTags = tagRepository + .findAllByUserIdAndTaskIdInList(user.getId(), List.of(task.id())); + + logger.info("Found {} tags for task {}", taskTags.size(), task.id()); + + List toDelete = new ArrayList<>(); + + for (TaskNoteTag tnt : taskTags) { + if (!Objects.isNull(patch.tags()) && !patch.tags().contains(tnt.name())) { + toDelete.add(tnt); + } + } + + List tagsIds = toDelete.stream().map((t) -> t.tagId()).toList(); + + if (!tagsIds.isEmpty()) { + int deleted = tagRepository.deleteTagFromTask(tagsIds, task.id()); + logger.info("Deleted {} tags from task id {}", deleted, task.id()); + } + + int deletedOrphan = tagRepository.deleteOrphanedTags(user.getId()); + logger.info("Deleted {} orphaned tags from task id {}", deletedOrphan, task.id()); + } + + private void patchTaskUrl(Task task, TaskPatchRequest patch, User user) { + if (!Objects.isNull(patch.urls()) && !patch.urls().isEmpty()) { + getOrCreateUrls(patch.urls(), user, task.id()); } + + List taskUrls = taskUrlRepository.findAllById_taskId(task.id()); + + logger.info("Found {} urls for task {}", taskUrls.size(), task.id()); + + int deletedCount = 0; + + for (TaskUrl tnt : taskUrls) { + if (!Objects.isNull(patch.urls()) && !patch.urls().contains(tnt.id().url())) { + taskUrlRepository.deleteById(tnt.id()); + deletedCount++; + } + } + + if (deletedCount > 0) { + logger.info("Deleted {} tags from task id {}", deletedCount, task.id()); + } + + int deletedOrphan = tagRepository.deleteOrphanedTags(user.getId()); + logger.info("Deleted {} orphaned tags from task id {}", deletedOrphan, task.id()); } } diff --git a/server/src/main/java/br/com/tasknoteapp/server/service/UserSessionService.java b/server/src/main/java/br/com/tasknoteapp/server/service/UserSessionService.java index cee7b01a..c90d4739 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/service/UserSessionService.java +++ b/server/src/main/java/br/com/tasknoteapp/server/service/UserSessionService.java @@ -1,18 +1,18 @@ package br.com.tasknoteapp.server.service; -import br.com.tasknoteapp.server.entity.UserEntity; +import br.com.tasknoteapp.server.entity.User; import br.com.tasknoteapp.server.exception.UserNotFoundException; import br.com.tasknoteapp.server.repository.TagRepository; import br.com.tasknoteapp.server.response.JwtAuthenticationResponse; import br.com.tasknoteapp.server.response.TaskResponse; import br.com.tasknoteapp.server.response.UserResponse; import br.com.tasknoteapp.server.util.SecurityUtil; -import jakarta.transaction.Transactional; import java.util.List; import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; /** This class contains methods to handle user session and account deletion. */ @Service @@ -67,9 +67,9 @@ public JwtAuthenticationResponse refreshUserSession() { * * @return {@link UserResponse} with user data */ - @Transactional + @Transactional public UserResponse deleteCurrentUserAccount() { - Optional userOptional = authService.getCurrentUser(); + Optional userOptional = authService.getCurrentUser(); if (userOptional.isEmpty()) { throw new UserNotFoundException(); } diff --git a/server/src/main/java/br/com/tasknoteapp/server/service/impl/JwtServiceImpl.java b/server/src/main/java/br/com/tasknoteapp/server/service/impl/JwtServiceImpl.java index 5ee9c604..0237c9a3 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/service/impl/JwtServiceImpl.java +++ b/server/src/main/java/br/com/tasknoteapp/server/service/impl/JwtServiceImpl.java @@ -1,6 +1,6 @@ package br.com.tasknoteapp.server.service.impl; -import br.com.tasknoteapp.server.entity.UserEntity; +import br.com.tasknoteapp.server.entity.User; import br.com.tasknoteapp.server.service.JwtService; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtException; @@ -27,8 +27,6 @@ class JwtServiceImpl implements JwtService { private static final long SECOND = 1000; private static final long MINUTE = SECOND * 60; - private static final long HOUR = MINUTE * 60; - private static final long DAY = HOUR * 24; private static final long EXPIRATION_TIME = MINUTE * 30; private final SecretKey key; @@ -60,7 +58,7 @@ private LocalDateTime extractIssuedAt(String token) { } @Override - public String generateToken(UserEntity user) { + public String generateToken(User user) { Map claims = new HashMap<>(); claims.put("userId", user.getId()); claims.put("email", user.getEmail()); @@ -101,7 +99,7 @@ public boolean validateTokenAndUser(String token, UserDetails user) { final String email = user.getUsername(); boolean basicValid = !isTokenExpired(token) && email.equals(getEmailFromToken(token)); - if (basicValid && user instanceof UserEntity userEntity) { + if (basicValid && user instanceof User userEntity) { LocalDateTime iat = extractIssuedAt(token); if (iat != null && userEntity.getLastPasswordChange() != null) { // Token must be issued after or at the same time as last password change diff --git a/server/src/main/java/br/com/tasknoteapp/server/service/impl/UserServiceImpl.java b/server/src/main/java/br/com/tasknoteapp/server/service/impl/UserServiceImpl.java index df79337a..86882a8e 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/service/impl/UserServiceImpl.java +++ b/server/src/main/java/br/com/tasknoteapp/server/service/impl/UserServiceImpl.java @@ -1,6 +1,6 @@ package br.com.tasknoteapp.server.service.impl; -import br.com.tasknoteapp.server.entity.UserEntity; +import br.com.tasknoteapp.server.entity.User; import br.com.tasknoteapp.server.repository.UserRepository; import br.com.tasknoteapp.server.service.UserService; import br.com.tasknoteapp.server.util.SecurityUtil; @@ -17,7 +17,7 @@ class UserServiceImpl implements UserService { public UserServiceImpl(UserRepository userRepository) { this.cachedUserDetailsService = email -> { - Optional user = userRepository.findByEmail(email); + Optional user = userRepository.findByEmail(email); if (user.isEmpty()) { throw new RuntimeException("User not found: " + SecurityUtil.redactEmail(email)); } diff --git a/server/src/main/java/br/com/tasknoteapp/server/util/TimeAgoUtil.java b/server/src/main/java/br/com/tasknoteapp/server/util/TimeAgoUtil.java index 585fe181..d7ff36d7 100644 --- a/server/src/main/java/br/com/tasknoteapp/server/util/TimeAgoUtil.java +++ b/server/src/main/java/br/com/tasknoteapp/server/util/TimeAgoUtil.java @@ -76,7 +76,6 @@ public static String formatDueDate(LocalDate futureDate) { StringBuilder sb = new StringBuilder(); - // Format should be: yyyy-MM-dd Period period = Period.between(LocalDate.now(), futureDate); if (period.getYears() > 1) { sb.append(String.format("%d years left", period.getYears())); diff --git a/server/src/main/resources/META-INF/native-image/reachability-metadata.json b/server/src/main/resources/META-INF/native-image/reachability-metadata.json new file mode 100644 index 00000000..5e1dfd8c --- /dev/null +++ b/server/src/main/resources/META-INF/native-image/reachability-metadata.json @@ -0,0 +1,9034 @@ +{ + "reflection": [ + { + "type": "boolean" + }, + { + "type": "boolean[]" + }, + { + "type": "br.com.tasknoteapp.server.JavaApiApplication", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "main", + "parameterTypes": [ + "java.lang.String[]" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.config.CloudNativeConfig" + }, + { + "type": "br.com.tasknoteapp.server.config.CloudNativeConfig$$SpringCGLIB$$0", + "fields": [ + { + "name": "$$beanFactory" + }, + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "CGLIB$SET_STATIC_CALLBACKS", + "parameterTypes": [ + "org.springframework.cglib.proxy.Callback[]" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.config.CorsConfig", + "fields": [ + { + "name": "allowedOrigins" + } + ] + }, + { + "type": "br.com.tasknoteapp.server.config.CorsConfig$$SpringCGLIB$$0", + "fields": [ + { + "name": "$$beanFactory" + }, + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "CGLIB$SET_STATIC_CALLBACKS", + "parameterTypes": [ + "org.springframework.cglib.proxy.Callback[]" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.config.SecurityConfig", + "methods": [ + { + "name": "authenticationManager", + "parameterTypes": [ + "org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration" + ] + }, + { + "name": "authenticationProvider", + "parameterTypes": [] + }, + { + "name": "filterChain", + "parameterTypes": [ + "org.springframework.security.config.annotation.web.builders.HttpSecurity" + ] + }, + { + "name": "passwordEncoder", + "parameterTypes": [] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.config.SecurityConfig$$SpringCGLIB$$0", + "fields": [ + { + "name": "$$beanFactory" + }, + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + "br.com.tasknoteapp.server.service.UserService", + "br.com.tasknoteapp.server.filter.JwtAuthenticationFilter" + ] + }, + { + "name": "CGLIB$SET_STATIC_CALLBACKS", + "parameterTypes": [ + "org.springframework.cglib.proxy.Callback[]" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.config.SecurityConfig$$SpringCGLIB$$FastClass$$0", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Class" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.config.SecurityConfig$$SpringCGLIB$$FastClass$$1", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Class" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.controller.AuthenticationController", + "methods": [ + { + "name": "", + "parameterTypes": [ + "br.com.tasknoteapp.server.service.AuthService" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.controller.HealthController", + "methods": [ + { + "name": "", + "parameterTypes": [ + "javax.sql.DataSource", + "br.com.tasknoteapp.server.service.AppVersionService" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.controller.HomeController", + "methods": [ + { + "name": "", + "parameterTypes": [ + "br.com.tasknoteapp.server.service.HomeService" + ] + }, + { + "name": "getTasksTags", + "parameterTypes": [] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.controller.NoteController", + "methods": [ + { + "name": "", + "parameterTypes": [ + "br.com.tasknoteapp.server.service.NoteService" + ] + }, + { + "name": "archiveNote", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "deleteNote", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "getAllNotes", + "parameterTypes": [] + }, + { + "name": "getTaskById", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "patchNote", + "parameterTypes": [ + "java.lang.Long", + "br.com.tasknoteapp.server.request.NotePatchRequest" + ] + }, + { + "name": "restoreNote", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "shareNote", + "parameterTypes": [ + "java.lang.Long" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.controller.PublicNoteController", + "methods": [ + { + "name": "", + "parameterTypes": [ + "br.com.tasknoteapp.server.service.NoteService" + ] + }, + { + "name": "getSharedNote", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.controller.RestExceptionController", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.controller.TaskController", + "methods": [ + { + "name": "", + "parameterTypes": [ + "br.com.tasknoteapp.server.service.TaskService" + ] + }, + { + "name": "deleteTask", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "getAllTasks", + "parameterTypes": [] + }, + { + "name": "getTaskById", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "patchTask", + "parameterTypes": [ + "java.lang.Long", + "br.com.tasknoteapp.server.request.TaskPatchRequest" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.controller.UserController", + "methods": [ + { + "name": "", + "parameterTypes": [ + "br.com.tasknoteapp.server.service.AuthService" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.controller.UserSessionController", + "methods": [ + { + "name": "", + "parameterTypes": [ + "br.com.tasknoteapp.server.service.UserSessionService", + "br.com.tasknoteapp.server.service.AuthService" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.filter.HeaderVersionFilter", + "methods": [ + { + "name": "", + "parameterTypes": [ + "br.com.tasknoteapp.server.service.AppVersionService" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.filter.JwtAuthenticationFilter", + "methods": [ + { + "name": "", + "parameterTypes": [ + "br.com.tasknoteapp.server.service.UserService", + "br.com.tasknoteapp.server.service.JwtService" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.hint.HttpServletRequestRuntimeHint" + }, + { + "type": "br.com.tasknoteapp.server.hint.JjwtRuntimeHints" + }, + { + "type": "br.com.tasknoteapp.server.hint.JjwtRuntimeHints$$SpringCGLIB$$0", + "fields": [ + { + "name": "$$beanFactory" + }, + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "CGLIB$SET_STATIC_CALLBACKS", + "parameterTypes": [ + "org.springframework.cglib.proxy.Callback[]" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.hint.JjwtRuntimeHints$JjwtHintsRegistrar" + }, + { + "type": "br.com.tasknoteapp.server.repository.NoteRepository", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" + ] + }, + { + "name": "delete", + "parameterTypes": [ + "br.com.tasknoteapp.server.entity.Note" + ] + }, + { + "name": "findAllByUserId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "findByIdAndUserId", + "parameterTypes": [ + "java.lang.Long", + "java.lang.Long" + ] + }, + { + "name": "findByShareToken", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "save", + "parameterTypes": [ + "br.com.tasknoteapp.server.entity.Note" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.repository.NoteRepository$$SpringCGLIB$$0", + "fields": [ + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ] + }, + { + "type": "br.com.tasknoteapp.server.repository.NoteUrlRepository", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" + ] + }, + { + "name": "deleteByNoteId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "findAllByNoteIdList", + "parameterTypes": [ + "java.util.List" + ] + }, + { + "name": "findByNoteId", + "parameterTypes": [ + "java.lang.Long" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.repository.NoteUrlRepository$$SpringCGLIB$$0", + "fields": [ + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ] + }, + { + "type": "br.com.tasknoteapp.server.repository.TagRepository", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" + ] + }, + { + "name": "deleteOrphanedTags", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "deleteTagFromNote", + "parameterTypes": [ + "java.util.List", + "java.lang.Long" + ] + }, + { + "name": "findAllByUserIdAndNoteIdInList", + "parameterTypes": [ + "java.lang.Long", + "java.util.List" + ] + }, + { + "name": "findAllByUserIdAndTaskIdInList", + "parameterTypes": [ + "java.lang.Long", + "java.util.List" + ] + }, + { + "name": "findByUserIdAndName", + "parameterTypes": [ + "java.lang.Long", + "java.lang.String" + ] + }, + { + "name": "updateTagForNote", + "parameterTypes": [ + "br.com.tasknoteapp.server.entity.Tag", + "java.lang.Long" + ] + }, + { + "name": "updateTagForTask", + "parameterTypes": [ + "br.com.tasknoteapp.server.entity.Tag", + "java.lang.Long" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.repository.TagRepository$$SpringCGLIB$$0", + "fields": [ + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ] + }, + { + "type": "br.com.tasknoteapp.server.repository.TaskRepository", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" + ] + }, + { + "name": "delete", + "parameterTypes": [ + "br.com.tasknoteapp.server.entity.Task" + ] + }, + { + "name": "findAllByUserId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "findByIdAndUserId", + "parameterTypes": [ + "java.lang.Long", + "java.lang.Long" + ] + }, + { + "name": "save", + "parameterTypes": [ + "br.com.tasknoteapp.server.entity.Task" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.repository.TaskRepository$$SpringCGLIB$$0", + "fields": [ + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ] + }, + { + "type": "br.com.tasknoteapp.server.repository.TaskUrlRepository", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" + ] + }, + { + "name": "deleteAllById_taskId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "findAllById_taskId", + "parameterTypes": [ + "java.lang.Long" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.repository.TaskUrlRepository$$SpringCGLIB$$0", + "fields": [ + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ] + }, + { + "type": "br.com.tasknoteapp.server.repository.UserPwdLimitRepository", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.repository.UserPwdLimitRepository$$SpringCGLIB$$0", + "fields": [ + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ] + }, + { + "type": "br.com.tasknoteapp.server.repository.UserRepository", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" + ] + }, + { + "name": "findByEmail", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.repository.UserRepository$$SpringCGLIB$$0", + "fields": [ + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ] + }, + { + "type": "br.com.tasknoteapp.server.request.NotePatchRequest", + "fields": [ + { + "name": "description" + }, + { + "name": "title" + }, + { + "name": "url" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.String", + "java.lang.String", + "java.lang.String", + "java.util.List" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.request.TaskPatchRequest", + "fields": [ + { + "name": "description" + }, + { + "name": "urls" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.String", + "java.util.List", + "java.lang.String", + "java.lang.Boolean", + "java.util.List" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.response.NoteResponse", + "methods": [ + { + "name": "archived", + "parameterTypes": [] + }, + { + "name": "description", + "parameterTypes": [] + }, + { + "name": "id", + "parameterTypes": [] + }, + { + "name": "lastUpdate", + "parameterTypes": [] + }, + { + "name": "shareToken", + "parameterTypes": [] + }, + { + "name": "shared", + "parameterTypes": [] + }, + { + "name": "tags", + "parameterTypes": [] + }, + { + "name": "title", + "parameterTypes": [] + }, + { + "name": "url", + "parameterTypes": [] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.response.TaskResponse", + "methods": [ + { + "name": "completed", + "parameterTypes": [] + }, + { + "name": "description", + "parameterTypes": [] + }, + { + "name": "dueDate", + "parameterTypes": [] + }, + { + "name": "dueDateFmt", + "parameterTypes": [] + }, + { + "name": "highPriority", + "parameterTypes": [] + }, + { + "name": "id", + "parameterTypes": [] + }, + { + "name": "lastUpdate", + "parameterTypes": [] + }, + { + "name": "tags", + "parameterTypes": [] + }, + { + "name": "urls", + "parameterTypes": [] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.service.AppVersionService", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.info.BuildProperties" + ] + }, + { + "name": "onApplicationReady", + "parameterTypes": [] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.service.AuthService", + "methods": [ + { + "name": "", + "parameterTypes": [ + "br.com.tasknoteapp.server.repository.UserRepository", + "org.springframework.security.crypto.password.PasswordEncoder", + "br.com.tasknoteapp.server.service.JwtService", + "org.springframework.security.authentication.AuthenticationManager", + "br.com.tasknoteapp.server.util.AuthUtil", + "br.com.tasknoteapp.server.repository.UserPwdLimitRepository", + "br.com.tasknoteapp.server.service.MailgunEmailService", + "org.springframework.core.env.Environment" + ] + }, + { + "name": "findByEmail", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.service.AuthService$$SpringCGLIB$$0", + "fields": [ + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ] + }, + { + "type": "br.com.tasknoteapp.server.service.HomeService", + "methods": [ + { + "name": "", + "parameterTypes": [ + "br.com.tasknoteapp.server.service.TaskService", + "br.com.tasknoteapp.server.service.NoteService", + "br.com.tasknoteapp.server.repository.TagRepository", + "br.com.tasknoteapp.server.service.AuthService", + "br.com.tasknoteapp.server.util.AuthUtil" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.service.JwtService" + }, + { + "type": "br.com.tasknoteapp.server.service.MailgunEmailService", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.String", + "java.lang.String", + "java.lang.String", + "java.lang.String", + "org.springframework.web.client.RestClient$Builder" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.service.NoteService", + "methods": [ + { + "name": "", + "parameterTypes": [ + "br.com.tasknoteapp.server.repository.NoteRepository", + "br.com.tasknoteapp.server.service.AuthService", + "br.com.tasknoteapp.server.util.AuthUtil", + "br.com.tasknoteapp.server.repository.NoteUrlRepository", + "br.com.tasknoteapp.server.repository.TagRepository" + ] + }, + { + "name": "archiveNote", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "deleteNote", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "getAllNotes", + "parameterTypes": [] + }, + { + "name": "getNoteById", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "getSharedNote", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "patchNote", + "parameterTypes": [ + "java.lang.Long", + "br.com.tasknoteapp.server.request.NotePatchRequest" + ] + }, + { + "name": "restoreNote", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "shareNote", + "parameterTypes": [ + "java.lang.Long" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.service.NoteService$$SpringCGLIB$$0", + "fields": [ + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ] + }, + { + "type": "br.com.tasknoteapp.server.service.TaskService", + "methods": [ + { + "name": "", + "parameterTypes": [ + "br.com.tasknoteapp.server.repository.TaskRepository", + "br.com.tasknoteapp.server.service.AuthService", + "br.com.tasknoteapp.server.util.AuthUtil", + "br.com.tasknoteapp.server.repository.TaskUrlRepository", + "br.com.tasknoteapp.server.repository.TagRepository" + ] + }, + { + "name": "deleteTask", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "getAllTasks", + "parameterTypes": [] + }, + { + "name": "getTaskById", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "getTasksByFilter", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "patchTask", + "parameterTypes": [ + "java.lang.Long", + "br.com.tasknoteapp.server.request.TaskPatchRequest" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.service.TaskService$$SpringCGLIB$$0", + "fields": [ + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ] + }, + { + "type": "br.com.tasknoteapp.server.service.UserService" + }, + { + "type": "br.com.tasknoteapp.server.service.UserSessionService", + "methods": [ + { + "name": "", + "parameterTypes": [ + "br.com.tasknoteapp.server.service.AuthService", + "br.com.tasknoteapp.server.service.TaskService", + "br.com.tasknoteapp.server.service.NoteService", + "br.com.tasknoteapp.server.repository.TagRepository" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.service.UserSessionService$$SpringCGLIB$$0", + "fields": [ + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ] + }, + { + "type": "br.com.tasknoteapp.server.service.impl.JwtServiceImpl", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.service.impl.UserServiceImpl", + "methods": [ + { + "name": "", + "parameterTypes": [ + "br.com.tasknoteapp.server.repository.UserRepository" + ] + } + ] + }, + { + "type": "br.com.tasknoteapp.server.util.AuthUtil", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "ch.qos.logback.classic.BasicConfigurator", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "ch.qos.logback.classic.LoggerContext" + }, + { + "type": "ch.qos.logback.classic.spi.LogbackServiceProvider" + }, + { + "type": "ch.qos.logback.classic.util.DefaultJoranConfigurator", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.fasterxml.jackson.databind.ObjectMapper" + }, + { + "type": "com.fasterxml.jackson.dataformat.cbor.CBORFactory" + }, + { + "type": "com.fasterxml.jackson.dataformat.smile.SmileFactory" + }, + { + "type": "com.fasterxml.jackson.dataformat.xml.XmlMapper" + }, + { + "type": "com.fasterxml.jackson.dataformat.yaml.YAMLFactory" + }, + { + "type": "com.google.gson.Gson" + }, + { + "type": "com.rometools.rome.feed.WireFeed" + }, + { + "type": "com.sun.crypto.provider.AESCipher$General", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.ARCFOURCipher", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.ChaCha20Cipher$ChaCha20Poly1305", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.DESCipher", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.DESedeCipher", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.DHParameters", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.GaloisCounterMode$AESGCM", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.HmacCore$HmacSHA256", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.PBKDF2Core$HmacSHA1", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.PBKDF2Core$HmacSHA224", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.PBKDF2Core$HmacSHA256", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.PBKDF2Core$HmacSHA384", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.PBKDF2Core$HmacSHA512", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.TlsMasterSecretGenerator", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.zaxxer.hikari.HikariConfig" + }, + { + "type": "com.zaxxer.hikari.HikariConfigMXBean" + }, + { + "type": "com.zaxxer.hikari.HikariDataSource", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.zaxxer.hikari.pool.PoolBase" + }, + { + "type": "com.zaxxer.hikari.pool.PoolEntry" + }, + { + "type": "groovy.lang.MetaClass" + }, + { + "type": "int[]" + }, + { + "type": "io.jsonwebtoken.Claims" + }, + { + "type": "io.jsonwebtoken.Jwts" + }, + { + "type": "io.jsonwebtoken.Jwts$ENC" + }, + { + "type": "io.jsonwebtoken.Jwts$KEY" + }, + { + "type": "io.jsonwebtoken.Jwts$SIG" + }, + { + "type": "io.jsonwebtoken.gson.io.GsonDeserializer" + }, + { + "type": "io.jsonwebtoken.impl.DefaultClaimsBuilder", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "io.jsonwebtoken.impl.DefaultJwtBuilder" + }, + { + "type": "io.jsonwebtoken.impl.DefaultJwtParserBuilder", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "io.jsonwebtoken.impl.io.StandardCompressionAlgorithms", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "io.jsonwebtoken.impl.security.KeysBridge" + }, + { + "type": "io.jsonwebtoken.impl.security.StandardEncryptionAlgorithms", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "io.jsonwebtoken.impl.security.StandardKeyAlgorithms", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "io.jsonwebtoken.impl.security.StandardKeyOperations", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "io.jsonwebtoken.impl.security.StandardSecureDigestAlgorithms", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "io.jsonwebtoken.lang.Supplier" + }, + { + "type": "io.micrometer.context.ContextSnapshot" + }, + { + "type": "io.micrometer.context.ContextSnapshotFactory" + }, + { + "type": "io.micrometer.core.instrument.MeterRegistry" + }, + { + "type": "io.micrometer.core.instrument.binder.tomcat.TomcatMetrics" + }, + { + "type": "io.micrometer.observation.Observation" + }, + { + "type": "io.micrometer.observation.ObservationRegistry" + }, + { + "type": "io.r2dbc.spi.ConnectionFactory" + }, + { + "type": "io.reactivex.rxjava3.core.Flowable" + }, + { + "type": "io.smallrye.mutiny.Multi" + }, + { + "type": "io.vavr.control.Try" + }, + { + "type": "jakarta.annotation.PostConstruct" + }, + { + "type": "jakarta.annotation.PreDestroy" + }, + { + "type": "jakarta.annotation.Resource" + }, + { + "type": "jakarta.ejb.EJB" + }, + { + "type": "jakarta.ejb.TransactionAttribute" + }, + { + "type": "jakarta.faces.context.FacesContext" + }, + { + "type": "jakarta.inject.Inject" + }, + { + "type": "jakarta.inject.Named" + }, + { + "type": "jakarta.inject.Provider" + }, + { + "type": "jakarta.inject.Qualifier" + }, + { + "type": "jakarta.json.bind.Jsonb" + }, + { + "type": "jakarta.persistence.EntityManagerFactory" + }, + { + "type": "jakarta.persistence.Persistence" + }, + { + "type": "jakarta.persistence.PersistenceContext" + }, + { + "type": "jakarta.servlet.Filter" + }, + { + "type": "jakarta.servlet.GenericFilter" + }, + { + "type": "jakarta.servlet.GenericServlet" + }, + { + "type": "jakarta.servlet.MultipartConfigElement" + }, + { + "type": "jakarta.servlet.Servlet" + }, + { + "type": "jakarta.servlet.ServletConfig" + }, + { + "type": "jakarta.servlet.ServletContext" + }, + { + "type": "jakarta.servlet.ServletRegistration" + }, + { + "type": "jakarta.servlet.ServletRequest" + }, + { + "type": "jakarta.servlet.http.HttpServlet" + }, + { + "type": "jakarta.servlet.jsp.jstl.core.Config" + }, + { + "type": "jakarta.transaction.Transaction" + }, + { + "type": "jakarta.transaction.TransactionManager" + }, + { + "type": "jakarta.transaction.Transactional" + }, + { + "type": "jakarta.validation.Constraint" + }, + { + "type": "jakarta.validation.ConstraintValidator" + }, + { + "type": "jakarta.validation.Valid" + }, + { + "type": "jakarta.validation.Validator" + }, + { + "type": "jakarta.validation.ValidatorFactory" + }, + { + "type": "jakarta.validation.bootstrap.GenericBootstrap" + }, + { + "type": "jakarta.validation.constraints.NotBlank" + }, + { + "type": "jakarta.validation.constraints.NotEmpty" + }, + { + "type": "jakarta.validation.constraints.NotNull" + }, + { + "type": "jakarta.validation.constraints.Pattern", + "methods": [ + { + "name": "flags", + "parameterTypes": [] + }, + { + "name": "groups", + "parameterTypes": [] + }, + { + "name": "message", + "parameterTypes": [] + }, + { + "name": "payload", + "parameterTypes": [] + }, + { + "name": "regexp", + "parameterTypes": [] + } + ] + }, + { + "type": "jakarta.validation.constraints.Size", + "methods": [ + { + "name": "groups", + "parameterTypes": [] + }, + { + "name": "max", + "parameterTypes": [] + }, + { + "name": "message", + "parameterTypes": [] + }, + { + "name": "min", + "parameterTypes": [] + }, + { + "name": "payload", + "parameterTypes": [] + } + ] + }, + { + "type": "jakarta.validation.executable.ExecutableValidator" + }, + { + "type": "jakarta.xml.bind.Binder" + }, + { + "type": "jakarta.xml.ws.WebServiceRef" + }, + { + "type": "java.beans.Introspector" + }, + { + "type": "java.io.Closeable" + }, + { + "type": "java.io.Console", + "methods": [ + { + "name": "isTerminal", + "parameterTypes": [] + } + ] + }, + { + "type": "java.io.Flushable" + }, + { + "type": "java.io.OutputStream" + }, + { + "type": "java.io.Serializable" + }, + { + "type": "java.io.Serializable[]" + }, + { + "type": "java.lang.AutoCloseable" + }, + { + "type": "java.lang.Boolean", + "jniAccessible": true, + "methods": [ + { + "name": "getBoolean", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "java.lang.CharSequence[]" + }, + { + "type": "java.lang.Class" + }, + { + "type": "java.lang.ClassLoader", + "fields": [ + { + "name": "classLoaderValueMap" + } + ] + }, + { + "type": "java.lang.Class[]" + }, + { + "type": "java.lang.CloneNotSupportedException" + }, + { + "type": "java.lang.Comparable" + }, + { + "type": "java.lang.Comparable[]" + }, + { + "type": "java.lang.Error" + }, + { + "type": "java.lang.Iterable" + }, + { + "type": "java.lang.Module" + }, + { + "type": "java.lang.Object", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "java.lang.Object[]" + }, + { + "type": "java.lang.Record" + }, + { + "type": "java.lang.RuntimeException" + }, + { + "type": "java.lang.SecurityManager", + "methods": [ + { + "name": "checkPermission", + "parameterTypes": [ + "java.security.Permission" + ] + } + ] + }, + { + "type": "java.lang.String" + }, + { + "type": "java.lang.String[]" + }, + { + "type": "java.lang.System", + "methods": [ + { + "name": "getSecurityManager", + "parameterTypes": [] + } + ] + }, + { + "type": "java.lang.Thread" + }, + { + "type": "java.lang.Thread$Builder" + }, + { + "type": "java.lang.Throwable" + }, + { + "type": "java.lang.WrongThreadException" + }, + { + "type": "java.lang.annotation.Annotation[]" + }, + { + "type": "java.lang.annotation.Documented" + }, + { + "type": "java.lang.annotation.Inherited" + }, + { + "type": "java.lang.annotation.Repeatable" + }, + { + "type": "java.lang.annotation.Retention" + }, + { + "type": "java.lang.annotation.Target" + }, + { + "type": "java.lang.constant.Constable" + }, + { + "type": "java.lang.constant.Constable[]" + }, + { + "type": "java.lang.constant.ConstantDesc[]" + }, + { + "type": "java.lang.invoke.TypeDescriptor$OfField" + }, + { + "type": "java.lang.reflect.AnnotatedElement" + }, + { + "type": "java.lang.reflect.GenericDeclaration" + }, + { + "type": "java.lang.reflect.ParameterizedType", + "methods": [ + { + "name": "getActualTypeArguments", + "parameterTypes": [] + }, + { + "name": "getRawType", + "parameterTypes": [] + } + ] + }, + { + "type": "java.lang.reflect.Type" + }, + { + "type": "java.lang.reflect.UndeclaredThrowableException" + }, + { + "type": "java.lang.reflect.WildcardType", + "methods": [ + { + "name": "getLowerBounds", + "parameterTypes": [] + }, + { + "name": "getUpperBounds", + "parameterTypes": [] + } + ] + }, + { + "type": "java.net.http.HttpClient" + }, + { + "type": "java.security.AccessController", + "methods": [ + { + "name": "doPrivileged", + "parameterTypes": [ + "java.security.PrivilegedExceptionAction" + ] + } + ] + }, + { + "type": "java.security.AlgorithmParametersSpi" + }, + { + "type": "java.security.KeyStoreSpi" + }, + { + "type": "java.security.interfaces.EdECKey" + }, + { + "type": "java.security.interfaces.XECKey" + }, + { + "type": "java.security.spec.NamedParameterSpec" + }, + { + "type": "java.sql.Date" + }, + { + "type": "java.sql.Driver" + }, + { + "type": "java.sql.DriverManager" + }, + { + "type": "java.sql.Statement[]" + }, + { + "type": "java.sql.Timestamp" + }, + { + "type": "java.sql.Types", + "fields": [ + { + "name": "ARRAY" + }, + { + "name": "BIGINT" + }, + { + "name": "BINARY" + }, + { + "name": "BIT" + }, + { + "name": "BLOB" + }, + { + "name": "BOOLEAN" + }, + { + "name": "CHAR" + }, + { + "name": "CLOB" + }, + { + "name": "DATALINK" + }, + { + "name": "DATE" + }, + { + "name": "DECIMAL" + }, + { + "name": "DISTINCT" + }, + { + "name": "DOUBLE" + }, + { + "name": "FLOAT" + }, + { + "name": "INTEGER" + }, + { + "name": "JAVA_OBJECT" + }, + { + "name": "LONGNVARCHAR" + }, + { + "name": "LONGVARBINARY" + }, + { + "name": "LONGVARCHAR" + }, + { + "name": "NCHAR" + }, + { + "name": "NCLOB" + }, + { + "name": "NULL" + }, + { + "name": "NUMERIC" + }, + { + "name": "NVARCHAR" + }, + { + "name": "OTHER" + }, + { + "name": "REAL" + }, + { + "name": "REF" + }, + { + "name": "REF_CURSOR" + }, + { + "name": "ROWID" + }, + { + "name": "SMALLINT" + }, + { + "name": "SQLXML" + }, + { + "name": "STRUCT" + }, + { + "name": "TIME" + }, + { + "name": "TIMESTAMP" + }, + { + "name": "TIMESTAMP_WITH_TIMEZONE" + }, + { + "name": "TIME_WITH_TIMEZONE" + }, + { + "name": "TINYINT" + }, + { + "name": "VARBINARY" + }, + { + "name": "VARCHAR" + } + ] + }, + { + "type": "java.sql.Wrapper" + }, + { + "type": "java.util.AbstractCollection" + }, + { + "type": "java.util.ArrayList" + }, + { + "type": "java.util.Collection" + }, + { + "type": "java.util.Enumeration" + }, + { + "type": "java.util.EventListener" + }, + { + "type": "java.util.HashSet" + }, + { + "type": "java.util.ImmutableCollections$AbstractImmutableCollection" + }, + { + "type": "java.util.ImmutableCollections$AbstractImmutableList" + }, + { + "type": "java.util.ImmutableCollections$ListN" + }, + { + "type": "java.util.List" + }, + { + "type": "java.util.RandomAccess" + }, + { + "type": "java.util.SequencedCollection" + }, + { + "type": "java.util.Set" + }, + { + "type": "java.util.concurrent.Callable" + }, + { + "type": "java.util.concurrent.Executor" + }, + { + "type": "java.util.concurrent.ThreadFactory" + }, + { + "type": "java.util.logging.LogManager" + }, + { + "type": "java.util.logging.SimpleFormatter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "javafx.beans.value.ObservableValue" + }, + { + "type": "javax.money.MonetaryAmount" + }, + { + "type": "javax.naming.InitialContext" + }, + { + "type": "javax.net.ssl.SSLParameters" + }, + { + "type": "javax.security.auth.Subject" + }, + { + "type": "javax.sql.CommonDataSource" + }, + { + "type": "javax.sql.DataSource" + }, + { + "type": "javax.sql.XADataSource" + }, + { + "type": "jdk.crac.management.CRaCMXBean" + }, + { + "type": "jdk.internal.loader.ClassLoaders$AppClassLoader" + }, + { + "type": "jdk.internal.loader.ClassLoaders$PlatformClassLoader" + }, + { + "type": "jdk.internal.misc.Unsafe" + }, + { + "type": "kotlin.Metadata" + }, + { + "type": "kotlin.reflect.full.KClasses" + }, + { + "type": "kotlinx.coroutines.reactor.MonoKt" + }, + { + "type": "kotlinx.serialization.Serializable" + }, + { + "type": "kotlinx.serialization.cbor.Cbor" + }, + { + "type": "kotlinx.serialization.json.Json" + }, + { + "type": "kotlinx.serialization.protobuf.ProtoBuf" + }, + { + "type": "long[]" + }, + { + "type": "oracle.jdbc.OracleConnection" + }, + { + "type": "oracle.ucp.jdbc.PoolDataSource" + }, + { + "type": "oracle.ucp.jdbc.PoolDataSourceImpl" + }, + { + "type": "org.aopalliance.intercept.MethodInterceptor" + }, + { + "type": "org.apache.catalina.core.ApplicationContextFacade" + }, + { + "type": "org.apache.catalina.loader.JdbcLeakPrevention", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "clearJdbcDriverRegistrations", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.catalina.startup.Tomcat" + }, + { + "type": "org.apache.catalina.util.CharsetMapper", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.commons.dbcp2.BasicDataSource" + }, + { + "type": "org.apache.commons.logging.LogFactory" + }, + { + "type": "org.apache.commons.logging.impl.Slf4jLogFactory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.commons.logging.impl.WeakHashtable", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.coyote.AbstractProtocol", + "methods": [ + { + "name": "getAddress", + "parameterTypes": [] + }, + { + "name": "getProperty", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setPort", + "parameterTypes": [ + "int" + ] + }, + { + "name": "setProperty", + "parameterTypes": [ + "java.lang.String", + "java.lang.String" + ] + } + ] + }, + { + "type": "org.apache.coyote.UpgradeProtocol" + }, + { + "type": "org.apache.coyote.http11.AbstractHttp11Protocol", + "methods": [ + { + "name": "isSSLEnabled", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.coyote.http11.Http11NioProtocol" + }, + { + "type": "org.apache.derby.jdbc.EmbeddedDriver" + }, + { + "type": "org.apache.el.ExpressionFactoryImpl", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.hc.client5.http.classic.HttpClient" + }, + { + "type": "org.apache.hc.client5.http.impl.classic.HttpClients" + }, + { + "type": "org.apache.jasper.compiler.JspConfig" + }, + { + "type": "org.apache.jasper.servlet.JspServlet" + }, + { + "type": "org.apache.logging.log4j.core.impl.Log4jContextFactory" + }, + { + "type": "org.apache.logging.log4j.util.EnvironmentPropertySource" + }, + { + "type": "org.apache.logging.log4j.util.SystemPropertiesPropertySource" + }, + { + "type": "org.apache.logging.slf4j.SLF4JProvider" + }, + { + "type": "org.apache.tomcat.jdbc.pool.DataSource" + }, + { + "type": "org.apache.tomcat.util.net.AbstractEndpoint", + "methods": [ + { + "name": "setBindOnInit", + "parameterTypes": [ + "boolean" + ] + } + ] + }, + { + "type": "org.apache.tomcat.util.net.NioEndpoint" + }, + { + "type": "org.apache.tomcat.websocket.server.WsFilter" + }, + { + "type": "org.apache.tomcat.websocket.server.WsSci" + }, + { + "type": "org.aspectj.weaver.Advice" + }, + { + "type": "org.crac.Core" + }, + { + "type": "org.crac.Resource" + }, + { + "type": "org.eclipse.core.runtime.FileLocator" + }, + { + "type": "org.eclipse.jetty.client.HttpClient" + }, + { + "type": "org.flywaydb.core.Flyway" + }, + { + "type": "org.flywaydb.core.api.migration.baseline.BaselineAppliedMigration" + }, + { + "type": "org.flywaydb.core.api.migration.baseline.BaselineMigrationConfigurationExtension", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "getBaselineMigrationPrefix", + "parameterTypes": [] + }, + { + "name": "setBaselineMigrationPrefix", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "org.flywaydb.core.api.migration.baseline.BaselineMigrationResolver" + }, + { + "type": "org.flywaydb.core.api.migration.baseline.BaselineResourceTypeProvider" + }, + { + "type": "org.flywaydb.core.extensibility.ConfigurationExtension" + }, + { + "type": "org.flywaydb.core.extensibility.Plugin" + }, + { + "type": "org.flywaydb.core.internal.NullFlywayTelemetryManager" + }, + { + "type": "org.flywaydb.core.internal.configuration.resolvers.EnvironmentProvisionerNone" + }, + { + "type": "org.flywaydb.core.internal.configuration.resolvers.EnvironmentVariableResolver" + }, + { + "type": "org.flywaydb.core.internal.configuration.resolvers.PlaceholderPropertyResolver" + }, + { + "type": "org.flywaydb.core.internal.database.base.TestContainersDatabaseType" + }, + { + "type": "org.flywaydb.core.internal.database.h2.H2DatabaseType" + }, + { + "type": "org.flywaydb.core.internal.database.sqlite.SQLiteDatabaseType" + }, + { + "type": "org.flywaydb.core.internal.logging.slf4j.Slf4jLogCreator", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.AuthCommandExtensionStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.AwsSecretsManagerSupportStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.CheckCommandExtensionStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.CherryPickConfigurationExtensionStub", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.CherryPickSupportStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.DeployCommandExtensionStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.DiffCommandExtensionStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.DiffTextCommandExtensionStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.DryRunConfigurationExtensionStub", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.DryRunSupportStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.ErrorOverrideInitializerStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.ErrorOverridesSupportStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.GenerateCommandExtensionStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.LicenseSupportStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.LicensingConfigurationExtensionStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.ModelCommandExtensionStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.OfflinePermitConfigurationExtensionStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.PATTokenConfigurationExtensionStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.PrepareCommandExtensionStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.ShouldExecuteEvaluatorStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.UndoCommandExtensionStub" + }, + { + "type": "org.flywaydb.core.internal.resource.CoreResourceTypeProvider" + }, + { + "type": "org.flywaydb.core.internal.scanner.classpath.ClasspathLocationHandlerImpl" + }, + { + "type": "org.flywaydb.core.internal.scanner.filesystem.FilesystemLocationHandler" + }, + { + "type": "org.flywaydb.core.internal.schemahistory.BaseAppliedMigration" + }, + { + "type": "org.flywaydb.database.cockroachdb.CockroachDBDatabaseType" + }, + { + "type": "org.flywaydb.database.cockroachdb.authentication.CockroachDBExternalAuthSupportStub" + }, + { + "type": "org.flywaydb.database.oracle.OracleConfigurationExtension" + }, + { + "type": "org.flywaydb.database.postgresql.PostgreSQLConfigurationExtension", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "getTransactional", + "parameterTypes": [] + }, + { + "name": "isTransactionalLock", + "parameterTypes": [] + }, + { + "name": "setTransactional", + "parameterTypes": [ + "org.flywaydb.database.postgresql.TransactionalModel" + ] + }, + { + "name": "setTransactionalLock", + "parameterTypes": [ + "boolean" + ] + } + ] + }, + { + "type": "org.flywaydb.database.postgresql.PostgreSQLDatabaseType" + }, + { + "type": "org.flywaydb.database.postgresql.TransactionalModel" + }, + { + "type": "org.flywaydb.database.postgresql.authentication.PostgreSQLExternalAuthSupportStub" + }, + { + "type": "org.flywaydb.database.sqlserver.SQLServerConfigurationExtension" + }, + { + "type": "org.graalvm.nativeimage.ImageInfo", + "methods": [ + { + "name": "inImageCode", + "parameterTypes": [] + } + ] + }, + { + "type": "org.h2.Driver" + }, + { + "type": "org.hibernate.validator.HibernateValidator" + }, + { + "type": "org.hibernate.validator.constraintvalidation.HibernateConstraintValidator" + }, + { + "type": "org.hibernate.validator.internal.constraintvalidators.bv.PatternValidator", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.hibernate.validator.internal.constraintvalidators.bv.size.SizeValidatorForCharSequence", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.hibernate.validator.internal.engine.AbstractConfigurationImpl", + "methods": [ + { + "name": "externalClassLoader", + "parameterTypes": [ + "java.lang.ClassLoader" + ] + } + ] + }, + { + "type": "org.hibernate.validator.internal.engine.ConfigurationImpl" + }, + { + "type": "org.hibernate.validator.internal.util.logging.Log_$logger", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jboss.logging.Logger" + ] + } + ] + }, + { + "type": "org.hibernate.validator.internal.util.logging.Messages_$bundle", + "fields": [ + { + "name": "INSTANCE" + } + ] + }, + { + "type": "org.hibernate.validator.internal.util.logging.Messages_$bundle_en" + }, + { + "type": "org.hsqldb.jdbc.JDBCDriver" + }, + { + "type": "org.jboss.logging.Logger" + }, + { + "type": "org.joda.time.ReadableInstant" + }, + { + "type": "org.jspecify.annotations.NullMarked" + }, + { + "type": "org.osgi.framework.FrameworkUtil" + }, + { + "type": "org.postgresql.Driver", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.postgresql.core.QueryExecutorCloseAction" + }, + { + "type": "org.postgresql.jdbc.PgStatement" + }, + { + "type": "org.reactivestreams.Publisher" + }, + { + "type": "org.slf4j.bridge.SLF4JBridgeHandler" + }, + { + "type": "org.slf4j.helpers.Log4jLoggerFactory" + }, + { + "type": "org.slf4j.spi.LocationAwareLogger" + }, + { + "type": "org.springframework.aop.PointcutAdvisor" + }, + { + "type": "org.springframework.aop.SpringProxy" + }, + { + "type": "org.springframework.aop.TargetClassAware" + }, + { + "type": "org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor" + }, + { + "type": "org.springframework.aop.framework.Advised" + }, + { + "type": "org.springframework.aop.framework.AopConfigException" + }, + { + "type": "org.springframework.aop.framework.AopInfrastructureBean" + }, + { + "type": "org.springframework.aop.framework.ProxyConfig", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setProxyTargetClass", + "parameterTypes": [ + "boolean" + ] + } + ] + }, + { + "type": "org.springframework.aop.framework.ProxyProcessorSupport", + "methods": [ + { + "name": "setOrder", + "parameterTypes": [ + "int" + ] + } + ] + }, + { + "type": "org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator" + }, + { + "type": "org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator" + }, + { + "type": "org.springframework.aop.framework.autoproxy.AbstractBeanFactoryAwareAdvisingPostProcessor" + }, + { + "type": "org.springframework.aop.framework.autoproxy.InfrastructureAdvisorAutoProxyCreator", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.aop.scope.ScopedObject" + }, + { + "type": "org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor" + }, + { + "type": "org.springframework.aop.support.AbstractPointcutAdvisor" + }, + { + "type": "org.springframework.aot.hint.annotation.Reflective" + }, + { + "type": "org.springframework.aot.hint.annotation.RegisterReflection" + }, + { + "type": "org.springframework.aot.hint.annotation.RegisterReflectionForBinding" + }, + { + "type": "org.springframework.beans.factory.Aware" + }, + { + "type": "org.springframework.beans.factory.BeanClassLoaderAware" + }, + { + "type": "org.springframework.beans.factory.BeanFactoryAware" + }, + { + "type": "org.springframework.beans.factory.BeanNameAware" + }, + { + "type": "org.springframework.beans.factory.DisposableBean" + }, + { + "type": "org.springframework.beans.factory.FactoryBean" + }, + { + "type": "org.springframework.beans.factory.InitializingBean" + }, + { + "type": "org.springframework.beans.factory.SmartInitializingSingleton" + }, + { + "type": "org.springframework.beans.factory.annotation.Autowired" + }, + { + "type": "org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.beans.factory.annotation.Qualifier" + }, + { + "type": "org.springframework.beans.factory.annotation.Value" + }, + { + "type": "org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor" + }, + { + "type": "org.springframework.beans.factory.aot.BeanRegistrationAotProcessor" + }, + { + "type": "org.springframework.beans.factory.config.BeanFactoryPostProcessor" + }, + { + "type": "org.springframework.beans.factory.config.BeanPostProcessor" + }, + { + "type": "org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor" + }, + { + "type": "org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor" + }, + { + "type": "org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor" + }, + { + "type": "org.springframework.beans.factory.support.NullBean" + }, + { + "type": "org.springframework.boot.ApplicationProperties" + }, + { + "type": "org.springframework.boot.ClearCachesApplicationListener", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.SpringBootConfiguration" + }, + { + "type": "org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint" + }, + { + "type": "org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration" + }, + { + "type": "org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties" + }, + { + "type": "org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextFactory" + }, + { + "type": "org.springframework.boot.actuate.web.exchanges.HttpExchangeRepository" + }, + { + "type": "org.springframework.boot.ansi.AnsiOutput$Enabled" + }, + { + "type": "org.springframework.boot.autoconfigure.AutoConfiguration" + }, + { + "type": "org.springframework.boot.autoconfigure.AutoConfigurationExcludeFilter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.AutoConfigurationImportSelector", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.AutoConfigurationImportSelector$AutoConfigurationGroup", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.AutoConfigurationPackage" + }, + { + "type": "org.springframework.boot.autoconfigure.AutoConfigurationPackages$BasePackages", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.String[]" + ] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.AutoConfigurationPackages$Registrar", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.AutoConfigureAfter" + }, + { + "type": "org.springframework.boot.autoconfigure.AutoConfigureBefore" + }, + { + "type": "org.springframework.boot.autoconfigure.AutoConfigureOrder" + }, + { + "type": "org.springframework.boot.autoconfigure.EnableAutoConfiguration" + }, + { + "type": "org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.SpringBootApplication" + }, + { + "type": "org.springframework.boot.autoconfigure.aop.AopAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$ClassProxyingConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "forceAutoProxyCreatorToUseClassProxying", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "applicationAvailability", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnBean" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnCheckpointRestore" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnClass" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingFilterBean" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnNotWarDeployment" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnProperty" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnResource" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnThreading" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication$Type" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.OnBeanCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.condition.OnClassCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.condition.OnPropertyCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.condition.OnResourceCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.condition.OnThreadingCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.condition.OnWarDeploymentCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.condition.SearchStrategy" + }, + { + "type": "org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "defaultLifecycleProcessor", + "parameterTypes": [ + "org.springframework.boot.autoconfigure.context.LifecycleProperties" + ] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.context.LifecycleProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration$ResourceBundleCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "propertySourcesPlaceholderConfigurer", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.autoconfigure.info.ProjectInfoProperties" + ] + }, + { + "name": "buildProperties", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration$GitResourceAvailableCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.info.ProjectInfoProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.preinitialize.BackgroundPreinitializingApplicationListener", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.preinitialize.CharsetsBackgroundPreinitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.preinitialize.ConversionServiceBackgroundPreinitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.service.connection.ConnectionDetails" + }, + { + "type": "org.springframework.boot.autoconfigure.ssl.FileWatcher" + }, + { + "type": "org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.core.io.ResourceLoader", + "org.springframework.boot.autoconfigure.ssl.SslProperties" + ] + }, + { + "name": "fileWatcher", + "parameterTypes": [] + }, + { + "name": "sslBundleRegistry", + "parameterTypes": [ + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "sslPropertiesSslBundleRegistrar", + "parameterTypes": [ + "org.springframework.boot.autoconfigure.ssl.FileWatcher" + ] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.ssl.SslBundleRegistrar" + }, + { + "type": "org.springframework.boot.autoconfigure.ssl.SslProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.ssl.SslPropertiesBundleRegistrar" + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskExecutionProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$ApplicationTaskExecutorAsyncConfigurer" + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$AsyncConfigurerConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "applicationTaskExecutorAsyncConfigurer", + "parameterTypes": [ + "org.springframework.beans.factory.BeanFactory" + ] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$AsyncConfigurerWrapperConfiguration" + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$BootstrapExecutorConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "bootstrapExecutorAliasPostProcessor", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$OnExecutorCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$SimpleAsyncTaskExecutorBuilderConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.autoconfigure.task.TaskExecutionProperties", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "simpleAsyncTaskExecutorBuilder", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$TaskExecutorConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "applicationTaskExecutor", + "parameterTypes": [ + "org.springframework.boot.task.ThreadPoolTaskExecutorBuilder" + ] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$TaskExecutorContextPropagationConfiguration" + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$ThreadPoolTaskExecutorBuilderConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "threadPoolTaskExecutorBuilder", + "parameterTypes": [ + "org.springframework.boot.autoconfigure.task.TaskExecutionProperties", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskSchedulingConfigurations$SimpleAsyncTaskSchedulerBuilderConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.autoconfigure.task.TaskSchedulingProperties", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "simpleAsyncTaskSchedulerBuilder", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskSchedulingConfigurations$TaskSchedulerConfiguration" + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskSchedulingConfigurations$ThreadPoolTaskSchedulerBuilderConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "threadPoolTaskSchedulerBuilder", + "parameterTypes": [ + "org.springframework.boot.autoconfigure.task.TaskSchedulingProperties", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskSchedulingProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain" + }, + { + "type": "org.springframework.boot.autoconfigure.web.OnEnabledResourceChainCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.web.WebProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.web.WebResourcesRuntimeHints" + }, + { + "type": "org.springframework.boot.autoconfigure.web.format.WebConversionService" + }, + { + "type": "org.springframework.boot.availability.ApplicationAvailability" + }, + { + "type": "org.springframework.boot.availability.ApplicationAvailabilityBean" + }, + { + "type": "org.springframework.boot.builder.ParentContextCloserApplicationListener", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.logging.DeferredLogFactory" + ] + } + ] + }, + { + "type": "org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.ContextIdApplicationContextInitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.FileEncodingApplicationListener", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.TypeExcludeFilter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.logging.DeferredLogFactory", + "org.springframework.boot.bootstrap.ConfigurableBootstrapContext" + ] + } + ] + }, + { + "type": "org.springframework.boot.context.config.ConfigDataLocation[]" + }, + { + "type": "org.springframework.boot.context.config.ConfigDataNotFoundAction" + }, + { + "type": "org.springframework.boot.context.config.ConfigDataProperties" + }, + { + "type": "org.springframework.boot.context.config.ConfigTreeConfigDataLoader", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.config.ConfigTreeConfigDataLocationResolver", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.core.io.ResourceLoader" + ] + } + ] + }, + { + "type": "org.springframework.boot.context.config.StandardConfigDataLoader", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.config.StandardConfigDataLocationResolver", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.logging.DeferredLogFactory", + "org.springframework.boot.context.properties.bind.Binder", + "org.springframework.core.io.ResourceLoader" + ] + } + ] + }, + { + "type": "org.springframework.boot.context.config.SystemEnvironmentConfigDataLoader", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.config.SystemEnvironmentConfigDataLocationResolver", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.event.EventPublishingRunListener", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.SpringApplication", + "java.lang.String[]" + ] + } + ] + }, + { + "type": "org.springframework.boot.context.logging.LoggingApplicationListener", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.properties.BoundConfigurationProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.properties.ConfigurationProperties" + }, + { + "type": "org.springframework.boot.context.properties.ConfigurationPropertiesBinder$ConfigurationPropertiesBinderFactory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.properties.ConfigurationPropertiesBinding" + }, + { + "type": "org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.properties.ConfigurationPropertiesSource" + }, + { + "type": "org.springframework.boot.context.properties.EnableConfigurationProperties" + }, + { + "type": "org.springframework.boot.context.properties.EnableConfigurationPropertiesRegistrar", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.properties.NestedConfigurationProperty" + }, + { + "type": "org.springframework.boot.context.properties.bind.Name" + }, + { + "type": "org.springframework.boot.context.properties.bind.Nested" + }, + { + "type": "org.springframework.boot.convert.DurationUnit" + }, + { + "type": "org.springframework.boot.env.PropertiesPropertySourceLoader", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.env.YamlPropertySourceLoader", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.flyway.FlywayDatabaseInitializerDetector", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.flyway.autoconfigure.FlywayAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "flywayDefaultDdlModeProvider", + "parameterTypes": [ + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "stringOrNumberMigrationVersionConverter", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.flyway.autoconfigure.FlywayAutoConfiguration$FlywayAutoConfigurationRuntimeHints" + }, + { + "type": "org.springframework.boot.flyway.autoconfigure.FlywayAutoConfiguration$FlywayConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.flyway.autoconfigure.FlywayProperties" + ] + }, + { + "name": "flyway", + "parameterTypes": [ + "org.springframework.boot.flyway.autoconfigure.FlywayConnectionDetails", + "org.springframework.core.io.ResourceLoader", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.boot.flyway.autoconfigure.ResourceProviderCustomizer" + ] + }, + { + "name": "flywayConnectionDetails", + "parameterTypes": [] + }, + { + "name": "flywayInitializer", + "parameterTypes": [ + "org.flywaydb.core.Flyway", + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "resourceProviderCustomizer", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.flyway.autoconfigure.FlywayAutoConfiguration$FlywayConfiguration$PostgresqlConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "postgresqlFlywayConfigurationCustomizer", + "parameterTypes": [ + "org.springframework.boot.flyway.autoconfigure.FlywayProperties" + ] + } + ] + }, + { + "type": "org.springframework.boot.flyway.autoconfigure.FlywayAutoConfiguration$FlywayDataSourceCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.flyway.autoconfigure.FlywayAutoConfiguration$PostgresqlFlywayConfigurationCustomizer" + }, + { + "type": "org.springframework.boot.flyway.autoconfigure.FlywayAutoConfiguration$PropertiesFlywayConnectionDetails" + }, + { + "type": "org.springframework.boot.flyway.autoconfigure.FlywayAutoConfiguration$StringOrNumberToMigrationVersionConverter" + }, + { + "type": "org.springframework.boot.flyway.autoconfigure.FlywayConfigurationCustomizer" + }, + { + "type": "org.springframework.boot.flyway.autoconfigure.FlywayConnectionDetails" + }, + { + "type": "org.springframework.boot.flyway.autoconfigure.FlywayDataSource" + }, + { + "type": "org.springframework.boot.flyway.autoconfigure.FlywayMigrationInitializer" + }, + { + "type": "org.springframework.boot.flyway.autoconfigure.FlywayMigrationInitializerDatabaseInitializerDetector", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.flyway.autoconfigure.FlywayProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "getLocations", + "parameterTypes": [] + }, + { + "name": "setBaselineOnMigrate", + "parameterTypes": [ + "boolean" + ] + }, + { + "name": "setEnabled", + "parameterTypes": [ + "boolean" + ] + }, + { + "name": "setLocations", + "parameterTypes": [ + "java.util.List" + ] + } + ] + }, + { + "type": "org.springframework.boot.flyway.autoconfigure.FlywaySchemaManagementProvider" + }, + { + "type": "org.springframework.boot.flyway.autoconfigure.ResourceProviderCustomizer" + }, + { + "type": "org.springframework.boot.health.actuate.endpoint.HealthEndpoint" + }, + { + "type": "org.springframework.boot.health.autoconfigure.contributor.ConditionalOnEnabledHealthIndicator" + }, + { + "type": "org.springframework.boot.http.client.AbstractClientHttpRequestFactoryBuilder" + }, + { + "type": "org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder" + }, + { + "type": "org.springframework.boot.http.client.HttpClientSettings" + }, + { + "type": "org.springframework.boot.http.client.JdkClientHttpRequestFactoryBuilder" + }, + { + "type": "org.springframework.boot.http.client.autoconfigure.HttpClientAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "httpClientSettings", + "parameterTypes": [ + "org.springframework.boot.http.client.autoconfigure.HttpClientsProperties", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + } + ] + }, + { + "type": "org.springframework.boot.http.client.autoconfigure.HttpClientSettingsProperties" + }, + { + "type": "org.springframework.boot.http.client.autoconfigure.HttpClientsProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.http.client.autoconfigure.imperative.ImperativeHttpClientAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.core.env.Environment" + ] + }, + { + "name": "clientHttpRequestFactoryBuilder", + "parameterTypes": [ + "org.springframework.core.io.ResourceLoader", + "org.springframework.boot.http.client.autoconfigure.imperative.ImperativeHttpClientsProperties", + "org.springframework.beans.factory.ObjectProvider" + ] + } + ] + }, + { + "type": "org.springframework.boot.http.client.autoconfigure.imperative.ImperativeHttpClientsProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.http.client.autoconfigure.imperative.NotReactiveWebApplicationCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.http.client.autoconfigure.service.HttpServiceClientProperties" + }, + { + "type": "org.springframework.boot.http.client.autoconfigure.service.HttpServiceClientPropertiesAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "httpServiceClientProperties", + "parameterTypes": [ + "org.springframework.core.env.Environment" + ] + } + ] + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.ClientHttpMessageConvertersCustomizer" + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.DefaultClientHttpMessageConvertersCustomizer" + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.DefaultServerHttpMessageConvertersCustomizer" + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.GsonHttpMessageConvertersConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.GsonHttpMessageConvertersConfiguration$JacksonAndJsonbUnavailableCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.GsonHttpMessageConvertersConfiguration$PreferGsonOrJacksonAndJsonbUnavailableCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "clientConvertersCustomizer", + "parameterTypes": [ + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "serverConvertersCustomizer", + "parameterTypes": [ + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + } + ] + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration$NotReactiveWebApplicationCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration$StringHttpMessageConverterConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "stringHttpMessageConvertersCustomizer", + "parameterTypes": [ + "org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersProperties" + ] + } + ] + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration$StringHttpMessageConvertersCustomizer" + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.Jackson2HttpMessageConvertersConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.Jackson2HttpMessageConvertersConfiguration$Jackson2JsonMessageConvertersCustomizer" + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.Jackson2HttpMessageConvertersConfiguration$PreferJackson2OrJacksonUnavailableCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.JacksonHttpMessageConvertersConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.JacksonHttpMessageConvertersConfiguration$JacksonJsonHttpMessageConverterConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "jacksonJsonHttpMessageConvertersCustomizer", + "parameterTypes": [ + "tools.jackson.databind.json.JsonMapper" + ] + } + ] + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.JacksonHttpMessageConvertersConfiguration$JacksonJsonHttpMessageConvertersCustomizer" + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.JsonbHttpMessageConvertersConfiguration" + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.KotlinSerializationHttpMessageConvertersConfiguration" + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.MessageConverterBackgroundPreinitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.ServerHttpMessageConvertersCustomizer" + }, + { + "type": "org.springframework.boot.info.BuildProperties" + }, + { + "type": "org.springframework.boot.info.InfoProperties" + }, + { + "type": "org.springframework.boot.io.Base64ProtocolResolver", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.io.ClassPathResourceFilePathResolver", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.io.ProtocolResolverApplicationContextInitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jackson.JacksonComponentModule" + }, + { + "type": "org.springframework.boot.jackson.JacksonMixinModule" + }, + { + "type": "org.springframework.boot.jackson.JacksonMixinModuleEntries" + }, + { + "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "jacksonJsonMapper", + "parameterTypes": [ + "tools.jackson.databind.json.JsonMapper$Builder" + ] + }, + { + "name": "jsonComponentModule", + "parameterTypes": [] + }, + { + "name": "jsonFactory", + "parameterTypes": [ + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "jsonMapperBuilder", + "parameterTypes": [ + "org.springframework.beans.factory.ObjectProvider", + "tools.jackson.core.json.JsonFactory" + ] + } + ] + }, + { + "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration$AbstractFactoryBuilderCustomizer" + }, + { + "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration$AbstractMapperBuilderCustomizer" + }, + { + "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration$JacksonJsonCustomizerConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.jackson.autoconfigure.JacksonProperties" + ] + }, + { + "name": "standardJsonFactoryBuilderCustomizer", + "parameterTypes": [] + }, + { + "name": "standardJsonMapperBuilderCustomizer", + "parameterTypes": [ + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.config.AutowireCapableBeanFactory" + ] + } + ] + }, + { + "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration$JacksonJsonCustomizerConfiguration$StandardJsonFactoryBuilderCustomizer" + }, + { + "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration$JacksonJsonCustomizerConfiguration$StandardJsonMapperBuilderCustomizer" + }, + { + "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration$JacksonMixinConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "jacksonMixinModule", + "parameterTypes": [ + "org.springframework.context.ApplicationContext", + "org.springframework.boot.jackson.JacksonMixinModuleEntries" + ] + }, + { + "name": "jacksonMixinModuleEntries", + "parameterTypes": [ + "org.springframework.context.ApplicationContext" + ] + } + ] + }, + { + "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration$JsonProblemDetailsConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "problemDetailJsonMapperBuilderCustomizer", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration$JsonProblemDetailsConfiguration$ProblemDetailJsonMapperBuilderCustomizer" + }, + { + "type": "org.springframework.boot.jackson.autoconfigure.JacksonBackgroundPreinitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jackson.autoconfigure.JacksonProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jackson.autoconfigure.JsonFactoryBuilderCustomizer" + }, + { + "type": "org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer" + }, + { + "type": "org.springframework.boot.jdbc.SchemaManagementProvider" + }, + { + "type": "org.springframework.boot.jdbc.SpringJdbcDependsOnDatabaseInitializationDetector", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.ApplicationDataSourceScriptDatabaseInitializer" + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration$EmbeddedDatabaseCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration$PooledDataSourceAvailableCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration$PooledDataSourceCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration$PooledDataSourceConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "jdbcConnectionDetails", + "parameterTypes": [ + "org.springframework.boot.jdbc.autoconfigure.DataSourceProperties" + ] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceCheckpointRestoreConfiguration" + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceConfiguration$Dbcp2" + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceConfiguration$Generic" + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceConfiguration$Hikari", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "dataSource", + "parameterTypes": [ + "org.springframework.boot.jdbc.autoconfigure.DataSourceProperties", + "org.springframework.boot.jdbc.autoconfigure.JdbcConnectionDetails", + "org.springframework.core.env.Environment" + ] + }, + { + "name": "jdbcConnectionDetailsHikariBeanPostProcessor", + "parameterTypes": [ + "org.springframework.beans.factory.ObjectProvider" + ] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceConfiguration$OracleUcp" + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceConfiguration$Tomcat" + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceInitializationAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "dataSourceScriptDatabaseInitializer", + "parameterTypes": [ + "javax.sql.DataSource", + "org.springframework.boot.sql.autoconfigure.init.SqlInitializationProperties" + ] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceJmxConfiguration" + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourcePoolMetadataProvidersConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourcePoolMetadataProvidersConfiguration$HikariDataSourcePoolMetadataRuntimeHints" + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourcePoolMetadataProvidersConfiguration$HikariPoolDataSourceMetadataProviderConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "hikariPoolDataSourceMetadataProvider", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setDriverClassName", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setPassword", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setUrl", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setUsername", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration$JdbcTransactionManagerConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "transactionManager", + "parameterTypes": [ + "org.springframework.core.env.Environment", + "javax.sql.DataSource", + "org.springframework.beans.factory.ObjectProvider" + ] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.HikariJdbcConnectionDetailsBeanPostProcessor" + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.JdbcClientAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "jdbcClient", + "parameterTypes": [ + "org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" + ] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.JdbcConnectionDetails" + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.JdbcProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.JdbcTemplateConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "jdbcTemplate", + "parameterTypes": [ + "javax.sql.DataSource", + "org.springframework.boot.jdbc.autoconfigure.JdbcProperties", + "org.springframework.beans.factory.ObjectProvider" + ] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.LazyConnectionDataSourceConfiguration" + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.NamedParameterJdbcTemplateConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "namedParameterJdbcTemplate", + "parameterTypes": [ + "org.springframework.jdbc.core.JdbcTemplate" + ] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.PropertiesJdbcConnectionDetails" + }, + { + "type": "org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer" + }, + { + "type": "org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializerDetector", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider" + }, + { + "type": "org.springframework.boot.loader.launch.LaunchedClassLoader" + }, + { + "type": "org.springframework.boot.logging.LogLevelEditor" + }, + { + "type": "org.springframework.boot.logging.java.JavaLoggingSystem$Factory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.logging.log4j2.Log4J2LoggingSystem$Factory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.logging.log4j2.SpringBootPropertySource" + }, + { + "type": "org.springframework.boot.logging.logback.LogbackLoggingSystem$Factory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.logging.logback.RootLogLevelConfigurator" + }, + { + "type": "org.springframework.boot.micrometer.observation.autoconfigure.ObservationProperties" + }, + { + "type": "org.springframework.boot.persistence.autoconfigure.PersistenceExceptionTranslationAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "persistenceExceptionTranslationPostProcessor", + "parameterTypes": [ + "org.springframework.core.env.Environment" + ] + } + ] + }, + { + "type": "org.springframework.boot.restclient.RestClientCustomizer" + }, + { + "type": "org.springframework.boot.restclient.autoconfigure.AutoConfiguredRestClientSsl" + }, + { + "type": "org.springframework.boot.restclient.autoconfigure.HttpMessageConvertersRestClientCustomizer" + }, + { + "type": "org.springframework.boot.restclient.autoconfigure.NotReactiveWebApplicationCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "restClientBuilder", + "parameterTypes": [ + "org.springframework.boot.restclient.autoconfigure.RestClientBuilderConfigurer" + ] + }, + { + "name": "restClientBuilderConfigurer", + "parameterTypes": [ + "org.springframework.core.io.ResourceLoader", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "restClientSsl", + "parameterTypes": [ + "org.springframework.core.io.ResourceLoader", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.boot.ssl.SslBundles" + ] + } + ] + }, + { + "type": "org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration$HttpMessageConvertersConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "httpMessageConvertersRestClientCustomizer", + "parameterTypes": [ + "org.springframework.beans.factory.ObjectProvider" + ] + } + ] + }, + { + "type": "org.springframework.boot.restclient.autoconfigure.RestClientBuilderConfigurer" + }, + { + "type": "org.springframework.boot.restclient.autoconfigure.RestClientSsl" + }, + { + "type": "org.springframework.boot.restclient.autoconfigure.RestTemplateAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.rsocket.server.RSocketServerCustomizer" + }, + { + "type": "org.springframework.boot.security.autoconfigure.MissingAlternativeUserDetailsManagerOrUserPropertiesConfigured", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.security.autoconfigure.ReactiveUserDetailsServiceAutoConfiguration$RSocketEnabledOrReactiveWebApplication", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.security.autoconfigure.SecurityAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "authenticationEventPublisher", + "parameterTypes": [ + "org.springframework.context.ApplicationEventPublisher" + ] + } + ] + }, + { + "type": "org.springframework.boot.security.autoconfigure.SecurityProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.security.autoconfigure.UserDetailsServiceAutoConfiguration" + }, + { + "type": "org.springframework.boot.security.autoconfigure.web.servlet.ConditionalOnDefaultWebSecurity" + }, + { + "type": "org.springframework.boot.security.autoconfigure.web.servlet.DefaultWebSecurityCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.security.autoconfigure.web.servlet.SecurityFilterAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "securityFilterChainRegistration", + "parameterTypes": [ + "org.springframework.boot.security.autoconfigure.web.servlet.SecurityFilterProperties" + ] + } + ] + }, + { + "type": "org.springframework.boot.security.autoconfigure.web.servlet.SecurityFilterProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.security.autoconfigure.web.servlet.ServletWebSecurityAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.security.autoconfigure.web.servlet.ServletWebSecurityAutoConfiguration$PathPatternRequestMatcherBuilderConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "pathPatternRequestMatcherBuilder", + "parameterTypes": [ + "org.springframework.boot.webmvc.autoconfigure.DispatcherServletPath" + ] + } + ] + }, + { + "type": "org.springframework.boot.servlet.autoconfigure.HttpEncodingAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "characterEncodingFilter", + "parameterTypes": [ + "org.springframework.boot.servlet.autoconfigure.ServletEncodingProperties" + ] + } + ] + }, + { + "type": "org.springframework.boot.servlet.autoconfigure.MultipartAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.servlet.autoconfigure.MultipartProperties" + ] + }, + { + "name": "multipartConfigElement", + "parameterTypes": [] + }, + { + "name": "multipartResolver", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.servlet.autoconfigure.MultipartProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.servlet.autoconfigure.ServletEncodingProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.servlet.filter.OrderedCharacterEncodingFilter" + }, + { + "type": "org.springframework.boot.servlet.filter.OrderedFilter" + }, + { + "type": "org.springframework.boot.servlet.filter.OrderedFormContentFilter" + }, + { + "type": "org.springframework.boot.servlet.filter.OrderedRequestContextFilter" + }, + { + "type": "org.springframework.boot.sql.autoconfigure.init.ApplicationScriptDatabaseInitializer" + }, + { + "type": "org.springframework.boot.sql.autoconfigure.init.ConditionalOnSqlInitialization" + }, + { + "type": "org.springframework.boot.sql.autoconfigure.init.OnSqlInitializationCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.sql.autoconfigure.init.SqlInitializationProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer" + }, + { + "type": "org.springframework.boot.sql.init.dependency.AnnotationDependsOnDatabaseInitializationDetector", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.ssl.DefaultSslBundleRegistry" + }, + { + "type": "org.springframework.boot.ssl.SslBundleRegistry" + }, + { + "type": "org.springframework.boot.ssl.SslBundles" + }, + { + "type": "org.springframework.boot.support.AnsiOutputApplicationListener", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.support.EnvironmentPostProcessorApplicationListener", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.support.RandomValuePropertySourceEnvironmentPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.logging.DeferredLogFactory" + ] + } + ] + }, + { + "type": "org.springframework.boot.support.SpringApplicationJsonEnvironmentPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.support.SystemEnvironmentPropertySourceEnvironmentPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.task.SimpleAsyncTaskExecutorBuilder" + }, + { + "type": "org.springframework.boot.task.SimpleAsyncTaskSchedulerBuilder" + }, + { + "type": "org.springframework.boot.task.ThreadPoolTaskExecutorBuilder" + }, + { + "type": "org.springframework.boot.task.ThreadPoolTaskSchedulerBuilder" + }, + { + "type": "org.springframework.boot.thread.Threading" + }, + { + "type": "org.springframework.boot.tomcat.ConfigurableTomcatWebServerFactory" + }, + { + "type": "org.springframework.boot.tomcat.TomcatWebServerFactory" + }, + { + "type": "org.springframework.boot.tomcat.autoconfigure.TomcatBackgroundPreinitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.tomcat.autoconfigure.TomcatServerProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.tomcat.autoconfigure.TomcatWebServerConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "tomcatWebServerFactoryCustomizer", + "parameterTypes": [ + "org.springframework.core.env.Environment", + "org.springframework.boot.web.server.autoconfigure.ServerProperties", + "org.springframework.boot.tomcat.autoconfigure.TomcatServerProperties", + "org.springframework.boot.autoconfigure.web.WebProperties" + ] + } + ] + }, + { + "type": "org.springframework.boot.tomcat.autoconfigure.TomcatWebServerConfiguration$TomcatWebSocketConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "webSocketWebServerCustomizer", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.tomcat.autoconfigure.TomcatWebServerFactoryCustomizer" + }, + { + "type": "org.springframework.boot.tomcat.autoconfigure.WebSocketTomcatWebServerFactoryCustomizer" + }, + { + "type": "org.springframework.boot.tomcat.autoconfigure.servlet.TomcatServletWebServerAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.tomcat.autoconfigure.TomcatServerProperties" + ] + }, + { + "name": "tomcatServletWebServerFactory", + "parameterTypes": [ + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "tomcatServletWebServerFactoryCustomizer", + "parameterTypes": [ + "org.springframework.boot.tomcat.autoconfigure.TomcatServerProperties" + ] + } + ] + }, + { + "type": "org.springframework.boot.tomcat.autoconfigure.servlet.TomcatServletWebServerFactoryCustomizer" + }, + { + "type": "org.springframework.boot.tomcat.reactive.TomcatReactiveWebServerFactory" + }, + { + "type": "org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory" + }, + { + "type": "org.springframework.boot.transaction.autoconfigure.ExecutionListenersTransactionManagerCustomizer" + }, + { + "type": "org.springframework.boot.transaction.autoconfigure.TransactionAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.transaction.autoconfigure.TransactionAutoConfiguration$EnableTransactionManagementConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.transaction.autoconfigure.TransactionAutoConfiguration$EnableTransactionManagementConfiguration$CglibAutoProxyConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.transaction.autoconfigure.TransactionAutoConfiguration$TransactionTemplateConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "transactionTemplate", + "parameterTypes": [ + "org.springframework.transaction.PlatformTransactionManager" + ] + } + ] + }, + { + "type": "org.springframework.boot.transaction.autoconfigure.TransactionManagerCustomizationAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "platformTransactionManagerCustomizers", + "parameterTypes": [ + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "transactionExecutionListeners", + "parameterTypes": [ + "org.springframework.beans.factory.ObjectProvider" + ] + } + ] + }, + { + "type": "org.springframework.boot.transaction.autoconfigure.TransactionManagerCustomizer" + }, + { + "type": "org.springframework.boot.transaction.autoconfigure.TransactionManagerCustomizers" + }, + { + "type": "org.springframework.boot.transaction.autoconfigure.TransactionProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.validation.autoconfigure.JakartaValidationBackgroundPreinitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.validation.autoconfigure.PrimaryDefaultValidatorPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.validation.autoconfigure.ValidationAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "defaultValidator", + "parameterTypes": [ + "org.springframework.context.ApplicationContext", + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "methodValidationPostProcessor", + "parameterTypes": [ + "org.springframework.core.env.Environment", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + } + ] + }, + { + "type": "org.springframework.boot.validation.autoconfigure.ValidatorAdapter" + }, + { + "type": "org.springframework.boot.validation.beanvalidation.FilteredMethodValidationPostProcessor" + }, + { + "type": "org.springframework.boot.validation.beanvalidation.MethodValidationExcludeFilter", + "methods": [ + { + "name": "byAnnotation", + "parameterTypes": [ + "java.lang.Class" + ] + } + ] + }, + { + "type": "org.springframework.boot.web.context.reactive.FilteredReactiveWebContextResourceFilePathResolver", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.web.context.servlet.ServletContextResourceFilePathResolver", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.web.error.ErrorPageRegistrar" + }, + { + "type": "org.springframework.boot.web.error.ErrorPageRegistrarBeanPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.web.error.ErrorPageRegistry" + }, + { + "type": "org.springframework.boot.web.server.AbstractConfigurableWebServerFactory" + }, + { + "type": "org.springframework.boot.web.server.ConfigurableWebServerFactory" + }, + { + "type": "org.springframework.boot.web.server.WebServerFactory" + }, + { + "type": "org.springframework.boot.web.server.WebServerFactoryCustomizer" + }, + { + "type": "org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.web.server.autoconfigure.ServerProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "getServlet", + "parameterTypes": [] + }, + { + "name": "setPort", + "parameterTypes": [ + "java.lang.Integer" + ] + } + ] + }, + { + "type": "org.springframework.boot.web.server.autoconfigure.ServerProperties$Servlet", + "methods": [ + { + "name": "setContextPath", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "org.springframework.boot.web.server.autoconfigure.servlet.ServletWebServerConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "servletWebServerFactoryCustomizer", + "parameterTypes": [ + "org.springframework.boot.web.server.autoconfigure.ServerProperties", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + } + ] + }, + { + "type": "org.springframework.boot.web.server.autoconfigure.servlet.ServletWebServerConfiguration$BeanPostProcessorsRegistrar", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.web.server.autoconfigure.servlet.ServletWebServerFactoryCustomizer" + }, + { + "type": "org.springframework.boot.web.server.context.ServerPortInfoApplicationContextInitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.web.server.reactive.context.ReactiveWebServerApplicationContextFactory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.web.server.servlet.ConfigurableServletWebServerFactory" + }, + { + "type": "org.springframework.boot.web.server.servlet.ServletWebServerFactory" + }, + { + "type": "org.springframework.boot.web.server.servlet.WebListenerRegistry" + }, + { + "type": "org.springframework.boot.web.server.servlet.context.ServletWebServerApplicationContextFactory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.web.servlet.AbstractFilterRegistrationBean" + }, + { + "type": "org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean" + }, + { + "type": "org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean$1" + }, + { + "type": "org.springframework.boot.web.servlet.DynamicRegistrationBean" + }, + { + "type": "org.springframework.boot.web.servlet.FilterRegistrationBean" + }, + { + "type": "org.springframework.boot.web.servlet.RegistrationBean" + }, + { + "type": "org.springframework.boot.web.servlet.ServletContextInitializer" + }, + { + "type": "org.springframework.boot.web.servlet.ServletRegistrationBean" + }, + { + "type": "org.springframework.boot.webmvc.WebMvcWebApplicationTypeDeducer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration$DefaultDispatcherServletCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration$DispatcherServletConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "dispatcherServlet", + "parameterTypes": [ + "org.springframework.boot.webmvc.autoconfigure.WebMvcProperties" + ] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration$DispatcherServletRegistrationCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "dispatcherServletRegistration", + "parameterTypes": [ + "org.springframework.web.servlet.DispatcherServlet", + "org.springframework.boot.webmvc.autoconfigure.WebMvcProperties", + "org.springframework.beans.factory.ObjectProvider" + ] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletPath" + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletRegistrationBean" + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.JspTemplateAvailabilityProvider", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "formContentFilter", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration$EnableWebMvcConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.webmvc.autoconfigure.WebMvcProperties", + "org.springframework.boot.autoconfigure.web.WebProperties", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ListableBeanFactory" + ] + }, + { + "name": "flashMapManager", + "parameterTypes": [] + }, + { + "name": "localeResolver", + "parameterTypes": [] + }, + { + "name": "mvcApiVersionStrategy", + "parameterTypes": [] + }, + { + "name": "mvcConversionService", + "parameterTypes": [] + }, + { + "name": "mvcValidator", + "parameterTypes": [] + }, + { + "name": "viewNameTranslator", + "parameterTypes": [] + }, + { + "name": "welcomePageHandlerMapping", + "parameterTypes": [ + "org.springframework.context.ApplicationContext", + "org.springframework.format.support.FormattingConversionService", + "org.springframework.web.servlet.resource.ResourceUrlProvider" + ] + }, + { + "name": "welcomePageNotAcceptableHandlerMapping", + "parameterTypes": [ + "org.springframework.context.ApplicationContext", + "org.springframework.format.support.FormattingConversionService", + "org.springframework.web.servlet.resource.ResourceUrlProvider" + ] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration$MvcValidatorRuntimeHints" + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.autoconfigure.web.WebProperties", + "org.springframework.boot.webmvc.autoconfigure.WebMvcProperties", + "org.springframework.beans.factory.ListableBeanFactory", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "defaultViewResolver", + "parameterTypes": [] + }, + { + "name": "requestContextFilter", + "parameterTypes": [] + }, + { + "name": "viewResolver", + "parameterTypes": [ + "org.springframework.beans.factory.BeanFactory" + ] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.WebMvcProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.WelcomePageHandlerMapping" + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.WelcomePageNotAcceptableHandlerMapping" + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.error.AbstractErrorController" + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.error.BasicErrorController" + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.error.DefaultErrorViewResolver" + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.autoconfigure.web.WebProperties" + ] + }, + { + "name": "basicErrorController", + "parameterTypes": [ + "org.springframework.boot.webmvc.error.ErrorAttributes", + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "errorAttributes", + "parameterTypes": [] + }, + { + "name": "errorPageCustomizer", + "parameterTypes": [ + "org.springframework.boot.webmvc.autoconfigure.DispatcherServletPath" + ] + }, + { + "name": "preserveErrorControllerTargetClassPostProcessor", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration$DefaultErrorViewResolverConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.context.ApplicationContext", + "org.springframework.boot.autoconfigure.web.WebProperties" + ] + }, + { + "name": "conventionErrorViewResolver", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration$ErrorPageCustomizer" + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration$ErrorTemplateMissingCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration$PreserveErrorControllerTargetClassPostProcessor" + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration$StaticView" + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "beanNameViewResolver", + "parameterTypes": [] + }, + { + "name": "defaultErrorView", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorViewResolver" + }, + { + "type": "org.springframework.boot.webmvc.error.DefaultErrorAttributes" + }, + { + "type": "org.springframework.boot.webmvc.error.ErrorAttributes" + }, + { + "type": "org.springframework.boot.webmvc.error.ErrorController" + }, + { + "type": "org.springframework.cglib.proxy.Dispatcher" + }, + { + "type": "org.springframework.cglib.proxy.MethodInterceptor" + }, + { + "type": "org.springframework.cglib.proxy.NoOp" + }, + { + "type": "org.springframework.context.ApplicationContextAware" + }, + { + "type": "org.springframework.context.ApplicationEventPublisherAware" + }, + { + "type": "org.springframework.context.ApplicationListener" + }, + { + "type": "org.springframework.context.ApplicationStartupAware" + }, + { + "type": "org.springframework.context.EmbeddedValueResolverAware" + }, + { + "type": "org.springframework.context.EnvironmentAware" + }, + { + "type": "org.springframework.context.Lifecycle" + }, + { + "type": "org.springframework.context.LifecycleProcessor" + }, + { + "type": "org.springframework.context.MessageSourceAware" + }, + { + "type": "org.springframework.context.Phased" + }, + { + "type": "org.springframework.context.ResourceLoaderAware" + }, + { + "type": "org.springframework.context.SmartLifecycle" + }, + { + "type": "org.springframework.context.annotation.AnnotationScopeMetadataResolver", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.context.annotation.AutoProxyRegistrar", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.context.annotation.Bean" + }, + { + "type": "org.springframework.context.annotation.CommonAnnotationBeanPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.context.annotation.ComponentScan" + }, + { + "type": "org.springframework.context.annotation.ComponentScan$Filter" + }, + { + "type": "org.springframework.context.annotation.Conditional" + }, + { + "type": "org.springframework.context.annotation.Configuration" + }, + { + "type": "org.springframework.context.annotation.ConfigurationClassEnhancer$EnhancedConfiguration" + }, + { + "type": "org.springframework.context.annotation.ConfigurationClassPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setMetadataReaderFactory", + "parameterTypes": [ + "org.springframework.core.type.classreading.MetadataReaderFactory" + ] + } + ] + }, + { + "type": "org.springframework.context.annotation.DependsOn" + }, + { + "type": "org.springframework.context.annotation.Import" + }, + { + "type": "org.springframework.context.annotation.ImportAware" + }, + { + "type": "org.springframework.context.annotation.ImportRuntimeHints" + }, + { + "type": "org.springframework.context.annotation.Lazy" + }, + { + "type": "org.springframework.context.annotation.Primary" + }, + { + "type": "org.springframework.context.annotation.Role" + }, + { + "type": "org.springframework.context.annotation.Scope" + }, + { + "type": "org.springframework.context.event.DefaultEventListenerFactory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.context.event.EventListener" + }, + { + "type": "org.springframework.context.event.EventListenerMethodProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.context.support.ApplicationObjectSupport" + }, + { + "type": "org.springframework.context.support.DefaultLifecycleProcessor" + }, + { + "type": "org.springframework.context.support.PropertySourcesPlaceholderConfigurer" + }, + { + "type": "org.springframework.core.Ordered" + }, + { + "type": "org.springframework.core.PriorityOrdered" + }, + { + "type": "org.springframework.core.annotation.AliasFor" + }, + { + "type": "org.springframework.core.annotation.AnnotationAttributes[]" + }, + { + "type": "org.springframework.core.annotation.Order" + }, + { + "type": "org.springframework.core.convert.ConversionService" + }, + { + "type": "org.springframework.core.convert.converter.ConverterRegistry" + }, + { + "type": "org.springframework.core.convert.converter.GenericConverter" + }, + { + "type": "org.springframework.core.convert.support.ConfigurableConversionService" + }, + { + "type": "org.springframework.core.convert.support.GenericConversionService" + }, + { + "type": "org.springframework.core.env.EnvironmentCapable" + }, + { + "type": "org.springframework.core.task.AsyncTaskExecutor" + }, + { + "type": "org.springframework.core.task.TaskExecutor" + }, + { + "type": "org.springframework.core.type.classreading.CachingMetadataReaderFactory" + }, + { + "type": "org.springframework.core.type.classreading.MetadataReaderFactory" + }, + { + "type": "org.springframework.dao.DataAccessException" + }, + { + "type": "org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" + }, + { + "type": "org.springframework.data.rest.webmvc.alps.AlpsJacksonJsonHttpMessageConverter" + }, + { + "type": "org.springframework.format.FormatterRegistry" + }, + { + "type": "org.springframework.format.support.DefaultFormattingConversionService" + }, + { + "type": "org.springframework.format.support.FormattingConversionService" + }, + { + "type": "org.springframework.hateoas.server.mvc.TypeConstrainedJacksonJsonHttpMessageConverter" + }, + { + "type": "org.springframework.http.HttpStatus" + }, + { + "type": "org.springframework.http.ProblemDetail" + }, + { + "type": "org.springframework.http.ReactiveHttpInputMessage" + }, + { + "type": "org.springframework.http.client.ClientHttpRequestFactory" + }, + { + "type": "org.springframework.http.converter.HttpMessageConverter" + }, + { + "type": "org.springframework.http.converter.HttpMessageConverters" + }, + { + "type": "org.springframework.http.converter.StringHttpMessageConverter" + }, + { + "type": "org.springframework.http.converter.json.JacksonJsonHttpMessageConverter" + }, + { + "type": "org.springframework.jdbc.core.JdbcOperations" + }, + { + "type": "org.springframework.jdbc.core.JdbcTemplate" + }, + { + "type": "org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations" + }, + { + "type": "org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" + }, + { + "type": "org.springframework.jdbc.core.simple.DefaultJdbcClient" + }, + { + "type": "org.springframework.jdbc.core.simple.JdbcClient" + }, + { + "type": "org.springframework.jdbc.datasource.DataSourceTransactionManager" + }, + { + "type": "org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType" + }, + { + "type": "org.springframework.jdbc.datasource.init.DatabasePopulator" + }, + { + "type": "org.springframework.jdbc.support.JdbcAccessor" + }, + { + "type": "org.springframework.jdbc.support.JdbcTransactionManager" + }, + { + "type": "org.springframework.jdbc.support.JdbcUtils" + }, + { + "type": "org.springframework.jmx.export.MBeanExporter" + }, + { + "type": "org.springframework.messaging.MessagingException" + }, + { + "type": "org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler" + }, + { + "type": "org.springframework.scheduling.SchedulingTaskExecutor" + }, + { + "type": "org.springframework.scheduling.annotation.AsyncConfigurer" + }, + { + "type": "org.springframework.scheduling.concurrent.CustomizableThreadFactory" + }, + { + "type": "org.springframework.scheduling.concurrent.ExecutorConfigurationSupport" + }, + { + "type": "org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor" + }, + { + "type": "org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler" + }, + { + "type": "org.springframework.security.access.SecurityConfig" + }, + { + "type": "org.springframework.security.access.expression.AbstractSecurityExpressionHandler" + }, + { + "type": "org.springframework.security.access.expression.SecurityExpressionHandler" + }, + { + "type": "org.springframework.security.authentication.AnonymousAuthenticationProvider" + }, + { + "type": "org.springframework.security.authentication.AuthenticationEventPublisher" + }, + { + "type": "org.springframework.security.authentication.AuthenticationManager" + }, + { + "type": "org.springframework.security.authentication.AuthenticationManagerResolver" + }, + { + "type": "org.springframework.security.authentication.AuthenticationProvider" + }, + { + "type": "org.springframework.security.authentication.DefaultAuthenticationEventPublisher" + }, + { + "type": "org.springframework.security.authentication.ProviderManager" + }, + { + "type": "org.springframework.security.authentication.ReactiveAuthenticationManager" + }, + { + "type": "org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider" + }, + { + "type": "org.springframework.security.authentication.dao.DaoAuthenticationProvider" + }, + { + "type": "org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent" + }, + { + "type": "org.springframework.security.authentication.event.AuthenticationFailureCredentialsExpiredEvent" + }, + { + "type": "org.springframework.security.authentication.event.AuthenticationFailureDisabledEvent" + }, + { + "type": "org.springframework.security.authentication.event.AuthenticationFailureExpiredEvent" + }, + { + "type": "org.springframework.security.authentication.event.AuthenticationFailureLockedEvent" + }, + { + "type": "org.springframework.security.authentication.event.AuthenticationFailureProviderNotFoundEvent" + }, + { + "type": "org.springframework.security.authentication.event.AuthenticationFailureProxyUntrustedEvent" + }, + { + "type": "org.springframework.security.authentication.event.AuthenticationFailureServiceExceptionEvent" + }, + { + "type": "org.springframework.security.authorization.AuthorizationManager" + }, + { + "type": "org.springframework.security.config.ObjectPostProcessor" + }, + { + "type": "org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder" + }, + { + "type": "org.springframework.security.config.annotation.AbstractSecurityBuilder" + }, + { + "type": "org.springframework.security.config.annotation.SecurityBuilder" + }, + { + "type": "org.springframework.security.config.annotation.SecurityConfigurer" + }, + { + "type": "org.springframework.security.config.annotation.authentication.ProviderManagerBuilder" + }, + { + "type": "org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder" + }, + { + "type": "org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "authenticationManagerBuilder", + "parameterTypes": [ + "org.springframework.security.config.ObjectPostProcessor", + "org.springframework.context.ApplicationContext" + ] + }, + { + "name": "enableGlobalAuthenticationAutowiredConfigurer", + "parameterTypes": [ + "org.springframework.context.ApplicationContext" + ] + }, + { + "name": "initializeAuthenticationProviderBeanManagerConfigurer", + "parameterTypes": [ + "org.springframework.context.ApplicationContext" + ] + }, + { + "name": "initializeUserDetailsBeanManagerConfigurer", + "parameterTypes": [ + "org.springframework.context.ApplicationContext" + ] + }, + { + "name": "setApplicationContext", + "parameterTypes": [ + "org.springframework.context.ApplicationContext" + ] + }, + { + "name": "setGlobalAuthenticationConfigurers", + "parameterTypes": [ + "java.util.List" + ] + }, + { + "name": "setObjectPostProcessor", + "parameterTypes": [ + "org.springframework.security.config.ObjectPostProcessor" + ] + } + ] + }, + { + "type": "org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration$DefaultPasswordEncoderAuthenticationManagerBuilder" + }, + { + "type": "org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration$EnableGlobalAuthenticationAutowiredConfigurer" + }, + { + "type": "org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication" + }, + { + "type": "org.springframework.security.config.annotation.authentication.configuration.GlobalAuthenticationConfigurerAdapter" + }, + { + "type": "org.springframework.security.config.annotation.authentication.configuration.InitializeAuthenticationProviderBeanManagerConfigurer" + }, + { + "type": "org.springframework.security.config.annotation.authentication.configuration.InitializeUserDetailsBeanManagerConfigurer" + }, + { + "type": "org.springframework.security.config.annotation.configuration.AutowireBeanFactoryObjectPostProcessor" + }, + { + "type": "org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "objectPostProcessor", + "parameterTypes": [ + "org.springframework.beans.factory.config.AutowireCapableBeanFactory" + ] + } + ] + }, + { + "type": "org.springframework.security.config.annotation.web.HttpSecurityBuilder" + }, + { + "type": "org.springframework.security.config.annotation.web.builders.HttpSecurity" + }, + { + "type": "org.springframework.security.config.annotation.web.builders.WebSecurity" + }, + { + "type": "org.springframework.security.config.annotation.web.builders.WebSecurity$SecurityExpressionHandlerAdapter" + }, + { + "type": "org.springframework.security.config.annotation.web.configuration.EnableWebSecurity" + }, + { + "type": "org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "httpSecurity", + "parameterTypes": [] + }, + { + "name": "setApplicationContext", + "parameterTypes": [ + "org.springframework.context.ApplicationContext" + ] + }, + { + "name": "setAuthenticationConfiguration", + "parameterTypes": [ + "org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration" + ] + }, + { + "name": "setContentNegotiationStrategy", + "parameterTypes": [ + "org.springframework.web.accept.ContentNegotiationStrategy" + ] + }, + { + "name": "setObjectPostProcessor", + "parameterTypes": [ + "org.springframework.security.config.ObjectPostProcessor" + ] + } + ] + }, + { + "type": "org.springframework.security.config.annotation.web.configuration.OAuth2ImportSelector", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.security.config.annotation.web.configuration.ObservationConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "authenticationManagerPostProcessor", + "parameterTypes": [ + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "filterChainDecoratorPostProcessor", + "parameterTypes": [ + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "webAuthorizationManagerPostProcessor", + "parameterTypes": [ + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + } + ] + }, + { + "type": "org.springframework.security.config.annotation.web.configuration.ObservationConfiguration$1" + }, + { + "type": "org.springframework.security.config.annotation.web.configuration.ObservationConfiguration$2" + }, + { + "type": "org.springframework.security.config.annotation.web.configuration.ObservationConfiguration$3" + }, + { + "type": "org.springframework.security.config.annotation.web.configuration.ObservationImportSelector", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.security.config.annotation.web.configuration.SpringWebMvcImportSelector", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "requestDataValueProcessor", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "conversionServicePostProcessor", + "parameterTypes": [] + }, + { + "name": "delegatingApplicationListener", + "parameterTypes": [] + }, + { + "name": "privilegeEvaluator", + "parameterTypes": [] + }, + { + "name": "setFilterChainProxySecurityConfigurer", + "parameterTypes": [ + "org.springframework.security.config.ObjectPostProcessor", + "org.springframework.beans.factory.config.ConfigurableListableBeanFactory" + ] + }, + { + "name": "setFilterChains", + "parameterTypes": [ + "java.util.List" + ] + }, + { + "name": "springSecurityFilterChain", + "parameterTypes": [ + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "springSecurityPathPatternParserBeanDefinitionRegistryPostProcessor", + "parameterTypes": [] + }, + { + "name": "webSecurityExpressionHandler", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$1" + }, + { + "type": "org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.util.List" + ] + } + ] + }, + { + "type": "org.springframework.security.config.crypto.RsaKeyConversionServicePostProcessor" + }, + { + "type": "org.springframework.security.config.http.SessionCreationPolicy" + }, + { + "type": "org.springframework.security.context.DelegatingApplicationListener" + }, + { + "type": "org.springframework.security.core.userdetails.UserDetailsService" + }, + { + "type": "org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" + }, + { + "type": "org.springframework.security.crypto.password.AbstractValidatingPasswordEncoder" + }, + { + "type": "org.springframework.security.crypto.password.PasswordEncoder" + }, + { + "type": "org.springframework.security.data.repository.query.SecurityEvaluationContextExtension" + }, + { + "type": "org.springframework.security.oauth2.client.registration.ClientRegistration" + }, + { + "type": "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository" + }, + { + "type": "org.springframework.security.oauth2.jwt.JwtDecoder" + }, + { + "type": "org.springframework.security.oauth2.server.resource.BearerTokenError" + }, + { + "type": "org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector" + }, + { + "type": "org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository" + }, + { + "type": "org.springframework.security.web.DefaultSecurityFilterChain" + }, + { + "type": "org.springframework.security.web.FilterChainProxy" + }, + { + "type": "org.springframework.security.web.SecurityFilterChain" + }, + { + "type": "org.springframework.security.web.access.ExceptionTranslationFilter" + }, + { + "type": "org.springframework.security.web.access.RequestMatcherDelegatingWebInvocationPrivilegeEvaluator" + }, + { + "type": "org.springframework.security.web.access.WebInvocationPrivilegeEvaluator" + }, + { + "type": "org.springframework.security.web.access.intercept.AuthorizationFilter" + }, + { + "type": "org.springframework.security.web.access.intercept.RequestMatcherDelegatingAuthorizationManager" + }, + { + "type": "org.springframework.security.web.authentication.AnonymousAuthenticationFilter" + }, + { + "type": "org.springframework.security.web.authentication.logout.LogoutFilter" + }, + { + "type": "org.springframework.security.web.authentication.logout.LogoutHandler" + }, + { + "type": "org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler" + }, + { + "type": "org.springframework.security.web.authentication.session.AbstractSessionFixationProtectionStrategy" + }, + { + "type": "org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy" + }, + { + "type": "org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy" + }, + { + "type": "org.springframework.security.web.authentication.session.SessionAuthenticationStrategy" + }, + { + "type": "org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer" + }, + { + "type": "org.springframework.security.web.context.SecurityContextHolderFilter" + }, + { + "type": "org.springframework.security.web.header.HeaderWriterFilter" + }, + { + "type": "org.springframework.security.web.savedrequest.RequestCacheAwareFilter" + }, + { + "type": "org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor" + }, + { + "type": "org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher$Builder" + }, + { + "type": "org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter" + }, + { + "type": "org.springframework.security.web.session.SessionManagementFilter" + }, + { + "type": "org.springframework.stereotype.Component" + }, + { + "type": "org.springframework.stereotype.Controller" + }, + { + "type": "org.springframework.stereotype.Indexed" + }, + { + "type": "org.springframework.stereotype.Repository" + }, + { + "type": "org.springframework.stereotype.Service" + }, + { + "type": "org.springframework.transaction.ConfigurableTransactionManager" + }, + { + "type": "org.springframework.transaction.PlatformTransactionManager" + }, + { + "type": "org.springframework.transaction.ReactiveTransactionManager" + }, + { + "type": "org.springframework.transaction.TransactionDefinition" + }, + { + "type": "org.springframework.transaction.TransactionManager" + }, + { + "type": "org.springframework.transaction.annotation.AbstractTransactionManagementConfiguration", + "methods": [ + { + "name": "transactionAttributeSource", + "parameterTypes": [] + }, + { + "name": "transactionalEventListenerFactory", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.transaction.annotation.AnnotationTransactionAttributeSource" + }, + { + "type": "org.springframework.transaction.annotation.EnableTransactionManagement" + }, + { + "type": "org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "transactionAdvisor", + "parameterTypes": [ + "org.springframework.transaction.interceptor.TransactionAttributeSource", + "org.springframework.transaction.interceptor.TransactionInterceptor" + ] + }, + { + "name": "transactionInterceptor", + "parameterTypes": [ + "org.springframework.transaction.interceptor.TransactionAttributeSource" + ] + } + ] + }, + { + "type": "org.springframework.transaction.annotation.RestrictedTransactionalEventListenerFactory" + }, + { + "type": "org.springframework.transaction.annotation.TransactionManagementConfigurationSelector", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.transaction.annotation.TransactionRuntimeHints" + }, + { + "type": "org.springframework.transaction.annotation.Transactional" + }, + { + "type": "org.springframework.transaction.aspectj.AbstractTransactionAspect" + }, + { + "type": "org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource" + }, + { + "type": "org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor" + }, + { + "type": "org.springframework.transaction.interceptor.TransactionAspectSupport" + }, + { + "type": "org.springframework.transaction.interceptor.TransactionAttributeSource" + }, + { + "type": "org.springframework.transaction.interceptor.TransactionInterceptor" + }, + { + "type": "org.springframework.transaction.support.AbstractPlatformTransactionManager" + }, + { + "type": "org.springframework.transaction.support.DefaultTransactionDefinition" + }, + { + "type": "org.springframework.transaction.support.ResourceTransactionManager" + }, + { + "type": "org.springframework.transaction.support.TransactionOperations" + }, + { + "type": "org.springframework.transaction.support.TransactionTemplate" + }, + { + "type": "org.springframework.util.AntPathMatcher" + }, + { + "type": "org.springframework.util.ConcurrentReferenceHashMap$Segment[]" + }, + { + "type": "org.springframework.util.CustomizableThreadCreator" + }, + { + "type": "org.springframework.util.PathMatcher" + }, + { + "type": "org.springframework.validation.SmartValidator" + }, + { + "type": "org.springframework.validation.Validator" + }, + { + "type": "org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" + }, + { + "type": "org.springframework.validation.beanvalidation.MethodValidationPostProcessor" + }, + { + "type": "org.springframework.validation.beanvalidation.SpringValidatorAdapter" + }, + { + "type": "org.springframework.web.accept.ApiVersionStrategy" + }, + { + "type": "org.springframework.web.accept.ApiVersionStrategyEditor" + }, + { + "type": "org.springframework.web.accept.ContentNegotiationManager" + }, + { + "type": "org.springframework.web.accept.ContentNegotiationStrategy" + }, + { + "type": "org.springframework.web.accept.MediaTypeFileExtensionResolver" + }, + { + "type": "org.springframework.web.bind.annotation.ControllerAdvice" + }, + { + "type": "org.springframework.web.bind.annotation.DeleteMapping" + }, + { + "type": "org.springframework.web.bind.annotation.ExceptionHandler" + }, + { + "type": "org.springframework.web.bind.annotation.GetMapping" + }, + { + "type": "org.springframework.web.bind.annotation.Mapping" + }, + { + "type": "org.springframework.web.bind.annotation.PatchMapping" + }, + { + "type": "org.springframework.web.bind.annotation.PathVariable" + }, + { + "type": "org.springframework.web.bind.annotation.PostMapping" + }, + { + "type": "org.springframework.web.bind.annotation.PutMapping" + }, + { + "type": "org.springframework.web.bind.annotation.RequestBody" + }, + { + "type": "org.springframework.web.bind.annotation.RequestMapping" + }, + { + "type": "org.springframework.web.bind.annotation.RequestMethod[]" + }, + { + "type": "org.springframework.web.bind.annotation.ResponseBody" + }, + { + "type": "org.springframework.web.bind.annotation.ResponseStatus" + }, + { + "type": "org.springframework.web.bind.annotation.RestController" + }, + { + "type": "org.springframework.web.bind.annotation.RestControllerAdvice" + }, + { + "type": "org.springframework.web.client.DefaultRestClientBuilder" + }, + { + "type": "org.springframework.web.client.RestClient" + }, + { + "type": "org.springframework.web.client.RestClient$Builder" + }, + { + "type": "org.springframework.web.client.RestClientException" + }, + { + "type": "org.springframework.web.client.RestTemplate" + }, + { + "type": "org.springframework.web.client.support.RestClientAdapter" + }, + { + "type": "org.springframework.web.context.ConfigurableWebApplicationContext" + }, + { + "type": "org.springframework.web.context.ServletContextAware" + }, + { + "type": "org.springframework.web.context.request.RequestContextListener" + }, + { + "type": "org.springframework.web.context.support.GenericWebApplicationContext" + }, + { + "type": "org.springframework.web.context.support.ServletContextResource" + }, + { + "type": "org.springframework.web.context.support.WebApplicationObjectSupport" + }, + { + "type": "org.springframework.web.cors.CorsConfigurationSource" + }, + { + "type": "org.springframework.web.cors.PreFlightRequestHandler" + }, + { + "type": "org.springframework.web.filter.CharacterEncodingFilter" + }, + { + "type": "org.springframework.web.filter.DelegatingFilterProxy" + }, + { + "type": "org.springframework.web.filter.FormContentFilter" + }, + { + "type": "org.springframework.web.filter.GenericFilterBean" + }, + { + "type": "org.springframework.web.filter.OncePerRequestFilter" + }, + { + "type": "org.springframework.web.filter.RequestContextFilter" + }, + { + "type": "org.springframework.web.filter.ServletRequestPathFilter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.web.method.annotation.ExceptionHandlerMethodResolver" + }, + { + "type": "org.springframework.web.method.support.CompositeUriComponentsContributor" + }, + { + "type": "org.springframework.web.method.support.UriComponentsContributor" + }, + { + "type": "org.springframework.web.multipart.MultipartResolver" + }, + { + "type": "org.springframework.web.multipart.support.StandardServletMultipartResolver" + }, + { + "type": "org.springframework.web.reactive.HandlerResult" + }, + { + "type": "org.springframework.web.reactive.function.client.ExchangeFilterFunction" + }, + { + "type": "org.springframework.web.reactive.function.client.WebClientException" + }, + { + "type": "org.springframework.web.service.registry.HttpServiceProxyRegistry" + }, + { + "type": "org.springframework.web.servlet.DispatcherServlet" + }, + { + "type": "org.springframework.web.servlet.FlashMapManager" + }, + { + "type": "org.springframework.web.servlet.FrameworkServlet" + }, + { + "type": "org.springframework.web.servlet.HandlerAdapter" + }, + { + "type": "org.springframework.web.servlet.HandlerExceptionResolver" + }, + { + "type": "org.springframework.web.servlet.HandlerMapping" + }, + { + "type": "org.springframework.web.servlet.HandlerMappingEditor" + }, + { + "type": "org.springframework.web.servlet.HttpServletBean" + }, + { + "type": "org.springframework.web.servlet.LocaleResolver" + }, + { + "type": "org.springframework.web.servlet.RequestToViewNameTranslator" + }, + { + "type": "org.springframework.web.servlet.View" + }, + { + "type": "org.springframework.web.servlet.ViewResolver" + }, + { + "type": "org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration", + "methods": [ + { + "name": "setConfigurers", + "parameterTypes": [ + "java.util.List" + ] + } + ] + }, + { + "type": "org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport", + "methods": [ + { + "name": "beanNameHandlerMapping", + "parameterTypes": [ + "org.springframework.format.support.FormattingConversionService", + "org.springframework.web.servlet.resource.ResourceUrlProvider" + ] + }, + { + "name": "defaultServletHandlerMapping", + "parameterTypes": [] + }, + { + "name": "handlerExceptionResolver", + "parameterTypes": [ + "org.springframework.web.accept.ContentNegotiationManager" + ] + }, + { + "name": "handlerFunctionAdapter", + "parameterTypes": [] + }, + { + "name": "httpRequestHandlerAdapter", + "parameterTypes": [] + }, + { + "name": "mvcContentNegotiationManager", + "parameterTypes": [] + }, + { + "name": "mvcHandlerMappingIntrospector", + "parameterTypes": [] + }, + { + "name": "mvcPathMatcher", + "parameterTypes": [] + }, + { + "name": "mvcPatternParser", + "parameterTypes": [] + }, + { + "name": "mvcResourceUrlProvider", + "parameterTypes": [] + }, + { + "name": "mvcUriComponentsContributor", + "parameterTypes": [ + "org.springframework.format.support.FormattingConversionService", + "org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" + ] + }, + { + "name": "mvcUrlPathHelper", + "parameterTypes": [] + }, + { + "name": "mvcViewResolver", + "parameterTypes": [ + "org.springframework.web.accept.ContentNegotiationManager" + ] + }, + { + "name": "requestMappingHandlerAdapter", + "parameterTypes": [ + "org.springframework.web.accept.ContentNegotiationManager", + "org.springframework.format.support.FormattingConversionService", + "org.springframework.validation.Validator" + ] + }, + { + "name": "requestMappingHandlerMapping", + "parameterTypes": [ + "org.springframework.web.accept.ContentNegotiationManager", + "org.springframework.web.accept.ApiVersionStrategy", + "org.springframework.format.support.FormattingConversionService", + "org.springframework.web.servlet.resource.ResourceUrlProvider" + ] + }, + { + "name": "resourceHandlerMapping", + "parameterTypes": [ + "org.springframework.web.accept.ContentNegotiationManager", + "org.springframework.format.support.FormattingConversionService", + "org.springframework.web.servlet.resource.ResourceUrlProvider" + ] + }, + { + "name": "routerFunctionMapping", + "parameterTypes": [ + "org.springframework.format.support.FormattingConversionService", + "org.springframework.web.servlet.resource.ResourceUrlProvider", + "org.springframework.web.accept.ApiVersionStrategy" + ] + }, + { + "name": "simpleControllerHandlerAdapter", + "parameterTypes": [] + }, + { + "name": "viewControllerHandlerMapping", + "parameterTypes": [ + "org.springframework.format.support.FormattingConversionService", + "org.springframework.web.servlet.resource.ResourceUrlProvider" + ] + } + ] + }, + { + "type": "org.springframework.web.servlet.config.annotation.WebMvcConfigurer" + }, + { + "type": "org.springframework.web.servlet.function.support.HandlerFunctionAdapter" + }, + { + "type": "org.springframework.web.servlet.function.support.RouterFunctionMapping" + }, + { + "type": "org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping" + }, + { + "type": "org.springframework.web.servlet.handler.AbstractHandlerMapping" + }, + { + "type": "org.springframework.web.servlet.handler.AbstractHandlerMethodMapping" + }, + { + "type": "org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$EmptyHandler" + }, + { + "type": "org.springframework.web.servlet.handler.AbstractUrlHandlerMapping" + }, + { + "type": "org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" + }, + { + "type": "org.springframework.web.servlet.handler.HandlerExceptionResolverComposite" + }, + { + "type": "org.springframework.web.servlet.handler.HandlerMappingIntrospector" + }, + { + "type": "org.springframework.web.servlet.handler.MatchableHandlerMapping" + }, + { + "type": "org.springframework.web.servlet.handler.SimpleUrlHandlerMapping" + }, + { + "type": "org.springframework.web.servlet.i18n.AbstractLocaleResolver" + }, + { + "type": "org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver" + }, + { + "type": "org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter" + }, + { + "type": "org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" + }, + { + "type": "org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter" + }, + { + "type": "org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping" + }, + { + "type": "org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping$HttpOptionsHandler" + }, + { + "type": "org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" + }, + { + "type": "org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" + }, + { + "type": "org.springframework.web.servlet.resource.ResourceUrlProvider" + }, + { + "type": "org.springframework.web.servlet.support.AbstractFlashMapManager" + }, + { + "type": "org.springframework.web.servlet.support.RequestDataValueProcessor" + }, + { + "type": "org.springframework.web.servlet.support.SessionFlashMapManager" + }, + { + "type": "org.springframework.web.servlet.support.WebContentGenerator" + }, + { + "type": "org.springframework.web.servlet.view.AbstractCachingViewResolver" + }, + { + "type": "org.springframework.web.servlet.view.BeanNameViewResolver" + }, + { + "type": "org.springframework.web.servlet.view.ContentNegotiatingViewResolver" + }, + { + "type": "org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator" + }, + { + "type": "org.springframework.web.servlet.view.InternalResourceViewResolver" + }, + { + "type": "org.springframework.web.servlet.view.UrlBasedViewResolver" + }, + { + "type": "org.springframework.web.servlet.view.ViewResolverComposite" + }, + { + "type": "org.springframework.web.util.UrlPathHelper" + }, + { + "type": "org.springframework.web.util.pattern.PathPatternParser" + }, + { + "type": "org.webjars.WebJarAssetLocator" + }, + { + "type": "org.webjars.WebJarVersionLocator" + }, + { + "type": "org.yaml.snakeyaml.Yaml" + }, + { + "type": "reactor.core.publisher.Flux" + }, + { + "type": "reactor.core.publisher.Mono" + }, + { + "type": "reactor.netty.http.client.HttpClient" + }, + { + "type": "sun.management.VMManagementImpl", + "jniAccessible": true, + "fields": [ + { + "name": "compTimeMonitoringSupport" + }, + { + "name": "currentThreadCpuTimeSupport" + }, + { + "name": "objectMonitorUsageSupport" + }, + { + "name": "otherThreadCpuTimeSupport" + }, + { + "name": "remoteDiagnosticCommandsSupport" + }, + { + "name": "synchronizerUsageSupport" + }, + { + "name": "threadAllocatedMemorySupport" + }, + { + "name": "threadContentionMonitoringSupport" + } + ] + }, + { + "type": "sun.reflect.ReflectionFactory", + "methods": [ + { + "name": "getReflectionFactory", + "parameterTypes": [] + }, + { + "name": "newConstructorForSerialization", + "parameterTypes": [ + "java.lang.Class", + "java.lang.reflect.Constructor" + ] + } + ] + }, + { + "type": "sun.security.pkcs12.PKCS12KeyStore", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.DSA$SHA224withDSA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.DSA$SHA256withDSA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.NativePRNG", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.security.SecureRandomParameters" + ] + } + ] + }, + { + "type": "sun.security.provider.SHA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.SHA2$SHA224", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.SHA2$SHA256", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.SHA5$SHA384", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.SHA5$SHA512", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.X509Factory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.rsa.PSSParameters", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.rsa.RSAKeyFactory$Legacy", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.rsa.RSAPSSSignature", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.rsa.RSASignature$SHA224withRSA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.ssl.KeyManagerFactoryImpl$SunX509", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.ssl.SSLContextImpl$DefaultSSLContext", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.x509.AuthorityInfoAccessExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.AuthorityKeyIdentifierExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.BasicConstraintsExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.CRLDistributionPointsExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.CertificatePoliciesExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.ExtendedKeyUsageExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.KeyUsageExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.NetscapeCertTypeExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.PrivateKeyUsageExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.SubjectKeyIdentifierExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.text.resources.cldr.FormatData" + }, + { + "type": "sun.text.resources.cldr.FormatData_en" + }, + { + "type": "sun.text.resources.cldr.FormatData_en_US" + }, + { + "type": "sun.util.resources.cldr.CalendarData" + }, + { + "type": "sun.util.resources.cldr.TimeZoneNames" + }, + { + "type": "sun.util.resources.cldr.TimeZoneNames_en" + }, + { + "type": "sun.util.resources.cldr.TimeZoneNames_en_US" + }, + { + "type": "tools.jackson.core.TokenStreamFactory" + }, + { + "type": "tools.jackson.core.TreeCodec" + }, + { + "type": "tools.jackson.core.Versioned" + }, + { + "type": "tools.jackson.core.base.DecorableTSFactory" + }, + { + "type": "tools.jackson.core.base.TextualTSFactory" + }, + { + "type": "tools.jackson.core.json.JsonFactory" + }, + { + "type": "tools.jackson.core.util.Snapshottable" + }, + { + "type": "tools.jackson.databind.JacksonModule" + }, + { + "type": "tools.jackson.databind.ObjectMapper" + }, + { + "type": "tools.jackson.databind.cfg.MapperBuilder" + }, + { + "type": "tools.jackson.databind.deser.Deserializers[]" + }, + { + "type": "tools.jackson.databind.deser.KeyDeserializers[]" + }, + { + "type": "tools.jackson.databind.deser.ValueInstantiators[]" + }, + { + "type": "tools.jackson.databind.json.JsonMapper" + }, + { + "type": "tools.jackson.databind.json.JsonMapper$Builder" + }, + { + "type": "tools.jackson.databind.module.SimpleModule" + }, + { + "type": "tools.jackson.databind.ser.Serializers[]" + }, + { + "type": "tools.jackson.dataformat.cbor.CBORMapper" + }, + { + "type": "tools.jackson.dataformat.smile.SmileMapper" + }, + { + "type": "tools.jackson.dataformat.xml.XmlMapper" + }, + { + "type": "tools.jackson.dataformat.yaml.YAMLMapper" + }, + { + "type": { + "proxy": [ + "java.lang.reflect.ParameterizedType", + "org.springframework.core.SerializableTypeWrapper$SerializableTypeProxy", + "java.io.Serializable" + ] + } + }, + { + "type": { + "proxy": [ + "java.lang.reflect.TypeVariable", + "org.springframework.core.SerializableTypeWrapper$SerializableTypeProxy", + "java.io.Serializable" + ] + } + }, + { + "type": { + "proxy": [ + "java.lang.reflect.WildcardType", + "org.springframework.core.SerializableTypeWrapper$SerializableTypeProxy", + "java.io.Serializable" + ] + } + }, + { + "type": { + "proxy": [ + "java.sql.Connection" + ] + } + }, + { + "type": { + "proxy": [ + "org.springframework.beans.factory.annotation.Qualifier" + ] + } + }, + { + "type": { + "proxy": [ + "org.springframework.boot.context.properties.ConfigurationProperties" + ] + } + }, + { + "type": { + "proxy": [ + "org.springframework.context.event.EventListener" + ] + } + }, + { + "type": { + "proxy": [ + "org.springframework.web.bind.annotation.ControllerAdvice" + ] + } + }, + { + "type": { + "proxy": [ + "org.springframework.web.bind.annotation.ExceptionHandler" + ] + } + }, + { + "type": { + "proxy": [ + "org.springframework.web.bind.annotation.PathVariable" + ] + } + }, + { + "type": { + "proxy": [ + "org.springframework.web.bind.annotation.RequestMapping" + ] + } + }, + { + "type": { + "lambda": { + "declaringClass": "org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$ClassProxyingConfiguration", + "interfaces": [ + "org.springframework.beans.factory.config.BeanFactoryPostProcessor" + ] + } + } + }, + { + "type": { + "lambda": { + "declaringClass": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$BootstrapExecutorConfiguration", + "interfaces": [ + "org.springframework.beans.factory.config.BeanFactoryPostProcessor" + ] + } + } + }, + { + "type": { + "lambda": { + "declaringClass": "org.springframework.boot.jdbc.autoconfigure.DataSourcePoolMetadataProvidersConfiguration$HikariPoolDataSourceMetadataProviderConfiguration", + "interfaces": [ + "org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider" + ] + } + } + }, + { + "type": { + "lambda": { + "declaringClass": "org.springframework.boot.validation.beanvalidation.MethodValidationExcludeFilter", + "interfaces": [ + "org.springframework.boot.validation.beanvalidation.MethodValidationExcludeFilter" + ] + } + } + } + ], + "resources": [ + { + "glob": "META-INF/build-info.properties" + }, + { + "glob": "META-INF/resources/index.html" + }, + { + "glob": "META-INF/services/ch.qos.logback.classic.spi.Configurator" + }, + { + "glob": "META-INF/services/io.jsonwebtoken.io.Deserializer" + }, + { + "glob": "META-INF/services/jakarta.el.ExpressionFactory" + }, + { + "glob": "META-INF/services/jakarta.validation.ConstraintValidator" + }, + { + "glob": "META-INF/services/jakarta.validation.spi.ValidationProvider" + }, + { + "glob": "META-INF/services/jakarta.validation.valueextraction.ValueExtractor" + }, + { + "glob": "META-INF/services/java.net.spi.InetAddressResolverProvider" + }, + { + "glob": "META-INF/services/java.net.spi.URLStreamHandlerProvider" + }, + { + "glob": "META-INF/services/java.nio.channels.spi.SelectorProvider" + }, + { + "glob": "META-INF/services/java.sql.Driver" + }, + { + "glob": "META-INF/services/java.time.zone.ZoneRulesProvider" + }, + { + "glob": "META-INF/services/java.util.spi.ResourceBundleControlProvider" + }, + { + "glob": "META-INF/services/org.apache.commons.logging.LogFactory" + }, + { + "glob": "META-INF/services/org.apache.juli.logging.Log" + }, + { + "glob": "META-INF/services/org.apache.logging.log4j.util.PropertySource" + }, + { + "glob": "META-INF/services/org.flywaydb.core.extensibility.Plugin" + }, + { + "glob": "META-INF/services/org.slf4j.spi.SLF4JServiceProvider" + }, + { + "glob": "META-INF/services/tools.jackson.databind.JacksonModule" + }, + { + "glob": "META-INF/spring-autoconfigure-metadata.properties" + }, + { + "glob": "META-INF/spring.components" + }, + { + "glob": "META-INF/spring.factories" + }, + { + "glob": "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports" + }, + { + "glob": "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.replacements" + }, + { + "glob": "META-INF/validation.xml" + }, + { + "glob": "application-default.properties" + }, + { + "glob": "application-default.xml" + }, + { + "glob": "application-default.yaml" + }, + { + "glob": "application-default.yml" + }, + { + "glob": "application.properties" + }, + { + "glob": "application.xml" + }, + { + "glob": "application.yaml" + }, + { + "glob": "application.yml" + }, + { + "glob": "banner.txt" + }, + { + "glob": "br/com/tasknoteapp/server" + }, + { + "glob": "br/com/tasknoteapp/server/config/CloudNativeConfig.class" + }, + { + "glob": "br/com/tasknoteapp/server/config/CorsConfig.class" + }, + { + "glob": "br/com/tasknoteapp/server/config/SecurityConfig.class" + }, + { + "glob": "br/com/tasknoteapp/server/controller/AuthenticationController.class" + }, + { + "glob": "br/com/tasknoteapp/server/controller/HealthController.class" + }, + { + "glob": "br/com/tasknoteapp/server/controller/HomeController.class" + }, + { + "glob": "br/com/tasknoteapp/server/controller/NoteController.class" + }, + { + "glob": "br/com/tasknoteapp/server/controller/PublicNoteController.class" + }, + { + "glob": "br/com/tasknoteapp/server/controller/RestExceptionController.class" + }, + { + "glob": "br/com/tasknoteapp/server/controller/TaskController.class" + }, + { + "glob": "br/com/tasknoteapp/server/controller/UserController.class" + }, + { + "glob": "br/com/tasknoteapp/server/controller/UserSessionController.class" + }, + { + "glob": "br/com/tasknoteapp/server/filter/HeaderVersionFilter.class" + }, + { + "glob": "br/com/tasknoteapp/server/filter/JwtAuthenticationFilter.class" + }, + { + "glob": "br/com/tasknoteapp/server/hint/JjwtRuntimeHints$JjwtHintsRegistrar.class" + }, + { + "glob": "br/com/tasknoteapp/server/hint/JjwtRuntimeHints.class" + }, + { + "glob": "br/com/tasknoteapp/server/repository/NoteRepository$NoteRowMapper.class" + }, + { + "glob": "br/com/tasknoteapp/server/repository/NoteRepository.class" + }, + { + "glob": "br/com/tasknoteapp/server/repository/NoteUrlRepository$NoteUrlRowMapper.class" + }, + { + "glob": "br/com/tasknoteapp/server/repository/NoteUrlRepository.class" + }, + { + "glob": "br/com/tasknoteapp/server/repository/TagRepository$TagRowMapper.class" + }, + { + "glob": "br/com/tasknoteapp/server/repository/TagRepository.class" + }, + { + "glob": "br/com/tasknoteapp/server/repository/TaskRepository$TaskRowMapper.class" + }, + { + "glob": "br/com/tasknoteapp/server/repository/TaskRepository.class" + }, + { + "glob": "br/com/tasknoteapp/server/repository/TaskUrlRepository$TaskUrlRowMapper.class" + }, + { + "glob": "br/com/tasknoteapp/server/repository/TaskUrlRepository.class" + }, + { + "glob": "br/com/tasknoteapp/server/repository/UserPwdLimitRepository$UserPwdLimitRowMapper.class" + }, + { + "glob": "br/com/tasknoteapp/server/repository/UserPwdLimitRepository.class" + }, + { + "glob": "br/com/tasknoteapp/server/repository/UserRepository$UserRowMapper.class" + }, + { + "glob": "br/com/tasknoteapp/server/repository/UserRepository.class" + }, + { + "glob": "br/com/tasknoteapp/server/service/AppVersionService.class" + }, + { + "glob": "br/com/tasknoteapp/server/service/AuthService.class" + }, + { + "glob": "br/com/tasknoteapp/server/service/HomeService.class" + }, + { + "glob": "br/com/tasknoteapp/server/service/JwtService.class" + }, + { + "glob": "br/com/tasknoteapp/server/service/MailgunEmailService.class" + }, + { + "glob": "br/com/tasknoteapp/server/service/NoteService.class" + }, + { + "glob": "br/com/tasknoteapp/server/service/TaskService.class" + }, + { + "glob": "br/com/tasknoteapp/server/service/UserService.class" + }, + { + "glob": "br/com/tasknoteapp/server/service/UserSessionService.class" + }, + { + "glob": "br/com/tasknoteapp/server/service/impl/JwtServiceImpl.class" + }, + { + "glob": "br/com/tasknoteapp/server/service/impl/UserServiceImpl.class" + }, + { + "glob": "br/com/tasknoteapp/server/util/AuthUtil.class" + }, + { + "glob": "ch/qos/logback/classic/logback-classic-version.properties" + }, + { + "glob": "ch/qos/logback/core/logback-core-version.properties" + }, + { + "glob": "commons-logging.properties" + }, + { + "glob": "config/application-default.properties" + }, + { + "glob": "config/application-default.xml" + }, + { + "glob": "config/application-default.yaml" + }, + { + "glob": "config/application-default.yml" + }, + { + "glob": "config/application.properties" + }, + { + "glob": "config/application.xml" + }, + { + "glob": "config/application.yaml" + }, + { + "glob": "config/application.yml" + }, + { + "glob": "data-all.sql" + }, + { + "glob": "data.sql" + }, + { + "glob": "db/callback" + }, + { + "glob": "db/migration" + }, + { + "glob": "db/migration/V202209041850__schema_and_user_creation.sql" + }, + { + "glob": "db/migration/V202409141732__task_table.sql" + }, + { + "glob": "db/migration/V202409201235__note_table.sql" + }, + { + "glob": "db/migration/V202409201638__update_note_table.sql" + }, + { + "glob": "db/migration/V202410081427__add_last_update_task.sql" + }, + { + "glob": "db/migration/V202410091821__add_user_pwd_limit.sql" + }, + { + "glob": "db/migration/V202411012109__update_task_table.sql" + }, + { + "glob": "db/migration/V202411221049__increase_notes_size.sql" + }, + { + "glob": "db/migration/V202501031704__add_tags.sql" + }, + { + "glob": "db/migration/V202501041332__update_task_pk.sql" + }, + { + "glob": "db/migration/V202503042111__add_user_info.sql" + }, + { + "glob": "db/migration/V202503052101__add_task_chart.sql" + }, + { + "glob": "db/migration/V202503071059__drop_taskdone_fk.sql" + }, + { + "glob": "db/migration/V202503080944__create_note_created_table.sql" + }, + { + "glob": "db/migration/V202504221604__add_email_confirmation.sql" + }, + { + "glob": "db/migration/V202505230842__clean_tables.sql" + }, + { + "glob": "db/migration/V202505280632__add_user_lang.sql" + }, + { + "glob": "db/migration/V202602281250__add_note_sharing.sql" + }, + { + "glob": "db/migration/V202604161000__add_user_last_password_change.sql" + }, + { + "glob": "db/migration/V202605171000__add_user_last_login.sql" + }, + { + "glob": "db/migration/V202605211000__add_multiple_tags.sql" + }, + { + "glob": "db/migration/V202606291000__restore_notes_description_check.sql" + }, + { + "glob": "db/migration/V202607231000__rename_task_done_to_completed.sql" + }, + { + "glob": "db/migration/V202607291800__add_note_archived.sql" + }, + { + "glob": "db/migration/V202608311200__add_user_theme.sql" + }, + { + "glob": "db/migration/V202609171142__drop_tag_from_notes_table.sql" + }, + { + "glob": "git.properties" + }, + { + "glob": "jakarta/servlet/Filter.class" + }, + { + "glob": "jakarta/servlet/LocalStrings.properties" + }, + { + "glob": "jakarta/servlet/LocalStrings_en.properties" + }, + { + "glob": "jakarta/servlet/http/LocalStrings.properties" + }, + { + "glob": "jakarta/servlet/http/LocalStrings_en.properties" + }, + { + "glob": "jndi.properties" + }, + { + "glob": "log4j2.StatusLogger.properties" + }, + { + "glob": "log4j2.component.properties" + }, + { + "glob": "log4j2.system.properties" + }, + { + "glob": "logback-spring.groovy" + }, + { + "glob": "logback-spring.xml" + }, + { + "glob": "logback-test-spring.groovy" + }, + { + "glob": "logback-test-spring.xml" + }, + { + "glob": "logback-test.groovy" + }, + { + "glob": "logback-test.xml" + }, + { + "glob": "logback.groovy" + }, + { + "glob": "logback.xml" + }, + { + "glob": "messages.properties" + }, + { + "glob": "org/apache/catalina/authenticator/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/authenticator/jaspic/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/connector/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/core/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/core/RestrictedFilters.properties" + }, + { + "glob": "org/apache/catalina/core/RestrictedListeners.properties" + }, + { + "glob": "org/apache/catalina/core/RestrictedServlets.properties" + }, + { + "glob": "org/apache/catalina/deploy/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/loader/JdbcLeakPrevention.class" + }, + { + "glob": "org/apache/catalina/loader/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/mapper/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/mbeans/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/realm/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/session/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/startup/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/util/CharsetMapperDefault.properties" + }, + { + "glob": "org/apache/catalina/util/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/util/ServerInfo.properties" + }, + { + "glob": "org/apache/catalina/valves/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/webresources/LocalStrings.properties" + }, + { + "glob": "org/apache/coyote/LocalStrings.properties" + }, + { + "glob": "org/apache/coyote/http11/LocalStrings.properties" + }, + { + "glob": "org/apache/coyote/http11/filters/LocalStrings.properties" + }, + { + "glob": "org/apache/naming/LocalStrings.properties" + }, + { + "glob": "org/apache/naming/LocalStrings_en.properties" + }, + { + "glob": "org/apache/tomcat/jni/AprStatus.class" + }, + { + "glob": "org/apache/tomcat/util/LocalStrings.properties" + }, + { + "glob": "org/apache/tomcat/util/buf/LocalStrings.properties" + }, + { + "glob": "org/apache/tomcat/util/compat/LocalStrings.properties" + }, + { + "glob": "org/apache/tomcat/util/descriptor/web/LocalStrings.properties" + }, + { + "glob": "org/apache/tomcat/util/http/LocalStrings.properties" + }, + { + "glob": "org/apache/tomcat/util/http/parser/LocalStrings.properties" + }, + { + "glob": "org/apache/tomcat/util/modeler/LocalStrings.properties" + }, + { + "glob": "org/apache/tomcat/util/net/LocalStrings.properties" + }, + { + "glob": "org/apache/tomcat/util/scan/LocalStrings.properties" + }, + { + "glob": "org/apache/tomcat/util/threads/LocalStrings.properties" + }, + { + "glob": "org/apache/tomcat/websocket/LocalStrings.properties" + }, + { + "glob": "org/apache/tomcat/websocket/server/LocalStrings.properties" + }, + { + "glob": "org/flywaydb/core/internal/version.txt" + }, + { + "glob": "org/postgresql/driverconfig.properties" + }, + { + "glob": "org/springframework/aot/hint/annotation/Reflective.class" + }, + { + "glob": "org/springframework/aot/hint/annotation/RegisterReflection.class" + }, + { + "glob": "org/springframework/aot/hint/annotation/RegisterReflectionForBinding.class" + }, + { + "glob": "org/springframework/beans/factory/Aware.class" + }, + { + "glob": "org/springframework/beans/factory/BeanFactoryAware.class" + }, + { + "glob": "org/springframework/beans/factory/BeanNameAware.class" + }, + { + "glob": "org/springframework/beans/factory/DisposableBean.class" + }, + { + "glob": "org/springframework/beans/factory/InitializingBean.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/AutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/AutoConfigureAfter.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/AutoConfigureBefore.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/AutoConfigureOrder.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/admin/SpringApplicationAdminJmxAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/aop/AopAutoConfiguration$AspectJAutoProxyingConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/aop/AopAutoConfiguration$ClassProxyingConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/aop/AopAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/availability/ApplicationAvailabilityAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnBean.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnBooleanProperty.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnClass.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnMissingClass.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnNotWarDeployment.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnProperty.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnResource.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnSingleCandidate.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnWebApplication.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/context/ConfigurationPropertiesAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/context/LifecycleAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/context/MessageSourceAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/context/PropertyPlaceholderAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/info/ProjectInfoAutoConfiguration$GitResourceAvailableCondition.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/info/ProjectInfoAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/jmx/JmxAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/ssl/SslAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/DefaultTaskSchedulerConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$AsyncConfigurerConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$AsyncConfigurerWrapperConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$BootstrapExecutorConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$OnExecutorCondition$ExecutorBeanCondition.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$OnExecutorCondition$ModelCondition.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$OnExecutorCondition.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$SimpleAsyncTaskExecutorBuilderConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$TaskExecutorConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$TaskExecutorContextPropagationConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$ThreadPoolTaskExecutorBuilderConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskSchedulingConfigurations$SimpleAsyncTaskSchedulerBuilderConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskSchedulingConfigurations$TaskSchedulerConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskSchedulingConfigurations$ThreadPoolTaskSchedulerBuilderConfiguration.class" + }, + { + "glob": "org/springframework/boot/context/properties/EnableConfigurationProperties.class" + }, + { + "glob": "org/springframework/boot/context/properties/EnableConfigurationPropertiesRegistrar.class" + }, + { + "glob": "org/springframework/boot/flyway/autoconfigure/FlywayAutoConfiguration$Extension.class" + }, + { + "glob": "org/springframework/boot/flyway/autoconfigure/FlywayAutoConfiguration$FlywayAutoConfigurationRuntimeHints.class" + }, + { + "glob": "org/springframework/boot/flyway/autoconfigure/FlywayAutoConfiguration$FlywayConfiguration$OracleConfiguration.class" + }, + { + "glob": "org/springframework/boot/flyway/autoconfigure/FlywayAutoConfiguration$FlywayConfiguration$PostgresqlConfiguration.class" + }, + { + "glob": "org/springframework/boot/flyway/autoconfigure/FlywayAutoConfiguration$FlywayConfiguration$SqlServerConfiguration.class" + }, + { + "glob": "org/springframework/boot/flyway/autoconfigure/FlywayAutoConfiguration$FlywayConfiguration.class" + }, + { + "glob": "org/springframework/boot/flyway/autoconfigure/FlywayAutoConfiguration$FlywayDataSourceCondition$DataSourceBeanCondition.class" + }, + { + "glob": "org/springframework/boot/flyway/autoconfigure/FlywayAutoConfiguration$FlywayDataSourceCondition$FlywayUrlCondition.class" + }, + { + "glob": "org/springframework/boot/flyway/autoconfigure/FlywayAutoConfiguration$FlywayDataSourceCondition$JdbcConnectionDetailsCondition.class" + }, + { + "glob": "org/springframework/boot/flyway/autoconfigure/FlywayAutoConfiguration$FlywayDataSourceCondition.class" + }, + { + "glob": "org/springframework/boot/flyway/autoconfigure/FlywayAutoConfiguration$LocationResolver.class" + }, + { + "glob": "org/springframework/boot/flyway/autoconfigure/FlywayAutoConfiguration$OracleFlywayConfigurationCustomizer.class" + }, + { + "glob": "org/springframework/boot/flyway/autoconfigure/FlywayAutoConfiguration$PostgresqlFlywayConfigurationCustomizer.class" + }, + { + "glob": "org/springframework/boot/flyway/autoconfigure/FlywayAutoConfiguration$PropertiesFlywayConnectionDetails.class" + }, + { + "glob": "org/springframework/boot/flyway/autoconfigure/FlywayAutoConfiguration$SqlServerFlywayConfigurationCustomizer.class" + }, + { + "glob": "org/springframework/boot/flyway/autoconfigure/FlywayAutoConfiguration$StringOrNumberToMigrationVersionConverter.class" + }, + { + "glob": "org/springframework/boot/flyway/autoconfigure/FlywayAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/gson/autoconfigure$GsonAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/gson/autoconfigure/GsonAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/client/autoconfigure/HttpClientAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/client/autoconfigure/imperative/ImperativeHttpClientAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/client/autoconfigure/imperative/NotReactiveWebApplicationCondition$ReactiveWebApplication.class" + }, + { + "glob": "org/springframework/boot/http/client/autoconfigure/imperative/NotReactiveWebApplicationCondition.class" + }, + { + "glob": "org/springframework/boot/http/client/autoconfigure/service/HttpServiceClientPropertiesAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration$GsonHttpConvertersCustomizer.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration$GsonHttpMessageConverterConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration$JacksonAndJsonbUnavailableCondition$Jackson2Available.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration$JacksonAndJsonbUnavailableCondition$JacksonAvailable.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration$JacksonAndJsonbUnavailableCondition$JsonbPreferred.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration$JacksonAndJsonbUnavailableCondition.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration$PreferGsonOrJacksonAndJsonbUnavailableCondition$GsonPreferred.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration$PreferGsonOrJacksonAndJsonbUnavailableCondition$JacksonJsonbUnavailable.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration$PreferGsonOrJacksonAndJsonbUnavailableCondition.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfiguration$NotReactiveWebApplicationCondition$ReactiveWebApplication.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfiguration$NotReactiveWebApplicationCondition.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfiguration$StringHttpMessageConverterConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfiguration$StringHttpMessageConvertersCustomizer.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration$Jackson2JsonMessageConvertersCustomizer.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration$Jackson2XmlMessageConvertersCustomizer.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration$MappingJackson2HttpMessageConverterConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration$MappingJackson2XmlHttpMessageConverterConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration$PreferJackson2OrJacksonUnavailableCondition.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/JacksonHttpMessageConvertersConfiguration$JacksonJsonHttpMessageConverterConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/JacksonHttpMessageConvertersConfiguration$JacksonJsonHttpMessageConvertersCustomizer.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/JacksonHttpMessageConvertersConfiguration$JacksonXmlHttpMessageConverterConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/JacksonHttpMessageConvertersConfiguration$JacksonXmlHttpMessageConvertersCustomizer.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/JacksonHttpMessageConvertersConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/JsonbHttpMessageConvertersConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/KotlinSerializationHttpMessageConvertersConfiguration.class" + }, + { + "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$AbstractFactoryBuilderCustomizer.class" + }, + { + "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$AbstractMapperBuilderCustomizer.class" + }, + { + "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$CborConfiguration.class" + }, + { + "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$JacksonAutoConfigurationRuntimeHints.class" + }, + { + "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$JacksonJsonCustomizerConfiguration$StandardJsonFactoryBuilderCustomizer.class" + }, + { + "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$JacksonJsonCustomizerConfiguration$StandardJsonMapperBuilderCustomizer.class" + }, + { + "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$JacksonJsonCustomizerConfiguration.class" + }, + { + "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$JacksonMixinConfiguration.class" + }, + { + "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$JsonProblemDetailsConfiguration$ProblemDetailJsonMapperBuilderCustomizer.class" + }, + { + "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$JsonProblemDetailsConfiguration.class" + }, + { + "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$XmlConfiguration.class" + }, + { + "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/jackson2/autoconfigure$Jackson2AutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/jackson2/autoconfigure/Jackson2AutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceAutoConfiguration$EmbeddedDatabaseCondition.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceAutoConfiguration$EmbeddedDatabaseConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceAutoConfiguration$PooledDataSourceAvailableCondition.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceAutoConfiguration$PooledDataSourceCondition$ExplicitType.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceAutoConfiguration$PooledDataSourceCondition$PooledDataSourceAvailable.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceAutoConfiguration$PooledDataSourceCondition.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceAutoConfiguration$PooledDataSourceConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceCheckpointRestoreConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceConfiguration$Dbcp2.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceConfiguration$Generic.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceConfiguration$Hikari.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceConfiguration$OracleUcp.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceConfiguration$Tomcat.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceInitializationAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceJmxConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourcePoolMetadataProvidersConfiguration$CommonsDbcp2PoolDataSourceMetadataProviderConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourcePoolMetadataProvidersConfiguration$HikariDataSourcePoolMetadataRuntimeHints.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourcePoolMetadataProvidersConfiguration$HikariPoolDataSourceMetadataProviderConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourcePoolMetadataProvidersConfiguration$OracleUcpPoolDataSourceMetadataProviderConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourcePoolMetadataProvidersConfiguration$TomcatDataSourcePoolMetadataProviderConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourcePoolMetadataProvidersConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceTransactionManagerAutoConfiguration$JdbcTransactionManagerConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceTransactionManagerAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/JdbcClientAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/JdbcTemplateAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/JdbcTemplateConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/JndiDataSourceAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/LazyConnectionDataSourceConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/NamedParameterJdbcTemplateConfiguration.class" + }, + { + "glob": "org/springframework/boot/jsonb/autoconfigure$JsonbAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/jsonb/autoconfigure/JsonbAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/kotlinx/serialization/json/autoconfigure$KotlinxSerializationJsonAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/kotlinx/serialization/json/autoconfigure/KotlinxSerializationJsonAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/persistence/autoconfigure/PersistenceExceptionTranslationAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/restclient/autoconfigure/NotReactiveWebApplicationCondition$ReactiveWebApplication.class" + }, + { + "glob": "org/springframework/boot/restclient/autoconfigure/NotReactiveWebApplicationCondition.class" + }, + { + "glob": "org/springframework/boot/restclient/autoconfigure/RestClientAutoConfiguration$HttpMessageConvertersConfiguration.class" + }, + { + "glob": "org/springframework/boot/restclient/autoconfigure/RestClientAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/restclient/autoconfigure/RestTemplateAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/restclient/autoconfigure/service/HttpServiceClientAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/rsocket/autoconfigure$RSocketMessagingAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/rsocket/autoconfigure/RSocketMessagingAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/MissingAlternativeUserDetailsManagerOrUserPropertiesConfigured$MissingAlternative.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/MissingAlternativeUserDetailsManagerOrUserPropertiesConfigured$NameConfigured.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/MissingAlternativeUserDetailsManagerOrUserPropertiesConfigured$PasswordConfigured.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/MissingAlternativeUserDetailsManagerOrUserPropertiesConfigured.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/ReactiveUserDetailsServiceAutoConfiguration$RSocketEnabledOrReactiveWebApplication$RSocketSecurityEnabledCondition.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/ReactiveUserDetailsServiceAutoConfiguration$RSocketEnabledOrReactiveWebApplication$ReactiveWebApplicationCondition.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/ReactiveUserDetailsServiceAutoConfiguration$RSocketEnabledOrReactiveWebApplication.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/ReactiveUserDetailsServiceAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/SecurityAutoConfiguration$SecurityDataConfiguration.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/SecurityAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/UserDetailsServiceAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/web/servlet/ConditionalOnDefaultWebSecurity.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/web/servlet/DefaultWebSecurityCondition$Beans.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/web/servlet/DefaultWebSecurityCondition$Classes.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/web/servlet/DefaultWebSecurityCondition.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/web/servlet/SecurityFilterAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/web/servlet/ServletWebSecurityAutoConfiguration$EnableWebSecurityConfiguration.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/web/servlet/ServletWebSecurityAutoConfiguration$PathPatternRequestMatcherBuilderConfiguration.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/web/servlet/ServletWebSecurityAutoConfiguration$SecurityFilterChainConfiguration.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/web/servlet/ServletWebSecurityAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/servlet/autoconfigure/HttpEncodingAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/servlet/autoconfigure/MultipartAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/sql/autoconfigure/init/ConditionalOnSqlInitialization.class" + }, + { + "glob": "org/springframework/boot/sql/init/dependency/DatabaseInitializationDependencyConfigurer.class" + }, + { + "glob": "org/springframework/boot/tomcat/autoconfigure/TomcatWebServerConfiguration$TomcatWebSocketConfiguration.class" + }, + { + "glob": "org/springframework/boot/tomcat/autoconfigure/TomcatWebServerConfiguration.class" + }, + { + "glob": "org/springframework/boot/tomcat/autoconfigure/servlet/TomcatServletWebServerAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/transaction/autoconfigure/TransactionAutoConfiguration$AspectJTransactionManagementConfiguration.class" + }, + { + "glob": "org/springframework/boot/transaction/autoconfigure/TransactionAutoConfiguration$EnableTransactionManagementConfiguration$CglibAutoProxyConfiguration.class" + }, + { + "glob": "org/springframework/boot/transaction/autoconfigure/TransactionAutoConfiguration$EnableTransactionManagementConfiguration$JdkDynamicAutoProxyConfiguration.class" + }, + { + "glob": "org/springframework/boot/transaction/autoconfigure/TransactionAutoConfiguration$EnableTransactionManagementConfiguration.class" + }, + { + "glob": "org/springframework/boot/transaction/autoconfigure/TransactionAutoConfiguration$TransactionTemplateConfiguration.class" + }, + { + "glob": "org/springframework/boot/transaction/autoconfigure/TransactionAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/transaction/autoconfigure/TransactionManagerCustomizationAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/validation/autoconfigure/PrimaryDefaultValidatorPostProcessor.class" + }, + { + "glob": "org/springframework/boot/validation/autoconfigure/ValidationAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/web/server/autoconfigure/servlet/ServletWebServerConfiguration$BeanPostProcessorsRegistrar.class" + }, + { + "glob": "org/springframework/boot/web/server/autoconfigure/servlet/ServletWebServerConfiguration.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/DispatcherServletAutoConfiguration$DefaultDispatcherServletCondition.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/DispatcherServletAutoConfiguration$DispatcherServletConfiguration.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/DispatcherServletAutoConfiguration$DispatcherServletRegistrationCondition.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/DispatcherServletAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$MvcValidatorRuntimeHints.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$ProblemDetailsErrorHandlingConfiguration.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$ResourceChainCustomizerConfiguration.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$ResourceChainResourceHandlerRegistrationCustomizer.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$ResourceHandlerRegistrationCustomizer.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$WelcomePageHandlerMappingFactory.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration$DefaultErrorViewResolverConfiguration.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration$ErrorPageCustomizer.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration$ErrorTemplateMissingCondition.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration$PreserveErrorControllerTargetClassPostProcessor.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration$StaticView.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/webmvc/test/autoconfigure$MockMvcAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/webmvc/test/autoconfigure/MockMvcAutoConfiguration.class" + }, + { + "glob": "org/springframework/context/ApplicationContextAware.class" + }, + { + "glob": "org/springframework/context/EmbeddedValueResolverAware.class" + }, + { + "glob": "org/springframework/context/EnvironmentAware.class" + }, + { + "glob": "org/springframework/context/ResourceLoaderAware.class" + }, + { + "glob": "org/springframework/context/annotation/AdviceModeImportSelector.class" + }, + { + "glob": "org/springframework/context/annotation/AutoProxyRegistrar.class" + }, + { + "glob": "org/springframework/context/annotation/Conditional.class" + }, + { + "glob": "org/springframework/context/annotation/Configuration.class" + }, + { + "glob": "org/springframework/context/annotation/Import.class" + }, + { + "glob": "org/springframework/context/annotation/ImportAware.class" + }, + { + "glob": "org/springframework/context/annotation/ImportBeanDefinitionRegistrar.class" + }, + { + "glob": "org/springframework/context/annotation/ImportRuntimeHints.class" + }, + { + "glob": "org/springframework/context/annotation/Role.class" + }, + { + "glob": "org/springframework/core/annotation/Order.class" + }, + { + "glob": "org/springframework/core/env/EnvironmentCapable.class" + }, + { + "glob": "org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration$AuthenticationManagerDelegator.class" + }, + { + "glob": "org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration$DefaultPasswordEncoderAuthenticationManagerBuilder.class" + }, + { + "glob": "org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration$EnableGlobalAuthenticationAutowiredConfigurer.class" + }, + { + "glob": "org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration$LazyPasswordEncoder.class" + }, + { + "glob": "org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration.class" + }, + { + "glob": "org/springframework/security/config/annotation/authentication/configuration/EnableGlobalAuthentication.class" + }, + { + "glob": "org/springframework/security/config/annotation/configuration/ObjectPostProcessorConfiguration.class" + }, + { + "glob": "org/springframework/security/config/annotation/web/configuration/EnableWebSecurity.class" + }, + { + "glob": "org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration$DefaultPasswordEncoderAuthenticationManagerBuilder.class" + }, + { + "glob": "org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration$LazyPasswordEncoder.class" + }, + { + "glob": "org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration.class" + }, + { + "glob": "org/springframework/security/config/annotation/web/configuration/OAuth2ImportSelector.class" + }, + { + "glob": "org/springframework/security/config/annotation/web/configuration/ObservationConfiguration.class" + }, + { + "glob": "org/springframework/security/config/annotation/web/configuration/ObservationImportSelector.class" + }, + { + "glob": "org/springframework/security/config/annotation/web/configuration/SpringWebMvcImportSelector.class" + }, + { + "glob": "org/springframework/security/config/annotation/web/configuration/WebMvcSecurityConfiguration.class" + }, + { + "glob": "org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration$AnnotationAwareOrderComparator.class" + }, + { + "glob": "org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration$CompositeFilterChainProxy.class" + }, + { + "glob": "org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.class" + }, + { + "glob": "org/springframework/transaction/annotation/AbstractTransactionManagementConfiguration.class" + }, + { + "glob": "org/springframework/transaction/annotation/EnableTransactionManagement.class" + }, + { + "glob": "org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.class" + }, + { + "glob": "org/springframework/transaction/annotation/TransactionManagementConfigurationSelector.class" + }, + { + "glob": "org/springframework/web/bind/annotation/ControllerAdvice.class" + }, + { + "glob": "org/springframework/web/bind/annotation/Mapping.class" + }, + { + "glob": "org/springframework/web/bind/annotation/RequestMapping.class" + }, + { + "glob": "org/springframework/web/bind/annotation/ResponseBody.class" + }, + { + "glob": "org/springframework/web/bind/annotation/RestController.class" + }, + { + "glob": "org/springframework/web/bind/annotation/RestControllerAdvice.class" + }, + { + "glob": "org/springframework/web/context/ServletContextAware.class" + }, + { + "glob": "org/springframework/web/filter/GenericFilterBean$FilterConfigPropertyValues.class" + }, + { + "glob": "org/springframework/web/filter/GenericFilterBean.class" + }, + { + "glob": "org/springframework/web/filter/OncePerRequestFilter.class" + }, + { + "glob": "org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration.class" + }, + { + "glob": "org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport$NoOpValidator.class" + }, + { + "glob": "org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.class" + }, + { + "glob": "org/springframework/web/servlet/config/annotation/WebMvcConfigurer.class" + }, + { + "glob": "public/index.html" + }, + { + "glob": "resources/index.html" + }, + { + "glob": "schema-all.sql" + }, + { + "glob": "schema.sql" + }, + { + "glob": "spring.properties" + }, + { + "glob": "static/index.html" + }, + { + "module": "java.base", + "glob": "jdk/internal/icu/impl/data/icudt76b/nfkc.nrm" + }, + { + "bundle": "jakarta.servlet.LocalStrings" + }, + { + "bundle": "jakarta.servlet.http.LocalStrings" + }, + { + "bundle": "org.apache.catalina.authenticator.LocalStrings" + }, + { + "bundle": "org.apache.catalina.authenticator.jaspic.LocalStrings" + }, + { + "bundle": "org.apache.catalina.connector.LocalStrings" + }, + { + "bundle": "org.apache.catalina.core.LocalStrings" + }, + { + "bundle": "org.apache.catalina.deploy.LocalStrings" + }, + { + "bundle": "org.apache.catalina.loader.LocalStrings" + }, + { + "bundle": "org.apache.catalina.mapper.LocalStrings" + }, + { + "bundle": "org.apache.catalina.mbeans.LocalStrings" + }, + { + "bundle": "org.apache.catalina.realm.LocalStrings" + }, + { + "bundle": "org.apache.catalina.session.LocalStrings" + }, + { + "bundle": "org.apache.catalina.startup.LocalStrings" + }, + { + "bundle": "org.apache.catalina.util.LocalStrings" + }, + { + "bundle": "org.apache.catalina.valves.LocalStrings" + }, + { + "bundle": "org.apache.catalina.webresources.LocalStrings" + }, + { + "bundle": "org.apache.coyote.LocalStrings" + }, + { + "bundle": "org.apache.coyote.http11.LocalStrings" + }, + { + "bundle": "org.apache.coyote.http11.filters.LocalStrings" + }, + { + "bundle": "org.apache.naming.LocalStrings" + }, + { + "bundle": "org.apache.tomcat.util.LocalStrings" + }, + { + "bundle": "org.apache.tomcat.util.buf.LocalStrings" + }, + { + "bundle": "org.apache.tomcat.util.compat.LocalStrings" + }, + { + "bundle": "org.apache.tomcat.util.descriptor.web.LocalStrings" + }, + { + "bundle": "org.apache.tomcat.util.http.LocalStrings" + }, + { + "bundle": "org.apache.tomcat.util.http.parser.LocalStrings" + }, + { + "bundle": "org.apache.tomcat.util.modeler.LocalStrings" + }, + { + "bundle": "org.apache.tomcat.util.net.LocalStrings" + }, + { + "bundle": "org.apache.tomcat.util.scan.LocalStrings" + }, + { + "bundle": "org.apache.tomcat.util.threads.LocalStrings" + }, + { + "bundle": "org.apache.tomcat.websocket.LocalStrings" + }, + { + "bundle": "org.apache.tomcat.websocket.server.LocalStrings" + } + ] +} \ No newline at end of file diff --git a/server/src/main/resources/application-native.yml b/server/src/main/resources/application-native.yml deleted file mode 100644 index 969f1a93..00000000 --- a/server/src/main/resources/application-native.yml +++ /dev/null @@ -1,43 +0,0 @@ -br: - com: - tasknote: - server: - jwt-secret: ${SECURITY_KEY} - target-env: ${TARGET_ENV:development} -cors: - allowed-origins: ${CORS_ALLOWED_ORIGINS:http://localhost} -logging: - level: - root: ${ROOT_LOG_LEVEL:INFO} - br.com.tasknoteapp: INFO - -mailgun: - api-key: ${MAILGUN_APIKEY:abc123456} - domain: tasknoteapp.dev.br - sender-email: no-reply@tasknoteapp.dev.br - -server: - port: 8585 - error: - include-message: never - servlet: - context-path: ${SERVER_SERVLET_CONTEXT_PATH:/server} -spring: - application: - name: tasknote-api - datasource: - driver-class-name: org.postgresql.Driver - password: ${POSTGRES_PASSWORD} - url: jdbc:postgresql://${POSTGRES_HOST:localhost}:${POSTGRES_PORT:5435}/${POSTGRES_DB:tasknote} - username: ${POSTGRES_USER:tasknoteuser} - flyway: - baseline-on-migrate: true - enabled: true - locations: classpath:db/migration - jpa: - database-platform: org.hibernate.dialect.PostgreSQLDialect - open-in-view: false - properties: - hibernate: - default_schema: tasknote - show-sql: false diff --git a/server/src/main/resources/application.yml b/server/src/main/resources/application.yml index 4360ec1f..cdb0c635 100644 --- a/server/src/main/resources/application.yml +++ b/server/src/main/resources/application.yml @@ -5,7 +5,7 @@ br: jwt-secret: ${SECURITY_KEY} target-env: ${TARGET_ENV:development} cors: - allowed-origins: ${CORS_ALLOWED_ORIGINS:http://localhost} + allowed-origins: ${CORS_ALLOWED_ORIGINS:https://flattop-depth-dropper.ngrok-free.dev} logging: level: root: ${ROOT_LOG_LEVEL:INFO} @@ -21,23 +21,16 @@ server: error: include-message: never servlet: - context-path: ${SERVER_SERVLET_CONTEXT_PATH:/server} + context-path: ${SERVER_SERVLET_CONTEXT_PATH:/} spring: application: name: tasknote-api datasource: driver-class-name: org.postgresql.Driver password: ${POSTGRES_PASSWORD} - url: jdbc:postgresql://${POSTGRES_HOST:localhost}:${POSTGRES_PORT:5435}/${POSTGRES_DB:tasknote} + url: jdbc:postgresql://${POSTGRES_HOST:localhost}:5432/${POSTGRES_DB:tasknote} username: ${POSTGRES_USER:tasknoteuser} flyway: baseline-on-migrate: true enabled: true locations: classpath:db/migration - jpa: - database-platform: org.hibernate.dialect.PostgreSQLDialect - open-in-view: false - properties: - hibernate: - default_schema: tasknote - show-sql: false diff --git a/server/src/main/resources/db/migration/V202609171142__drop_tag_from_notes_table.sql b/server/src/main/resources/db/migration/V202609171142__drop_tag_from_notes_table.sql new file mode 100644 index 00000000..8dcfbfaa --- /dev/null +++ b/server/src/main/resources/db/migration/V202609171142__drop_tag_from_notes_table.sql @@ -0,0 +1,2 @@ +ALTER TABLE tasknote.notes DROP COLUMN tag; +ALTER TABLE tasknote.tasks DROP COLUMN status; diff --git a/server/src/test/java/br/com/tasknoteapp/server/controller/HealthControllerTest.java b/server/src/test/java/br/com/tasknoteapp/server/controller/HealthControllerTest.java new file mode 100644 index 00000000..5dda5483 --- /dev/null +++ b/server/src/test/java/br/com/tasknoteapp/server/controller/HealthControllerTest.java @@ -0,0 +1,39 @@ +package br.com.tasknoteapp.server.controller; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import br.com.tasknoteapp.server.service.AppVersionService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +@SpringBootTest +@AutoConfigureMockMvc +class HealthControllerTest { + + @Autowired private MockMvc mockMvc; + + @MockitoBean private AppVersionService appVersionService; + + @Test + @DisplayName("Health check without authentication should succeed") + void health_withoutAuth_shouldSucceed() throws Exception { + when(appVersionService.getVersion()).thenReturn("test-version"); + + mockMvc + .perform(get("/health").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.application").value("UP")) + .andExpect(jsonPath("$.version").value("test-version")) + .andExpect(jsonPath("$.database.status").value("UP")) + .andReturn(); + } +} diff --git a/server/src/test/java/br/com/tasknoteapp/server/controller/UserControllerTest.java b/server/src/test/java/br/com/tasknoteapp/server/controller/UserControllerTest.java index 7a0b13c4..c88aeb77 100644 --- a/server/src/test/java/br/com/tasknoteapp/server/controller/UserControllerTest.java +++ b/server/src/test/java/br/com/tasknoteapp/server/controller/UserControllerTest.java @@ -12,7 +12,6 @@ import br.com.tasknoteapp.server.request.UserPatchRequest; import br.com.tasknoteapp.server.response.UserResponse; import br.com.tasknoteapp.server.service.AuthService; -import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -31,27 +30,6 @@ class UserControllerTest { @MockitoBean private AuthService authService; - @Test - @DisplayName("Get all users happy path should succeed") - @WithMockUser(username = "user@domain.com", password = "abcde123456A@") - void getAllUsers_happyPath_shouldSucceed() throws Exception { - UserResponse userResponse = - new UserResponse(1L, "John", "email@test.com", false, null, null, null, null, "light"); - when(authService.getAllUsers()).thenReturn(List.of(userResponse)); - - mockMvc - .perform( - get("/rest/users") - .with(csrf().asHeader()) - .header("Content-Type", MediaType.APPLICATION_JSON_VALUE) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$[0].userId").value(userResponse.userId())) - .andExpect(jsonPath("$[0].email").value(userResponse.email())) - .andExpect(jsonPath("$[0].admin").value(userResponse.admin())) - .andReturn(); - } - @Test @DisplayName("Get all users with 401 unauthorized request should fail") void getAllUsers_unauthorized_shouldFail() throws Exception { diff --git a/server/src/test/java/br/com/tasknoteapp/server/controller/UserSessionControllerTest.java b/server/src/test/java/br/com/tasknoteapp/server/controller/UserSessionControllerTest.java index 0c3f6c49..599671d2 100644 --- a/server/src/test/java/br/com/tasknoteapp/server/controller/UserSessionControllerTest.java +++ b/server/src/test/java/br/com/tasknoteapp/server/controller/UserSessionControllerTest.java @@ -7,7 +7,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import br.com.tasknoteapp.server.entity.UserEntity; +import br.com.tasknoteapp.server.entity.User; import br.com.tasknoteapp.server.response.JwtAuthenticationResponse; import br.com.tasknoteapp.server.response.UserResponse; import br.com.tasknoteapp.server.service.AuthService; @@ -68,7 +68,7 @@ void refresh_unauthorized_shouldFail() throws Exception { @DisplayName("Delete account happy path should succeed") @WithMockUser(username = "user@domain.com", password = "abcde123456A@") void deleteAccount_happyPath_shouldSucceed() throws Exception { - UserEntity user = new UserEntity(); + User user = new User(); user.setId(1L); UserResponse response = new UserResponse(1L, "John", "email@test.com", false, null, null, null, null, "light"); diff --git a/server/src/test/java/br/com/tasknoteapp/server/repository/RepositoryIntTest.java b/server/src/test/java/br/com/tasknoteapp/server/repository/RepositoryIntTest.java new file mode 100644 index 00000000..e64c5914 --- /dev/null +++ b/server/src/test/java/br/com/tasknoteapp/server/repository/RepositoryIntTest.java @@ -0,0 +1,30 @@ +package br.com.tasknoteapp.server.repository; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase; +import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase.Replace; +import org.springframework.boot.jdbc.test.autoconfigure.JdbcTest; +import org.springframework.context.annotation.Import; + +/** + * Composed annotation for repository integration tests running against H2 with JDBC named + * parameter templates. Registers the JDBC test slice and imports the repository beans, which are + * not picked up by component scanning in this slice. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@JdbcTest +@AutoConfigureTestDatabase(replace = Replace.NONE) +@Import({ + NoteRepository.class, + NoteUrlRepository.class, + TagRepository.class, + TaskRepository.class, + TaskUrlRepository.class, + UserPwdLimitRepository.class, + UserRepository.class +}) +@interface RepositoryIntTest {} diff --git a/server/src/test/java/br/com/tasknoteapp/server/repository/TaskRepositoryIntTest.java b/server/src/test/java/br/com/tasknoteapp/server/repository/TaskRepositoryIntTest.java index d47695e3..7e8afc2f 100644 --- a/server/src/test/java/br/com/tasknoteapp/server/repository/TaskRepositoryIntTest.java +++ b/server/src/test/java/br/com/tasknoteapp/server/repository/TaskRepositoryIntTest.java @@ -1,28 +1,17 @@ package br.com.tasknoteapp.server.repository; -import br.com.tasknoteapp.server.entity.TaskEntity; -import br.com.tasknoteapp.server.entity.UserEntity; +import br.com.tasknoteapp.server.entity.Task; +import br.com.tasknoteapp.server.entity.User; import java.util.List; import java.util.Optional; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest; -import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase; -import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase.Replace; import org.springframework.test.context.jdbc.Sql; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; -@DataJpaTest -@AutoConfigureTestDatabase(replace = Replace.NONE) +@RepositoryIntTest @Sql(scripts = {"classpath:sql/TaskRepositoryTest.sql"}) -@Transactional(propagation = Propagation.NOT_SUPPORTED) -@TestMethodOrder(MethodOrderer.OrderAnnotation.class) class TaskRepositoryIntTest { @Autowired private TaskRepository taskRepository; @@ -30,30 +19,28 @@ class TaskRepositoryIntTest { @Autowired private UserRepository userRepository; @Test - @Order(1) @DisplayName("Find all tasks by user id") void findAllByUser_idIntTest() { - List entities = taskRepository.findAllByUser_id(getUserId()); + List entities = taskRepository.findAllByUserId(getUserId()); Assertions.assertFalse(entities.isEmpty()); Assertions.assertEquals(2, entities.size()); - Assertions.assertEquals("Refactor", entities.get(0).getDescription()); - Assertions.assertEquals("Cleanup", entities.get(1).getDescription()); + Assertions.assertEquals("Refactor", entities.get(0).description()); + Assertions.assertEquals("Cleanup", entities.get(1).description()); } @Test - @Order(2) @DisplayName("Find all tasks by search term") void findAllBySearchTerm_intTest() { - List entities = taskRepository.findAllBySearchTerm("refactor", getUserId()); + List entities = taskRepository.findAllBySearchTerm("refactor", getUserId()); Assertions.assertFalse(entities.isEmpty()); Assertions.assertEquals(1, entities.size()); - Assertions.assertEquals("Refactor", entities.get(0).getDescription()); + Assertions.assertEquals("Refactor", entities.get(0).description()); } private Long getUserId() { - Optional user = userRepository.findByEmail("test@domain.com"); + Optional user = userRepository.findByEmail("test@domain.com"); if (user.isPresent()) { return user.get().getId(); } diff --git a/server/src/test/java/br/com/tasknoteapp/server/repository/TaskUrlRepositoryIntTest.java b/server/src/test/java/br/com/tasknoteapp/server/repository/TaskUrlRepositoryIntTest.java index 1d86ed8d..fef7fdd8 100644 --- a/server/src/test/java/br/com/tasknoteapp/server/repository/TaskUrlRepositoryIntTest.java +++ b/server/src/test/java/br/com/tasknoteapp/server/repository/TaskUrlRepositoryIntTest.java @@ -1,21 +1,17 @@ package br.com.tasknoteapp.server.repository; -import br.com.tasknoteapp.server.entity.TaskEntity; -import br.com.tasknoteapp.server.entity.TaskUrlEntity; -import br.com.tasknoteapp.server.entity.UserEntity; +import br.com.tasknoteapp.server.entity.Task; +import br.com.tasknoteapp.server.entity.TaskUrl; +import br.com.tasknoteapp.server.entity.User; import java.util.List; import java.util.Optional; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest; -import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase; -import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase.Replace; import org.springframework.test.context.jdbc.Sql; -@DataJpaTest -@AutoConfigureTestDatabase(replace = Replace.NONE) +@RepositoryIntTest @Sql(scripts = {"classpath:sql/TaskUrlRepositoryTest.sql"}) class TaskUrlRepositoryIntTest { @@ -30,22 +26,22 @@ class TaskUrlRepositoryIntTest { @Test @DisplayName("Delete all task url by task id") void deleteAllById_taskIdIntTest() { - Optional user = userRepository.findByEmail(USER_EMAIL); + Optional user = userRepository.findByEmail(USER_EMAIL); Assertions.assertFalse(user.isEmpty()); Long userId = user.get().getId(); - List tasks = taskRepository.findAllByUser_id(userId); + List tasks = taskRepository.findAllByUserId(userId); Assertions.assertFalse(tasks.isEmpty()); - Optional debianTask = - tasks.stream().filter(t -> t.getDescription().equals("Install Debian")).findFirst(); + Optional debianTask = + tasks.stream().filter(t -> t.description().equals("Install Debian")).findFirst(); Assertions.assertFalse(debianTask.isEmpty()); - Long taskId = debianTask.get().getId(); + Long taskId = debianTask.get().id(); taskUrlRepository.deleteAllById_taskId(taskId); - List taskUrls = taskUrlRepository.findAllById_taskId(taskId); + List taskUrls = taskUrlRepository.findAllById_taskId(taskId); Assertions.assertTrue(taskUrls.isEmpty()); } } diff --git a/server/src/test/java/br/com/tasknoteapp/server/repository/UserRepositoryIntTest.java b/server/src/test/java/br/com/tasknoteapp/server/repository/UserRepositoryIntTest.java index c9fa4211..e537fcd9 100644 --- a/server/src/test/java/br/com/tasknoteapp/server/repository/UserRepositoryIntTest.java +++ b/server/src/test/java/br/com/tasknoteapp/server/repository/UserRepositoryIntTest.java @@ -1,18 +1,14 @@ package br.com.tasknoteapp.server.repository; -import br.com.tasknoteapp.server.entity.UserEntity; +import br.com.tasknoteapp.server.entity.User; import java.util.Optional; import java.util.UUID; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest; -import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase; -import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase.Replace; import org.springframework.test.context.jdbc.Sql; -@DataJpaTest -@AutoConfigureTestDatabase(replace = Replace.NONE) +@RepositoryIntTest @Sql(scripts = {"classpath:sql/UserRepositoryTest.sql"}) class UserRepositoryIntTest { @@ -23,7 +19,7 @@ class UserRepositoryIntTest { @Test void findByEmailUuid_happyPath_shouldSucceed() { UUID uuid = UUID.fromString(UUID_CODE); - Optional user = userRepository.findByEmailUuid(uuid); + Optional user = userRepository.findByEmailUuid(uuid); Assertions.assertFalse(user.isEmpty()); } @@ -31,7 +27,7 @@ void findByEmailUuid_happyPath_shouldSucceed() { @Test void findByResetToken_happyPath_shouldSucceed() { String token = "abc123456"; - Optional user = userRepository.findByResetToken(token); + Optional user = userRepository.findByResetToken(token); Assertions.assertFalse(user.isEmpty()); } diff --git a/server/src/test/java/br/com/tasknoteapp/server/service/AccountDeletionAttemptSurvivesIntTest.java b/server/src/test/java/br/com/tasknoteapp/server/service/AccountDeletionAttemptSurvivesIntTest.java index 7259609f..d1509480 100644 --- a/server/src/test/java/br/com/tasknoteapp/server/service/AccountDeletionAttemptSurvivesIntTest.java +++ b/server/src/test/java/br/com/tasknoteapp/server/service/AccountDeletionAttemptSurvivesIntTest.java @@ -4,8 +4,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import br.com.tasknoteapp.server.entity.UserEntity; -import br.com.tasknoteapp.server.entity.UserPwdLimitEntity; +import br.com.tasknoteapp.server.entity.User; +import br.com.tasknoteapp.server.entity.UserPwdLimit; import br.com.tasknoteapp.server.exception.InvalidCredentialsException; import br.com.tasknoteapp.server.repository.UserPwdLimitRepository; import br.com.tasknoteapp.server.repository.UserRepository; @@ -46,7 +46,7 @@ class AccountDeletionAttemptSurvivesIntTest { @Autowired private TransactionTemplate transactionTemplate; - private UserEntity user; + private User user; @BeforeEach void setUp() { @@ -57,10 +57,10 @@ void setUp() { .ifPresent( u -> { userPwdLimitRepository.deleteAllForUser(u.getId()); - userRepository.deleteById(u.getId()); + userRepository.delete(u); })); - user = new UserEntity(); + user = new User(); user.setEmail(EMAIL); user.setPassword(passwordEncoder.encode(RAW_PASSWORD)); user.setAdmin(false); @@ -84,7 +84,7 @@ void tearDown() { .ifPresent( u -> { userPwdLimitRepository.deleteAllForUser(u.getId()); - userRepository.deleteById(u.getId()); + userRepository.delete(u); })); } @@ -102,7 +102,7 @@ void deleteAccount_wrongPassword_attemptShouldSurvive() { assertTrue(userRepository.findById(userId).isPresent()); - List attempts = + List attempts = userPwdLimitRepository.findTop3ByUser_idOrderByWhenHappenedDesc(userId); assertEquals(1, attempts.size()); } diff --git a/server/src/test/java/br/com/tasknoteapp/server/service/AccountDeletionIntTest.java b/server/src/test/java/br/com/tasknoteapp/server/service/AccountDeletionIntTest.java index 9d573e5d..d44f634b 100644 --- a/server/src/test/java/br/com/tasknoteapp/server/service/AccountDeletionIntTest.java +++ b/server/src/test/java/br/com/tasknoteapp/server/service/AccountDeletionIntTest.java @@ -1,18 +1,18 @@ package br.com.tasknoteapp.server.service; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import br.com.tasknoteapp.server.entity.NoteEntity; -import br.com.tasknoteapp.server.entity.NoteUrlEntity; -import br.com.tasknoteapp.server.entity.TagEntity; -import br.com.tasknoteapp.server.entity.TaskEntity; -import br.com.tasknoteapp.server.entity.UserEntity; -import br.com.tasknoteapp.server.entity.UserPwdLimitEntity; +import br.com.tasknoteapp.server.entity.Note; +import br.com.tasknoteapp.server.entity.NoteUrl; +import br.com.tasknoteapp.server.entity.Task; +import br.com.tasknoteapp.server.entity.User; +import br.com.tasknoteapp.server.entity.UserPwdLimit; import br.com.tasknoteapp.server.exception.InvalidCredentialsException; import br.com.tasknoteapp.server.exception.MaxLoginLimitAttemptException; -import br.com.tasknoteapp.server.exception.NoteArchivedException; +import br.com.tasknoteapp.server.exception.NoteNotArchivedException; import br.com.tasknoteapp.server.repository.NoteRepository; import br.com.tasknoteapp.server.repository.NoteUrlRepository; import br.com.tasknoteapp.server.repository.TagRepository; @@ -21,7 +21,6 @@ import br.com.tasknoteapp.server.repository.UserRepository; import java.time.LocalDateTime; import java.util.List; -import java.util.Set; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -60,11 +59,11 @@ class AccountDeletionIntTest { @Autowired private AuthService authService; - private UserEntity user; + private User user; @BeforeEach void setUp() { - user = new UserEntity(); + user = new User(); user.setEmail("account-deletion@domain.com"); user.setPassword(passwordEncoder.encode(RAW_PASSWORD)); user.setAdmin(false); @@ -86,48 +85,60 @@ void tearDown() { @Test @DisplayName("Delete account with mixed archived and non-archived notes should remove all data") void deleteAccount_mixedArchivedNotes_shouldRemoveAllUserData() { - TagEntity tag = tagRepository.save(new TagEntity("work", user)); - - NoteEntity activeNote = new NoteEntity(); - activeNote.setTitle("Active note"); - activeNote.setDescription("Not archived"); - activeNote.setUser(user); - activeNote.setLastUpdate(LocalDateTime.now()); - activeNote.setTags(Set.of(tag)); + Note activeNote = new Note( + null, + user.getId(), + "Not archived", + "Active note", + LocalDateTime.now(), + Boolean.FALSE, + null, + Boolean.FALSE + ); activeNote = noteRepository.save(activeNote); - NoteUrlEntity noteUrl = new NoteUrlEntity(); - noteUrl.setUrl("http://example.com"); - noteUrl.setNote(activeNote); + NoteUrl noteUrl = new NoteUrl( + null, + activeNote.id(), + "http://example.com" + ); noteUrlRepository.save(noteUrl); - NoteEntity archivedNote = new NoteEntity(); - archivedNote.setTitle("Archived note"); - archivedNote.setDescription("Archived"); - archivedNote.setUser(user); - archivedNote.setLastUpdate(LocalDateTime.now()); - archivedNote.setArchived(true); - archivedNote.setTags(Set.of(tag)); + Note archivedNote = new Note( + null, + user.getId(), + "Archived", + "Archived note", + LocalDateTime.now(), + Boolean.FALSE, + null, + Boolean.TRUE + ); archivedNote = noteRepository.save(archivedNote); - TaskEntity task = new TaskEntity(); - task.setDescription("A task"); - task.setCompleted(false); - task.setUser(user); - task.setLastUpdate(LocalDateTime.now()); - task.setTags(Set.of(tag)); + Task task = new Task( + null, + user.getId(), + "A task", + Boolean.FALSE, + LocalDateTime.now(), + null, + Boolean.FALSE, + Boolean.FALSE, + Boolean.FALSE + ); taskRepository.save(task); Long userId = user.getId(); - final Long activeNoteId = activeNote.getId(); + final Long activeNoteId = activeNote.id(); userSessionService.deleteCurrentUserAccount(); assertTrue(userRepository.findById(userId).isEmpty()); - assertTrue(noteRepository.findAllByUser_id(userId).isEmpty()); - assertTrue(noteUrlRepository.findByNote_id(activeNoteId).isEmpty()); - assertTrue(taskRepository.findAllByUser_id(userId).isEmpty()); - assertTrue(tagRepository.findAllByUser_idOrderByNameAsc(userId).isEmpty()); + assertTrue(noteRepository.findAllByUserId(userId).isEmpty()); + assertTrue(noteUrlRepository.findByNoteId(activeNoteId).isEmpty()); + assertTrue(taskRepository.findAllByUserId(userId).isEmpty()); + assertFalse(tagRepository.userHasAnyTags(userId)); assertTrue( userPwdLimitRepository.findTop3ByUser_idOrderByWhenHappenedDesc(userId).isEmpty()); } @@ -146,7 +157,7 @@ void deleteAccount_wrongPassword_shouldFailAndRecordAttempt() { assertTrue(userRepository.findById(userId).isPresent()); - List attempts = + List attempts = userPwdLimitRepository.findTop3ByUser_idOrderByWhenHappenedDesc(userId); assertEquals(1, attempts.size()); } @@ -157,9 +168,11 @@ void deleteAccount_maxAttempts_shouldFail() { Long userId = user.getId(); for (int i = 0; i < 3; i++) { - UserPwdLimitEntity attempt = new UserPwdLimitEntity(); - attempt.setWhenHappened(LocalDateTime.now().minusSeconds(30)); - attempt.setUser(user); + UserPwdLimit attempt = new UserPwdLimit( + null, + LocalDateTime.now().minusSeconds(30), + user.getId() + ); userPwdLimitRepository.save(attempt); } @@ -176,16 +189,21 @@ void deleteAccount_maxAttempts_shouldFail() { @Test @DisplayName("Single-note delete should still reject non-archived notes") void deleteNote_nonArchived_shouldStillThrow() { - NoteEntity activeNote = new NoteEntity(); - activeNote.setTitle("Active note"); - activeNote.setDescription("Not archived"); - activeNote.setUser(user); - activeNote.setLastUpdate(LocalDateTime.now()); + Note activeNote = new Note( + null, + user.getId(), + "Not archived", + "Active note", + LocalDateTime.now(), + Boolean.FALSE, + null, + Boolean.FALSE + ); activeNote = noteRepository.save(activeNote); - Long noteId = activeNote.getId(); + Long noteId = activeNote.id(); - assertThrows(NoteArchivedException.class, () -> noteService.deleteNote(noteId)); + assertThrows(NoteNotArchivedException.class, () -> noteService.deleteNote(noteId)); assertTrue(noteRepository.findById(noteId).isPresent()); } @@ -193,19 +211,23 @@ void deleteNote_nonArchived_shouldStillThrow() { @Test @DisplayName("Single-note delete of archived note should still work") void deleteNote_archived_shouldSucceed() { - NoteEntity archivedNote = new NoteEntity(); - archivedNote.setTitle("Archived note"); - archivedNote.setDescription("Archived"); - archivedNote.setUser(user); - archivedNote.setLastUpdate(LocalDateTime.now()); - archivedNote.setArchived(true); + Note archivedNote = new Note( + null, + user.getId(), + "Archived", + "Archived note", + LocalDateTime.now(), + Boolean.FALSE, + null, + Boolean.TRUE + ); archivedNote = noteRepository.save(archivedNote); - Long noteId = archivedNote.getId(); + Long noteId = archivedNote.id(); noteService.deleteNote(noteId); assertTrue(noteRepository.findById(noteId).isEmpty()); - assertEquals(0, noteRepository.findAllByUser_id(user.getId()).size()); + assertEquals(0, noteRepository.findAllByUserId(user.getId()).size()); } } diff --git a/server/src/test/java/br/com/tasknoteapp/server/service/AuthServiceTest.java b/server/src/test/java/br/com/tasknoteapp/server/service/AuthServiceTest.java index bc9f06d2..36164905 100644 --- a/server/src/test/java/br/com/tasknoteapp/server/service/AuthServiceTest.java +++ b/server/src/test/java/br/com/tasknoteapp/server/service/AuthServiceTest.java @@ -6,8 +6,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import br.com.tasknoteapp.server.entity.UserEntity; -import br.com.tasknoteapp.server.entity.UserPwdLimitEntity; +import br.com.tasknoteapp.server.entity.User; +import br.com.tasknoteapp.server.entity.UserPwdLimit; import br.com.tasknoteapp.server.exception.BadPasswordException; import br.com.tasknoteapp.server.exception.BadThemeException; import br.com.tasknoteapp.server.exception.BadUuidException; @@ -16,7 +16,6 @@ import br.com.tasknoteapp.server.exception.InvalidCredentialsException; import br.com.tasknoteapp.server.exception.MaxLoginLimitAttemptException; import br.com.tasknoteapp.server.exception.ResetExpiredException; -import br.com.tasknoteapp.server.exception.UserForbiddenException; import br.com.tasknoteapp.server.exception.UserNotFoundException; import br.com.tasknoteapp.server.repository.UserPwdLimitRepository; import br.com.tasknoteapp.server.repository.UserRepository; @@ -40,7 +39,6 @@ import org.springframework.core.env.Environment; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.core.userdetails.User; import org.springframework.security.crypto.password.PasswordEncoder; @ExtendWith(MockitoExtension.class) @@ -87,7 +85,7 @@ void signUpNewUser_happyPath_shouldSucceed() { when(userRepository.findByEmail(request.email())).thenReturn(Optional.empty()); when(authUtil.validatePassword(request.password())).thenReturn(Optional.empty()); - UserEntity entity = new UserEntity(); + User entity = new User(); entity.setId(3L); entity.setEmail(request.email()); entity.setName("User"); @@ -101,7 +99,7 @@ void signUpNewUser_happyPath_shouldSucceed() { Assertions.assertNull(token.token()); Assertions.assertEquals(entity.getEmail(), token.email()); Assertions.assertNotNull(entity.getEmailUuid()); - verify(userRepository).save(any(UserEntity.class)); + verify(userRepository).save(any(User.class)); } @Test @@ -110,7 +108,7 @@ void signUpNewUser_emailExists_shouldFail() { LoginRequest request = new LoginRequest("email@domain.com", "123456@abcde!", "123456@abcde!", "en"); - UserEntity existing = new UserEntity(); + User existing = new User(); when(userRepository.findByEmail(request.email())).thenReturn(Optional.of(existing)); Assertions.assertThrows( @@ -140,11 +138,11 @@ void signUpNewUser_badPassword_shouldFail() { void findByEmail_happyPath_shouldSucceed() { String email = "user@email.com"; - UserEntity existing = new UserEntity(); + User existing = new User(); existing.setEmail(email); when(userRepository.findByEmail(email)).thenReturn(Optional.of(existing)); - Optional userOp = authService.findByEmail(email); + Optional userOp = authService.findByEmail(email); Assertions.assertTrue(userOp.isPresent()); Assertions.assertEquals(email, userOp.get().getEmail()); @@ -157,7 +155,7 @@ void findByEmail_notFound_shouldSucceed() { when(userRepository.findByEmail(email)).thenReturn(Optional.empty()); - Optional userOp = authService.findByEmail(email); + Optional userOp = authService.findByEmail(email); Assertions.assertTrue(userOp.isEmpty()); } @@ -167,12 +165,12 @@ void findByEmail_notFound_shouldSucceed() { void loadUserByUsername_happyPath_shouldSucceed() { String email = "user@domain.com"; - UserEntity existing = new UserEntity(); + User existing = new User(); existing.setEmail(email); existing.setPassword(email + "123"); when(userRepository.findByEmail(email)).thenReturn(Optional.of(existing)); - User user = authService.loadUserByUsername(email); + org.springframework.security.core.userdetails.User user = authService.loadUserByUsername(email); Assertions.assertNotNull(user); Assertions.assertEquals(email, user.getUsername()); @@ -198,7 +196,7 @@ void signInUser_happyPath_shouldSucceed() { LoginRequest request = new LoginRequest("email@domain.com", "123456", "123456", "en"); LocalDateTime oldLastLogin = LocalDateTime.of(2026, 5, 1, 10, 20, 30); - UserEntity existing = new UserEntity(); + User existing = new User(); existing.setId(919L); existing.setEmail(request.email()); existing.setEmailConfirmedAt(LocalDateTime.now()); @@ -224,7 +222,7 @@ void signInUser_happyPath_shouldSucceed() { void signInUser_notConfirmed_shouldFail() { LoginRequest request = new LoginRequest("email@domain.com", "123456", "123456", "en"); - UserEntity existing = new UserEntity(); + User existing = new User(); existing.setId(919L); existing.setEmail(request.email()); existing.setEmailConfirmedAt(null); @@ -259,17 +257,26 @@ void signInUser_wrongUserOrPassword_shouldFail() { void signInUser_maxLoginAttempt_shouldFail() { LoginRequest request = new LoginRequest("email@domain.com", "123456", "123456", "en"); - UserEntity existing = new UserEntity(); + User existing = new User(); existing.setId(919L); existing.setEmailConfirmedAt(LocalDateTime.now()); when(userRepository.findByEmail(request.email())).thenReturn(Optional.of(existing)); - UserPwdLimitEntity limit1 = new UserPwdLimitEntity(); - limit1.setWhenHappened(LocalDateTime.now().minusSeconds(30)); - UserPwdLimitEntity limit2 = new UserPwdLimitEntity(); - limit2.setWhenHappened(LocalDateTime.now().minusMinutes(1)); - UserPwdLimitEntity limit3 = new UserPwdLimitEntity(); - limit3.setWhenHappened(LocalDateTime.now().minusMinutes(2)); + UserPwdLimit limit1 = new UserPwdLimit( + null, + LocalDateTime.now().minusSeconds(30), + null + ); + UserPwdLimit limit2 = new UserPwdLimit( + null, + LocalDateTime.now().minusMinutes(1), + null + ); + UserPwdLimit limit3 = new UserPwdLimit( + null, + LocalDateTime.now().minusMinutes(2), + null + ); when(userPwdLimitRepository.findTop3ByUser_idOrderByWhenHappenedDesc(existing.getId())) .thenReturn(List.of(limit1, limit2, limit3)); @@ -285,7 +292,7 @@ void signInUser_maxLoginAttempt_shouldFail() { void signInUser_badCredentials_shouldFail() { LoginRequest request = new LoginRequest("email@domain.com", "123456", "123456", "en"); - UserEntity existing = new UserEntity(); + User existing = new User(); existing.setId(919L); existing.setEmailConfirmedAt(LocalDateTime.now()); when(userRepository.findByEmail(request.email())).thenReturn(Optional.of(existing)); @@ -300,84 +307,13 @@ void signInUser_badCredentials_shouldFail() { verify(userPwdLimitRepository, times(1)).save(any()); } - @Test - @DisplayName("Get all users happy path should succeed") - void getAllUsers_happyPath_shouldSucceed() { - String email = "user@domain.com"; - when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(email)); - - UserEntity existing = new UserEntity(); - existing.setId(919L); - existing.setEmail(email); - existing.setAdmin(true); - when(userRepository.findByEmail(email)).thenReturn(Optional.of(existing)); - - UserEntity user1 = new UserEntity(); - user1.setEmail("user1@domain.com"); - UserEntity user2 = new UserEntity(); - user2.setEmail("user2@domain.com"); - when(userRepository.findAll()).thenReturn(List.of(user1, user2)); - - List users = authService.getAllUsers(); - - Assertions.assertNotNull(users); - Assertions.assertFalse(users.isEmpty()); - Assertions.assertEquals(user1.getEmail(), users.get(0).email()); - Assertions.assertEquals(user2.getEmail(), users.get(1).email()); - } - - @Test - @DisplayName("Get all users no current user should fail") - void getAllUsers_noCurrentUser_shouldFail() { - when(authUtil.getCurrentUserEmail()).thenReturn(Optional.empty()); - - Assertions.assertThrows( - UserNotFoundException.class, - () -> { - authService.getAllUsers(); - }); - } - - @Test - @DisplayName("Get all users user not found should fail") - void getAllUsers_userNotFound_shouldFail() { - String email = "user@domain.com"; - when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(email)); - when(userRepository.findByEmail(email)).thenReturn(Optional.empty()); - - Assertions.assertThrows( - UserNotFoundException.class, - () -> { - authService.getAllUsers(); - }); - } - - @Test - @DisplayName("Get all users user not admin should fail") - void getAllUsers_userNotAdmin_shouldFail() { - String email = "user@domain.com"; - when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(email)); - - UserEntity existing = new UserEntity(); - existing.setId(919L); - existing.setEmail(email); - existing.setAdmin(false); - when(userRepository.findByEmail(email)).thenReturn(Optional.of(existing)); - - Assertions.assertThrows( - UserForbiddenException.class, - () -> { - authService.getAllUsers(); - }); - } - @Test @DisplayName("Refresh current user token happy path should succeed") void refreshCurrentUserToken_happyPath_shouldSucceed() { String email = "user@domain.com"; when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(email)); - UserEntity existing = new UserEntity(); + User existing = new User(); existing.setId(919L); existing.setEmail(email); existing.setAdmin(false); @@ -397,7 +333,7 @@ void deleteUserAccount_happyPath_shouldSucceed() { String email = "user@domain.com"; when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(email)); - UserEntity existing = new UserEntity(); + User existing = new User(); existing.setId(919L); existing.setEmail(email); when(userRepository.findByEmail(email)).thenReturn(Optional.of(existing)); @@ -415,7 +351,7 @@ void pathUserInfo_nameAndEmail_shouldSucceed() { String email = "user@domain.com"; when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(email)); - UserEntity existing = new UserEntity(); + User existing = new User(); existing.setId(919L); existing.setName(null); existing.setEmail(email); @@ -443,7 +379,7 @@ void pathUserInfo_password_shouldSucceed() { String email = "user@domain.com"; when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(email)); - UserEntity existing = new UserEntity(); + User existing = new User(); existing.setId(919L); existing.setName(null); existing.setEmail(email); @@ -475,7 +411,7 @@ void patchUserInfo_theme_shouldSucceed() { String email = "user@domain.com"; when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(email)); - UserEntity existing = new UserEntity(); + User existing = new User(); existing.setId(919L); existing.setEmail(email); existing.setAdmin(false); @@ -498,7 +434,7 @@ void patchUserInfo_invalidTheme_shouldFail() { String email = "user@domain.com"; when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(email)); - UserEntity existing = new UserEntity(); + User existing = new User(); existing.setId(919L); existing.setEmail(email); existing.setAdmin(false); @@ -518,7 +454,7 @@ void patchUserInfo_blankTheme_shouldBeIgnored() { String email = "user@domain.com"; when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(email)); - UserEntity existing = new UserEntity(); + User existing = new User(); existing.setId(919L); existing.setEmail(email); existing.setAdmin(false); @@ -540,14 +476,14 @@ void getCurrentUser_happyPath_shouldSucceed() { String email = "user@domain.com"; when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(email)); - UserEntity existing = new UserEntity(); + User existing = new User(); existing.setId(775L); existing.setName("Test"); existing.setEmail(email); existing.setAdmin(false); when(userRepository.findByEmail(email)).thenReturn(Optional.of(existing)); - Optional userOptional = authService.getCurrentUser(); + Optional userOptional = authService.getCurrentUser(); Assertions.assertNotNull(userOptional); Assertions.assertTrue(userOptional.isPresent()); @@ -566,7 +502,7 @@ void getCurrentUser_userNotFound_shouldFail() { void confirmUserAccount_happyPath_shouldSucceed() { String uuid = UUID.randomUUID().toString(); - UserEntity user = new UserEntity(); + User user = new User(); user.setEmailUuid(UUID.fromString(uuid)); when(userRepository.findByEmailUuid(UUID.fromString(uuid))).thenReturn(Optional.of(user)); when(userRepository.save(any())).thenReturn(user); @@ -609,7 +545,7 @@ void confirmUserAccount_nonExistentUuid_shouldFail() { void resendEmailConfirmation_happyPath_shouldSucceed() { String email = "user@domain.com"; - UserEntity existing = new UserEntity(); + User existing = new User(); existing.setId(919L); existing.setEmail(email); when(userRepository.findByEmail(email)).thenReturn(Optional.of(existing)); @@ -626,7 +562,7 @@ void resendEmailConfirmation_happyPath_shouldSucceed() { void resendEmailConfirmation_noMailgunApiKey_shouldSucceed() { String email = "user@domain.com"; - UserEntity existing = new UserEntity(); + User existing = new User(); existing.setId(919L); existing.setEmail(email); when(userRepository.findByEmail(email)).thenReturn(Optional.of(existing)); @@ -652,7 +588,7 @@ void resendEmailConfirmation_nonExistentEmail_shouldFail() { void resetPasswordForUser_happyPath_shouldSucceed() { String email = "user@domain.com"; - UserEntity existing = new UserEntity(); + User existing = new User(); existing.setId(919L); existing.setEmail(email); when(userRepository.findByEmail(email)).thenReturn(Optional.of(existing)); @@ -682,7 +618,7 @@ void resetPasswordForUser_nonExistentEmail_shouldSucceed() { @DisplayName("Confirm reset password happy path should succeed") void confirmResetPasswordForUser_happyPath_shouldSucceed() { String token = "validToken"; - UserEntity user = new UserEntity(); + User user = new User(); user.setResetToken(token); user.setResetPasswordExpiration(LocalDateTime.now().plusMinutes(30)); @@ -711,7 +647,7 @@ void confirmResetPasswordForUser_happyPath_shouldSucceed() { @DisplayName("Confirm reset password no mailgun api token should succeed") void confirmResetPasswordForUser_noMailgunApiToken_shouldSucceed() { String token = "validToken"; - UserEntity user = new UserEntity(); + User user = new User(); user.setResetToken(token); user.setResetPasswordExpiration(LocalDateTime.now().plusMinutes(30)); @@ -741,7 +677,7 @@ void confirmResetPasswordForUser_expiredToken_shouldFail() { String token = "expiredToken"; String newPassword = "NewPassword@123"; - UserEntity user = new UserEntity(); + User user = new User(); user.setResetToken(token); user.setResetPasswordExpiration(LocalDateTime.now().minusHours(3)); @@ -779,7 +715,7 @@ void confirmResetPasswordForUser_mismatchedPasswords_shouldFail() { String newPassword = "NewPassword@123"; String mismatchedPassword = "Mismatch@123"; - UserEntity user = new UserEntity(); + User user = new User(); user.setResetToken(token); user.setResetPasswordExpiration(LocalDateTime.now().plusMinutes(30)); PasswordResetRequest request = new PasswordResetRequest(token, newPassword, mismatchedPassword); @@ -799,7 +735,7 @@ void confirmResetPasswordForUser_invalidPassword_shouldFail() { String token = "validToken"; String newPassword = "weak"; - UserEntity user = new UserEntity(); + User user = new User(); user.setResetToken(token); user.setResetPasswordExpiration(LocalDateTime.now().plusMinutes(30)); diff --git a/server/src/test/java/br/com/tasknoteapp/server/service/HomeServiceTest.java b/server/src/test/java/br/com/tasknoteapp/server/service/HomeServiceTest.java index a69168fe..ed77344b 100644 --- a/server/src/test/java/br/com/tasknoteapp/server/service/HomeServiceTest.java +++ b/server/src/test/java/br/com/tasknoteapp/server/service/HomeServiceTest.java @@ -2,8 +2,8 @@ import static org.mockito.Mockito.when; -import br.com.tasknoteapp.server.entity.TagEntity; -import br.com.tasknoteapp.server.entity.UserEntity; +import br.com.tasknoteapp.server.entity.Tag; +import br.com.tasknoteapp.server.entity.User; import br.com.tasknoteapp.server.repository.TagRepository; import br.com.tasknoteapp.server.response.TaskResponse; import br.com.tasknoteapp.server.util.AuthUtil; @@ -37,8 +37,8 @@ void setUp() { homeService = new HomeService(taskService, noteService, tagRepository, authService, authUtil); } - private UserEntity mockUser() { - UserEntity user = new UserEntity(); + private User mockUser() { + User user = new User(); user.setId(1L); user.setEmail("user@test.com"); when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of("user@test.com")); @@ -49,34 +49,43 @@ private UserEntity mockUser() { @Test @DisplayName("Get tasks tags should return all tags ordered alphabetically") void getTopTasksTag_shouldReturnAllTagsAlphabetically() { - UserEntity user = mockUser(); - TagEntity tag1 = new TagEntity("tag1", user); - TagEntity tag2 = new TagEntity("tag2", user); - TagEntity tag3 = new TagEntity("tag3", user); - TagEntity tag4 = new TagEntity("tag4", user); - TagEntity tag5 = new TagEntity("tag5", user); - TagEntity tag6 = new TagEntity("tag6", user); - - when(tagRepository.findAllByUser_idOrderByNameAsc(user.getId())) - .thenReturn(List.of(tag1, tag2, tag3, tag4, tag5, tag6)); - + User user = mockUser(); + Tag tag1 = new Tag(null, "tag1", user.getId()); + Tag tag2 = new Tag(null, "tag2", user.getId()); + Tag tag3 = new Tag(null, "tag3", user.getId()); + Tag tag4 = new Tag(null, "tag4", user.getId()); + Tag tag5 = new Tag(null, "tag5", user.getId()); + Tag tag6 = new Tag(null, "tag6", user.getId()); + Tag tag7 = new Tag(null, "atag", user.getId()); + Tag tag8 = new Tag(null, "dev", user.getId()); + + List tags = List.of(tag1.name(), tag2.name(), tag3.name(), tag4.name(), tag5.name(), + tag6.name(), tag7.name(), tag8.name()); TaskResponse task1 = - new TaskResponse(1L, false, "Task 1", false, null, null, null, List.of("tag1"), List.of()); + new TaskResponse(1L, false, "Task 1", false, null, null, null, tags, List.of()); when(taskService.getTasksByFilter("all")).thenReturn(List.of(task1)); when(noteService.getAllNotes()).thenReturn(List.of()); - List tags = homeService.getTopTasksTag(); + // List taskTags = List.of( + // new TaskNoteTag(tag1.id(), tag1.name(), tag1.userId(), 1L), + // new TaskNoteTag(tag2.id(), tag2.name(), tag2.userId(), 1L), + // new TaskNoteTag(tag3.id(), tag3.name(), tag3.userId(), 1L) + // ); + // when(tagRepository.findAllByUserIdAndTaskIdInList(user.getId(), List.of(1L))) + // .thenReturn(taskTags); + + List homeTags = homeService.getTopTasksTag(); - Assertions.assertNotNull(tags); - Assertions.assertEquals(6, tags.size()); - Assertions.assertEquals(List.of("tag1", "tag2", "tag3", "tag4", "tag5", "tag6"), tags); + Assertions.assertNotNull(homeTags); + Assertions.assertEquals(8, homeTags.size()); + Assertions.assertEquals(List.of("atag", "dev", "tag1", "tag2", "tag3", "tag4", "tag5", "tag6"), + homeTags); } @Test @DisplayName("Get top tasks tag with no tags should return empty list") void getTopTasksTag_noTags_shouldReturnEmptyList() { - UserEntity user = mockUser(); - when(tagRepository.findAllByUser_idOrderByNameAsc(user.getId())).thenReturn(List.of()); + mockUser(); when(taskService.getTasksByFilter("all")).thenReturn(List.of()); when(noteService.getAllNotes()).thenReturn(List.of()); @@ -89,9 +98,7 @@ void getTopTasksTag_noTags_shouldReturnEmptyList() { @Test @DisplayName("Get top tasks tag with untagged tasks/notes should include 'untagged'") void getTopTasksTag_withUntagged_shouldIncludeUntagged() { - UserEntity user = mockUser(); - TagEntity tag1 = new TagEntity("tag1", user); - when(tagRepository.findAllByUser_idOrderByNameAsc(user.getId())).thenReturn(List.of(tag1)); + mockUser(); TaskResponse task1 = new TaskResponse(1L, false, "Task 1", false, null, null, null, List.of(), List.of()); @@ -101,10 +108,9 @@ void getTopTasksTag_withUntagged_shouldIncludeUntagged() { List topTags = homeService.getTopTasksTag(); Assertions.assertNotNull(topTags); - Assertions.assertEquals(2, topTags.size()); + Assertions.assertEquals(1, topTags.size()); Assertions.assertTrue(topTags.contains("untagged")); - Assertions.assertTrue(topTags.contains("tag1")); - Assertions.assertEquals(List.of("tag1", "untagged"), topTags); + Assertions.assertEquals(List.of("untagged"), topTags); } } diff --git a/server/src/test/java/br/com/tasknoteapp/server/service/MailgunEmailServiceTest.java b/server/src/test/java/br/com/tasknoteapp/server/service/MailgunEmailServiceTest.java index 0c58e88c..0b841be7 100644 --- a/server/src/test/java/br/com/tasknoteapp/server/service/MailgunEmailServiceTest.java +++ b/server/src/test/java/br/com/tasknoteapp/server/service/MailgunEmailServiceTest.java @@ -6,7 +6,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import br.com.tasknoteapp.server.entity.UserEntity; +import br.com.tasknoteapp.server.entity.User; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -47,13 +47,13 @@ private void setupMockChain() { when(restClient.post()).thenReturn(requestBodyUriSpec); when(requestBodyUriSpec.uri(anyString())).thenReturn(requestBodySpec); when(requestBodySpec.contentType(any())).thenReturn(requestBodySpec); - when(requestBodySpec.body(any())).thenReturn(requestBodySpec); + when(requestBodySpec.body(any(Object.class))).thenReturn(requestBodySpec); when(requestBodySpec.retrieve()).thenReturn(responseSpec); } @Test void testSendResetPassword() { - UserEntity user = new UserEntity(); + User user = new User(); user.setEmail("test@example.com"); user.setResetToken("reset-token"); @@ -62,11 +62,12 @@ void testSendResetPassword() { mailgunEmailService.sendResetPassword(user); verify(restClient, times(1)).post(); + verify(responseSpec, times(1)).toBodilessEntity(); } @Test void testSendPasswordResetConfirmation() { - UserEntity user = new UserEntity(); + User user = new User(); user.setEmail("test@example.com"); setupMockChain(); @@ -74,11 +75,12 @@ void testSendPasswordResetConfirmation() { mailgunEmailService.sendPasswordResetConfirmation(user); verify(restClient, times(1)).post(); + verify(responseSpec, times(1)).toBodilessEntity(); } @Test void testSendNewUser() { - UserEntity user = new UserEntity(); + User user = new User(); user.setEmail("test@example.com"); user.setEmailUuid(java.util.UUID.randomUUID()); @@ -87,11 +89,12 @@ void testSendNewUser() { mailgunEmailService.sendNewUser(user); verify(restClient, times(1)).post(); + verify(responseSpec, times(1)).toBodilessEntity(); } @Test void testSendEmailHandlesHttpClientErrorException() { - UserEntity user = new UserEntity(); + User user = new User(); user.setEmail("test@example.com"); user.setResetToken("reset-token"); @@ -102,11 +105,12 @@ void testSendEmailHandlesHttpClientErrorException() { mailgunEmailService.sendResetPassword(user); verify(restClient, times(1)).post(); + verify(responseSpec, times(1)).toBodilessEntity(); } @Test void testSendEmailChanged() { - UserEntity user = new UserEntity(); + User user = new User(); user.setEmail("test@example.com"); setupMockChain(); @@ -116,5 +120,6 @@ void testSendEmailChanged() { mailgunEmailService.sendEmailChangedNotification(user, oldEmail); verify(restClient, times(1)).post(); + verify(responseSpec, times(1)).toBodilessEntity(); } } diff --git a/server/src/test/java/br/com/tasknoteapp/server/service/NoteServiceTest.java b/server/src/test/java/br/com/tasknoteapp/server/service/NoteServiceTest.java index 5d3c5493..ba8c028b 100644 --- a/server/src/test/java/br/com/tasknoteapp/server/service/NoteServiceTest.java +++ b/server/src/test/java/br/com/tasknoteapp/server/service/NoteServiceTest.java @@ -9,9 +9,10 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import br.com.tasknoteapp.server.entity.NoteEntity; -import br.com.tasknoteapp.server.entity.NoteUrlEntity; -import br.com.tasknoteapp.server.entity.UserEntity; +import br.com.tasknoteapp.server.entity.Note; +import br.com.tasknoteapp.server.entity.NoteUrl; +import br.com.tasknoteapp.server.entity.Tag; +import br.com.tasknoteapp.server.entity.User; import br.com.tasknoteapp.server.exception.NoteNotFoundException; import br.com.tasknoteapp.server.repository.NoteRepository; import br.com.tasknoteapp.server.repository.NoteUrlRepository; @@ -44,22 +45,27 @@ class NoteServiceTest { @InjectMocks private NoteService noteService; - private UserEntity user; - private NoteEntity note; + private User user; + private Note note; private NoteRequest noteRequest; private NotePatchRequest notePatchRequest; @BeforeEach void setUp() { - user = new UserEntity(); + user = new User(); user.setId(1L); user.setEmail("test@example.com"); - note = new NoteEntity(); - note.setId(1L); - note.setTitle("Test Note"); - note.setDescription("Test Description"); - note.setUser(user); + note = new Note( + 1L, + user.getId(), + "Test Description", + "Test Note", + null, + Boolean.FALSE, + null, + Boolean.FALSE + ); noteRequest = new NoteRequest("Test Note", "Test Description", "http://example.com", List.of("tag")); @@ -72,33 +78,33 @@ void setUp() { void getAllNotes() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail())); when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user)); - when(noteRepository.findAllByUser_id(user.getId())).thenReturn(List.of(note)); + when(noteRepository.findAllByUserId(user.getId())).thenReturn(List.of(note)); List notes = noteService.getAllNotes(); assertEquals(1, notes.size()); assertEquals("Test Note", notes.get(0).title()); - verify(noteRepository, times(1)).findAllByUser_id(user.getId()); + verify(noteRepository, times(1)).findAllByUserId(user.getId()); } @Test void getNoteById() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail())); when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user)); - when(noteRepository.findById(note.getId())).thenReturn(Optional.of(note)); + when(noteRepository.findByIdAndUserId(note.id(), user.getId())).thenReturn(Optional.of(note)); - NoteResponse noteResponse = noteService.getNoteById(note.getId()); + NoteResponse noteResponse = noteService.getNoteById(note.id()); assertEquals("Test Note", noteResponse.title()); - verify(noteRepository, times(1)).findById(note.getId()); + verify(noteRepository, times(1)).findByIdAndUserId(note.id(), user.getId()); } @Test void getNoteById_NotFound() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail())); when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user)); - when(noteRepository.findById(note.getId())).thenReturn(Optional.empty()); - Long noteId = note.getId(); + when(noteRepository.findByIdAndUserId(note.id(), user.getId())).thenReturn(Optional.empty()); + Long noteId = note.id(); assertThrows(NoteNotFoundException.class, () -> noteService.getNoteById(noteId)); } @@ -107,42 +113,53 @@ void getNoteById_NotFound() { void createNote_withExistingCount() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail())); when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user)); - when(noteRepository.save(any(NoteEntity.class))).thenReturn(note); - when(noteUrlRepository.save(any(NoteUrlEntity.class))).thenReturn(new NoteUrlEntity()); - when(tagRepository.findByNameAndUser_id(anyString(), eq(user.getId()))) - .thenReturn(Optional.of(new br.com.tasknoteapp.server.entity.TagEntity("tag", user))); + when(noteRepository.save(any(Note.class))).thenReturn(note); + when(noteUrlRepository.save(any(NoteUrl.class))).thenReturn(new NoteUrl(null, null, null)); + when(tagRepository.findByUserIdAndName(eq(user.getId()), anyString())) + .thenReturn(Optional.of(new Tag(null, "tag", user.getId()))); NoteResponse createdNote = noteService.createNote(noteRequest); assertEquals("Test Note", createdNote.title()); - verify(noteRepository, times(1)).save(any(NoteEntity.class)); - verify(noteUrlRepository, times(1)).save(any(NoteUrlEntity.class)); + verify(noteRepository, times(1)).save(any(Note.class)); + verify(noteUrlRepository, times(1)).save(any(NoteUrl.class)); } @Test void patchNote() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail())); when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user)); - when(noteRepository.findByIdAndUser_id(note.getId(), user.getId())) + + Note noteUpdated = new Note( + note.id(), + note.userId(), + note.description(), + "Updated Note", + note.lastUpdate(), + note.shared(), + note.shareToken(), + note.archived() + ); + when(noteRepository.findByIdAndUserId(note.id(), user.getId())) .thenReturn(Optional.of(note)); - when(noteRepository.save(any(NoteEntity.class))).thenReturn(note); - when(tagRepository.findByNameAndUser_id(anyString(), eq(user.getId()))) - .thenReturn(Optional.of(new br.com.tasknoteapp.server.entity.TagEntity("tag", user))); + when(noteRepository.save(any(Note.class))).thenReturn(noteUpdated); + when(tagRepository.findByUserIdAndName(eq(user.getId()), anyString())) + .thenReturn(Optional.of(new Tag(null, "tag", user.getId()))); - NoteResponse patchedNote = noteService.patchNote(note.getId(), notePatchRequest); + NoteResponse patchedNote = noteService.patchNote(note.id(), notePatchRequest); assertEquals("Updated Note", patchedNote.title()); - verify(noteRepository, times(1)).findByIdAndUser_id(note.getId(), user.getId()); - verify(noteRepository, times(1)).save(any(NoteEntity.class)); + verify(noteRepository, times(1)).findByIdAndUserId(note.id(), user.getId()); + verify(noteRepository, times(1)).save(any(Note.class)); } @Test void patchNote_notFound() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail())); when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user)); - when(noteRepository.findByIdAndUser_id(note.getId(), user.getId())) + when(noteRepository.findByIdAndUserId(note.id(), user.getId())) .thenReturn(Optional.empty()); - Long noteId = note.getId(); + Long noteId = note.id(); assertThrows( NoteNotFoundException.class, () -> noteService.patchNote(noteId, notePatchRequest)); @@ -150,35 +167,48 @@ void patchNote_notFound() { @Test void deleteNote() { - note.setArchived(true); + Note noteToDelete = new Note( + 11L, + user.getId(), + "Test Description", + "Test Note", + null, + Boolean.FALSE, + null, + Boolean.TRUE + ); when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail())); when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user)); - when(noteRepository.findByIdAndUser_id(note.getId(), user.getId())) - .thenReturn(Optional.of(note)); + when(noteRepository.findByIdAndUserId(noteToDelete.id(), user.getId())) + .thenReturn(Optional.of(noteToDelete)); - noteService.deleteNote(note.getId()); + noteService.deleteNote(noteToDelete.id()); - verify(noteRepository, times(1)).findByIdAndUser_id(note.getId(), user.getId()); - verify(noteRepository, times(1)).delete(note); + verify(noteRepository, times(1)).findByIdAndUserId(noteToDelete.id(), user.getId()); + verify(noteRepository, times(1)).delete(noteToDelete); } @Test void deleteAllNotesForCurrentUser() { - NoteEntity archivedNote = new NoteEntity(); - archivedNote.setId(2L); - archivedNote.setTitle("Archived Note"); - archivedNote.setDescription("Archived Description"); - archivedNote.setUser(user); - archivedNote.setArchived(true); + Note archivedNote = new Note( + 2L, + user.getId(), + "Archived Description", + "Archived Note", + null, + Boolean.FALSE, + null, + Boolean.TRUE + ); when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail())); when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user)); - when(noteRepository.findAllByUser_id(user.getId())).thenReturn(List.of(note, archivedNote)); + when(noteRepository.findAllByUserId(user.getId())).thenReturn(List.of(note, archivedNote)); noteService.deleteAllNotesForCurrentUser(); - verify(noteUrlRepository, times(1)).deleteByNote_id(note.getId()); - verify(noteUrlRepository, times(1)).deleteByNote_id(archivedNote.getId()); + verify(noteUrlRepository, times(1)).deleteByNoteId(note.id()); + verify(noteUrlRepository, times(1)).deleteByNoteId(archivedNote.id()); verify(noteRepository, times(1)).delete(note); verify(noteRepository, times(1)).delete(archivedNote); verify(tagRepository, times(1)).deleteOrphanedTags(user.getId()); @@ -188,64 +218,72 @@ void deleteAllNotesForCurrentUser() { void searchNotes() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail())); when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user)); - when(noteRepository.findAllBySearchTerm(anyString(), eq(user.getId()))) + when(noteRepository.findAllBySearchTerm(eq(user.getId()), anyString())) .thenReturn(List.of(note)); List notes = noteService.searchNotes("Test"); assertEquals(1, notes.size()); assertEquals("Test Note", notes.get(0).title()); - verify(noteRepository, times(1)).findAllBySearchTerm(anyString(), eq(user.getId())); + verify(noteRepository, times(1)).findAllBySearchTerm(eq(user.getId()), anyString()); } @Test void shareNote() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail())); when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user)); - when(noteRepository.findByIdAndUser_id(note.getId(), user.getId())) + when(noteRepository.findByIdAndUserId(note.id(), user.getId())) .thenReturn(Optional.of(note)); - when(noteRepository.save(any(NoteEntity.class))).thenReturn(note); + when(noteRepository.save(any(Note.class))).thenReturn(note); - NoteResponse response = noteService.shareNote(note.getId()); + NoteResponse response = noteService.shareNote(note.id()); assertEquals("Test Note", response.title()); - verify(noteRepository, times(1)).save(any(NoteEntity.class)); + verify(noteRepository, times(1)).save(any(Note.class)); } @Test void shareNote_notFound() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail())); when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user)); - when(noteRepository.findByIdAndUser_id(note.getId(), user.getId())) + when(noteRepository.findByIdAndUserId(note.id(), user.getId())) .thenReturn(Optional.empty()); - Long noteId = note.getId(); + Long noteId = note.id(); assertThrows(NoteNotFoundException.class, () -> noteService.shareNote(noteId)); } @Test void unshareNote() { - note.setShared(true); - note.setShareToken("some-token"); + Note noteToUnShare = new Note( + 111L, + user.getId(), + "Test Description", + "Test Note", + null, + Boolean.TRUE, + "some-token", + Boolean.FALSE + ); when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail())); when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user)); - when(noteRepository.findByIdAndUser_id(note.getId(), user.getId())) - .thenReturn(Optional.of(note)); - when(noteRepository.save(any(NoteEntity.class))).thenReturn(note); + when(noteRepository.findByIdAndUserId(noteToUnShare.id(), user.getId())) + .thenReturn(Optional.of(noteToUnShare)); + when(noteRepository.save(any(Note.class))).thenReturn(noteToUnShare); - NoteResponse response = noteService.unshareNote(note.getId()); + NoteResponse response = noteService.unshareNote(noteToUnShare.id()); assertEquals("Test Note", response.title()); - verify(noteRepository, times(1)).save(any(NoteEntity.class)); + verify(noteRepository, times(1)).save(any(Note.class)); } @Test void unshareNote_notFound() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail())); when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user)); - when(noteRepository.findByIdAndUser_id(note.getId(), user.getId())) + when(noteRepository.findByIdAndUserId(note.id(), user.getId())) .thenReturn(Optional.empty()); - Long noteId = note.getId(); + Long noteId = note.id(); assertThrows(NoteNotFoundException.class, () -> noteService.unshareNote(noteId)); } @@ -253,9 +291,18 @@ void unshareNote_notFound() { @Test void getSharedNote() { final String token = "share-token-123"; - note.setShared(true); - note.setShareToken(token); - when(noteRepository.findByShareToken(token)).thenReturn(Optional.of(note)); + Note sharedNote = new Note( + 2L, + user.getId(), + "Archived Description", + "Test Note", + null, + Boolean.TRUE, + token, + Boolean.FALSE + ); + + when(noteRepository.findByShareToken(token)).thenReturn(Optional.of(sharedNote)); NoteResponse response = noteService.getSharedNote(token); @@ -273,9 +320,17 @@ void getSharedNote_notFound() { @Test void getSharedNote_notShared() { final String token = "share-token-456"; - note.setShared(false); - note.setShareToken(token); - when(noteRepository.findByShareToken(token)).thenReturn(Optional.of(note)); + Note notSharedNote = new Note( + 2L, + user.getId(), + "Archived Description", + "Archived Note", + null, + Boolean.FALSE, + token, + Boolean.FALSE + ); + when(noteRepository.findByShareToken(token)).thenReturn(Optional.of(notSharedNote)); assertThrows(NoteNotFoundException.class, () -> noteService.getSharedNote(token)); } diff --git a/server/src/test/java/br/com/tasknoteapp/server/service/TaskServiceTest.java b/server/src/test/java/br/com/tasknoteapp/server/service/TaskServiceTest.java index 87c4a61d..cbd04752 100644 --- a/server/src/test/java/br/com/tasknoteapp/server/service/TaskServiceTest.java +++ b/server/src/test/java/br/com/tasknoteapp/server/service/TaskServiceTest.java @@ -7,18 +7,18 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doNothing; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import br.com.tasknoteapp.server.entity.TagEntity; -import br.com.tasknoteapp.server.entity.TaskEntity; -import br.com.tasknoteapp.server.entity.TaskUrlEntity; -import br.com.tasknoteapp.server.entity.TaskUrlEntityPk; -import br.com.tasknoteapp.server.entity.UserEntity; +import br.com.tasknoteapp.server.entity.Tag; +import br.com.tasknoteapp.server.entity.Task; +import br.com.tasknoteapp.server.entity.TaskNoteTag; +import br.com.tasknoteapp.server.entity.TaskUrl; +import br.com.tasknoteapp.server.entity.TaskUrlPk; +import br.com.tasknoteapp.server.entity.User; import br.com.tasknoteapp.server.exception.TaskNotFoundException; import br.com.tasknoteapp.server.repository.TagRepository; import br.com.tasknoteapp.server.repository.TaskRepository; @@ -31,7 +31,6 @@ import java.time.LocalDate; import java.util.List; import java.util.Optional; -import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -72,27 +71,38 @@ void setup() { void getTaskById_happyPath_shouldSucceed() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL)); - UserEntity userEntity = new UserEntity(); + User userEntity = new User(); userEntity.setId(USER_ID); userEntity.setEmail(USER_EMAIL); when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity)); Long taskId = 9976L; - TaskEntity taskEntity = new TaskEntity(); - taskEntity.setId(taskId); - taskEntity.setDescription("Test task"); - taskEntity.setHighPriority(true); - taskEntity.setTags(Set.of(new TagEntity("test", userEntity))); - taskEntity.setUser(userEntity); - when(taskRepository.findByIdAndUser_id(taskId, USER_ID)).thenReturn(Optional.of(taskEntity)); + Task taskEntity = new Task( + taskId, + userEntity.getId(), + "Test task", + Boolean.FALSE, + null, + null, + Boolean.FALSE, + Boolean.FALSE, + Boolean.TRUE + ); + when(taskRepository.findByIdAndUserId(taskId, USER_ID)).thenReturn(Optional.of(taskEntity)); + + Tag tagDev = new Tag(1L, "test", USER_ID); + TaskNoteTag taskTags = new TaskNoteTag(tagDev.id(), tagDev.name(), tagDev.userId(), + taskEntity.id()); + when(tagRepository.findAllByUserIdAndTaskIdInList(USER_ID, List.of(taskEntity.id()))) + .thenReturn(List.of(taskTags)); TaskResponse taskResponse = taskService.getTaskById(taskId); assertNotNull(taskResponse); - assertEquals(taskEntity.getId(), taskResponse.id()); - assertEquals(taskEntity.getDescription(), taskResponse.description()); - assertEquals(taskEntity.getHighPriority(), taskResponse.highPriority()); + assertEquals(taskEntity.id(), taskResponse.id()); + assertEquals(taskEntity.description(), taskResponse.description()); + assertEquals(taskEntity.highPriority(), taskResponse.highPriority()); assertTrue(taskResponse.tags().contains("test")); } @@ -101,14 +111,14 @@ void getTaskById_happyPath_shouldSucceed() { void getTaskById_notFound_shouldFail() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL)); - UserEntity userEntity = new UserEntity(); + User userEntity = new User(); userEntity.setId(USER_ID); userEntity.setEmail(USER_EMAIL); when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity)); Long taskId = 9976L; - when(taskRepository.findByIdAndUser_id(taskId, USER_ID)).thenReturn(Optional.empty()); + when(taskRepository.findByIdAndUserId(taskId, USER_ID)).thenReturn(Optional.empty()); assertThrows(TaskNotFoundException.class, () -> taskService.getTaskById(taskId)); } @@ -118,7 +128,7 @@ void getTaskById_notFound_shouldFail() { void createTask_nullDueDate_shouldSucceed() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL)); - UserEntity userEntity = new UserEntity(); + User userEntity = new User(); userEntity.setId(USER_ID); userEntity.setEmail(USER_EMAIL); when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity)); @@ -126,20 +136,34 @@ void createTask_nullDueDate_shouldSucceed() { List tags = List.of("development"); TaskRequest request = new TaskRequest("Write unit tests", null, null, false, tags); - TagEntity tagEntity = new TagEntity("development", userEntity); - when(tagRepository.findByNameAndUser_id(anyString(), anyLong())) - .thenReturn(Optional.of(tagEntity)); - - TaskEntity entity = new TaskEntity(); - entity.setDescription(request.description()); - entity.setHighPriority(request.highPriority()); - entity.setTags(Set.of(tagEntity)); + Task entity = new Task( + 123L, + null, + request.description(), + Boolean.FALSE, + null, + null, + Boolean.FALSE, + Boolean.FALSE, + request.highPriority() + ); when(taskRepository.save(any())).thenReturn(entity); - taskService.createTask(request); + Tag tag = new Tag(333L, "development", USER_ID); + when(tagRepository.findByUserIdAndName(eq(userEntity.getId()), eq(tag.name()))) + .thenReturn(Optional.of(tag)); - assertNotNull(entity); - assertTrue(entity.getTags().stream().anyMatch(t -> t.getName().equals("development"))); + when(tagRepository.deleteOrphanedTags(USER_ID)).thenReturn(0); + + TaskNoteTag taskTags = new TaskNoteTag(tag.id(), tag.name(), tag.userId(), entity.id()); + when(tagRepository.findAllByUserIdAndTaskIdInList(USER_ID, List.of(entity.id()))) + .thenReturn(List.of(taskTags)); + + TaskResponse response = taskService.createTask(request); + + assertNotNull(response); + assertNotNull(response.id()); + assertTrue(response.tags().stream().anyMatch(t -> t.equals("development"))); } @ParameterizedTest @@ -148,7 +172,7 @@ void createTask_nullDueDate_shouldSucceed() { void createTask_parametrizedDueDate_shouldSucceed(String dueDate, String expectedDueDate) { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL)); - UserEntity userEntity = new UserEntity(); + User userEntity = new User(); userEntity.setId(USER_ID); userEntity.setEmail(USER_EMAIL); when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity)); @@ -156,20 +180,32 @@ void createTask_parametrizedDueDate_shouldSucceed(String dueDate, String expecte List tags = List.of("development"); TaskRequest request = new TaskRequest("Write unit tests", null, dueDate, false, tags); - TagEntity tagEntity = new TagEntity("development", userEntity); - when(tagRepository.findByNameAndUser_id(anyString(), anyLong())) + Tag tagEntity = new Tag(null, "development", userEntity.getId()); + when(tagRepository.findByUserIdAndName(eq(userEntity.getId()), anyString())) .thenReturn(Optional.of(tagEntity)); - TaskEntity entity = new TaskEntity(); - entity.setDescription(request.description()); - entity.setHighPriority(request.highPriority()); - entity.setTags(Set.of(tagEntity)); + Task entity = new Task( + 188L, + null, + request.description(), + Boolean.FALSE, + null, + null, + Boolean.FALSE, + Boolean.FALSE, + request.highPriority() + ); when(taskRepository.save(any())).thenReturn(entity); - taskService.createTask(request); + TaskNoteTag taskTags = new TaskNoteTag(tagEntity.id(), tagEntity.name(), tagEntity.userId(), + entity.id()); + when(tagRepository.findAllByUserIdAndTaskIdInList(USER_ID, List.of(entity.id()))) + .thenReturn(List.of(taskTags)); + + TaskResponse response = taskService.createTask(request); assertNotNull(entity); - assertTrue(entity.getTags().stream().anyMatch(t -> t.getName().equals("development"))); + assertTrue(response.tags().stream().anyMatch(t -> t.equals("development"))); } @Test @@ -177,7 +213,7 @@ void createTask_parametrizedDueDate_shouldSucceed(String dueDate, String expecte void createTask_nullUrl_shouldSucceed() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL)); - UserEntity userEntity = new UserEntity(); + User userEntity = new User(); userEntity.setId(USER_ID); userEntity.setEmail(USER_EMAIL); when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity)); @@ -186,15 +222,21 @@ void createTask_nullUrl_shouldSucceed() { TaskRequest request = new TaskRequest("Write unit tests", null, "2025-12-12", false, tags); - TagEntity tagEntity = new TagEntity("development", userEntity); - when(tagRepository.findByNameAndUser_id(anyString(), anyLong())) + Tag tagEntity = new Tag(null, "development", userEntity.getId()); + when(tagRepository.findByUserIdAndName(eq(userEntity.getId()), anyString())) .thenReturn(Optional.of(tagEntity)); - TaskEntity entity = new TaskEntity(); - entity.setId(123L); - entity.setDescription(request.description()); - entity.setHighPriority(request.highPriority()); - entity.setTags(Set.of(tagEntity)); + Task entity = new Task( + 123L, + null, + request.description(), + Boolean.FALSE, + null, + null, + Boolean.FALSE, + Boolean.FALSE, + request.highPriority() + ); when(taskRepository.save(any())).thenReturn(entity); TaskResponse response = taskService.createTask(request); @@ -208,7 +250,7 @@ void createTask_nullUrl_shouldSucceed() { void createTask_emptyUrl_shouldSucceed() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL)); - UserEntity userEntity = new UserEntity(); + User userEntity = new User(); userEntity.setId(USER_ID); userEntity.setEmail(USER_EMAIL); when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity)); @@ -217,15 +259,21 @@ void createTask_emptyUrl_shouldSucceed() { TaskRequest request = new TaskRequest("Write unit tests", List.of(), "2025-12-12", false, tags); - TagEntity tagEntity = new TagEntity("development", userEntity); - when(tagRepository.findByNameAndUser_id(anyString(), anyLong())) + Tag tagEntity = new Tag(null, "development", userEntity.getId()); + when(tagRepository.findByUserIdAndName(eq(userEntity.getId()), anyString())) .thenReturn(Optional.of(tagEntity)); - TaskEntity entity = new TaskEntity(); - entity.setId(123L); - entity.setDescription(request.description()); - entity.setHighPriority(request.highPriority()); - entity.setTags(Set.of(tagEntity)); + Task entity = new Task( + 123L, + null, + request.description(), + Boolean.FALSE, + null, + null, + Boolean.FALSE, + Boolean.FALSE, + request.highPriority() + ); when(taskRepository.save(any())).thenReturn(entity); TaskResponse response = taskService.createTask(request); @@ -239,7 +287,7 @@ void createTask_emptyUrl_shouldSucceed() { void createTask_fullUrl_shouldSucceed() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL)); - UserEntity userEntity = new UserEntity(); + User userEntity = new User(); userEntity.setId(USER_ID); userEntity.setEmail(USER_EMAIL); when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity)); @@ -249,20 +297,26 @@ void createTask_fullUrl_shouldSucceed() { new TaskRequest( "Write unit tests", List.of("debian.org"), "2025-12-12", false, tags); - TagEntity tagEntity = new TagEntity("development", userEntity); - when(tagRepository.findByNameAndUser_id(anyString(), anyLong())) + Tag tagEntity = new Tag(null, "development", userEntity.getId()); + when(tagRepository.findByUserIdAndName(eq(userEntity.getId()), anyString())) .thenReturn(Optional.of(tagEntity)); - TaskEntity entity = new TaskEntity(); - entity.setId(123L); - entity.setDescription(request.description()); - entity.setHighPriority(request.highPriority()); - entity.setTags(Set.of(tagEntity)); + Task entity = new Task( + 123L, + null, + request.description(), + Boolean.FALSE, + null, + null, + Boolean.FALSE, + Boolean.FALSE, + request.highPriority() + ); + when(taskRepository.save(any())).thenReturn(entity); - TaskUrlEntity urlEntity = new TaskUrlEntity(); - urlEntity.setId(new TaskUrlEntityPk(entity.getId(), "debian.org")); - when(taskUrlRepository.findAllById_taskId(entity.getId())).thenReturn(List.of(urlEntity)); + TaskUrl urlEntity = new TaskUrl(new TaskUrlPk(entity.id(), "debian.org")); + when(taskUrlRepository.findAllById_taskId(entity.id())).thenReturn(List.of(urlEntity)); TaskResponse response = taskService.createTask(request); @@ -276,16 +330,30 @@ void createTask_fullUrl_shouldSucceed() { void getAllTasks_happyPath_shouldSucceed() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL)); - UserEntity userEntity = new UserEntity(); + User userEntity = new User(); userEntity.setId(USER_ID); userEntity.setEmail(USER_EMAIL); when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity)); - TaskEntity entity = new TaskEntity(); - entity.setDescription("Writ unit tests"); - entity.setHighPriority(true); - entity.setTags(Set.of(new TagEntity("dev", userEntity))); - when(taskRepository.findAllByUser_id(USER_ID)).thenReturn(List.of(entity)); + Task entity = new Task( + 123L, + null, + "Writ unit tests", + Boolean.FALSE, + null, + null, + Boolean.FALSE, + Boolean.FALSE, + Boolean.TRUE + ); + + when(taskRepository.findAllByUserId(USER_ID)).thenReturn(List.of(entity)); + + Tag tagDev = new Tag(1L, "dev", USER_ID); + TaskNoteTag taskTags = new TaskNoteTag(tagDev.id(), tagDev.name(), tagDev.userId(), + entity.id()); + when(tagRepository.findAllByUserIdAndTaskIdInList(USER_ID, List.of(entity.id()))) + .thenReturn(List.of(taskTags)); List responses = taskService.getAllTasks(); @@ -299,24 +367,30 @@ void getAllTasks_happyPath_shouldSucceed() { void deleteTask_happyPath_shouldSucceed() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL)); - UserEntity userEntity = new UserEntity(); + User userEntity = new User(); userEntity.setId(USER_ID); userEntity.setEmail(USER_EMAIL); when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity)); Long taskId = 2525L; - TaskEntity taskEntity = new TaskEntity(); - taskEntity.setId(taskId); - taskEntity.setDescription("Test task"); - taskEntity.setHighPriority(true); - taskEntity.setTags(Set.of(new TagEntity("test", userEntity))); - taskEntity.setUser(userEntity); - when(taskRepository.findByIdAndUser_id(taskId, USER_ID)).thenReturn(Optional.of(taskEntity)); + Task taskEntity = new Task( + taskId, + userEntity.getId(), + "Test task", + Boolean.FALSE, + null, + null, + Boolean.FALSE, + Boolean.FALSE, + Boolean.TRUE + ); + + when(taskRepository.findByIdAndUserId(taskId, USER_ID)).thenReturn(Optional.of(taskEntity)); when(taskUrlRepository.findAllById_taskId(taskId)).thenReturn(List.of()); - doNothing().when(taskRepository).delete(taskEntity); + when(taskRepository.delete(taskEntity)).thenReturn(1); taskService.deleteTask(taskId); @@ -328,14 +402,14 @@ void deleteTask_happyPath_shouldSucceed() { void deleteTask_notFound_shouldFail() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL)); - UserEntity userEntity = new UserEntity(); + User userEntity = new User(); userEntity.setId(USER_ID); userEntity.setEmail(USER_EMAIL); when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity)); Long taskId = 2526L; - when(taskRepository.findByIdAndUser_id(taskId, USER_ID)).thenReturn(Optional.empty()); + when(taskRepository.findByIdAndUserId(taskId, USER_ID)).thenReturn(Optional.empty()); assertThrows(TaskNotFoundException.class, () -> taskService.deleteTask(taskId)); } @@ -345,38 +419,51 @@ void deleteTask_notFound_shouldFail() { void patchTask_happyPath_shouldSucceed() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL)); - UserEntity userEntity = new UserEntity(); + User userEntity = new User(); userEntity.setId(USER_ID); userEntity.setEmail(USER_EMAIL); when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity)); Long taskId = 2525L; - TaskEntity taskEntity = new TaskEntity(); - taskEntity.setId(taskId); - taskEntity.setDescription("Test task"); - taskEntity.setHighPriority(true); - taskEntity.setCompleted(false); - taskEntity.setTags(Set.of(new TagEntity("test", userEntity))); - taskEntity.setUser(userEntity); - when(taskRepository.findByIdAndUser_id(taskId, USER_ID)).thenReturn(Optional.of(taskEntity)); + Task taskEntity = new Task( + taskId, + userEntity.getId(), + "Test task", + Boolean.FALSE, + null, + null, + Boolean.FALSE, + Boolean.FALSE, + Boolean.TRUE + ); + + when(taskRepository.findByIdAndUserId(taskId, USER_ID)).thenReturn(Optional.of(taskEntity)); when(taskUrlRepository.findAllById_taskId(taskId)).thenReturn(List.of()); final String dueDate = "2026-12-31"; - TagEntity tagEntity = new TagEntity("test", userEntity); - when(tagRepository.findByNameAndUser_id(anyString(), anyLong())) - .thenReturn(Optional.of(tagEntity)); + Task savedTask = new Task( + taskId, + userEntity.getId(), + "Test task updated", + Boolean.TRUE, + null, + LocalDate.parse(dueDate), + Boolean.FALSE, + Boolean.FALSE, + Boolean.FALSE + ); - TaskEntity savedTask = new TaskEntity(); - savedTask.setDescription("Test task updated"); - savedTask.setHighPriority(false); - savedTask.setDueDate(LocalDate.parse(dueDate)); - savedTask.setCompleted(true); - savedTask.setTags(taskEntity.getTags()); when(taskRepository.save(any())).thenReturn(savedTask); + Tag tag = new Tag(22L, "test", userEntity.getId()); + TaskNoteTag taskTags = new TaskNoteTag(tag.id(), tag.name(), tag.userId(), + savedTask.id()); + when(tagRepository.findAllByUserIdAndTaskIdInList(USER_ID, List.of(savedTask.id()))) + .thenReturn(List.of(taskTags)); + List tags = List.of("test"); TaskPatchRequest patch = new TaskPatchRequest(true, "Test task updated", null, dueDate, false, tags); @@ -396,39 +483,48 @@ void patchTask_happyPath_shouldSucceed() { void patchTask_withUrl_shouldSucceed() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL)); - UserEntity userEntity = new UserEntity(); + User userEntity = new User(); userEntity.setId(USER_ID); userEntity.setEmail(USER_EMAIL); when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity)); Long taskId = 2525L; - TaskEntity taskEntity = new TaskEntity(); - taskEntity.setId(taskId); - taskEntity.setDescription("Test task"); - taskEntity.setHighPriority(true); - taskEntity.setCompleted(false); - taskEntity.setTags(Set.of(new TagEntity("test", userEntity))); - taskEntity.setUser(userEntity); - when(taskRepository.findByIdAndUser_id(taskId, USER_ID)).thenReturn(Optional.of(taskEntity)); - - TaskUrlEntity urlEntity = new TaskUrlEntity(); - urlEntity.setId(new TaskUrlEntityPk(taskId, "www.url.com")); + Task taskEntity = new Task( + taskId, + userEntity.getId(), + "Test task", + Boolean.FALSE, + null, + null, + Boolean.FALSE, + Boolean.FALSE, + Boolean.TRUE + ); + + when(taskRepository.findByIdAndUserId(taskId, USER_ID)).thenReturn(Optional.of(taskEntity)); + + TaskUrl urlEntity = new TaskUrl(new TaskUrlPk(taskId, "www.url.com")); when(taskUrlRepository.findAllById_taskId(taskId)).thenReturn(List.of(urlEntity)); - doNothing().when(taskUrlRepository).deleteAllById_taskId(taskId); final String dueDate = "2026-12-31"; - TagEntity tagEntity = new TagEntity("test", userEntity); - when(tagRepository.findByNameAndUser_id(anyString(), anyLong())) - .thenReturn(Optional.of(tagEntity)); + Tag tag = new Tag(510L, "test", userEntity.getId()); + when(tagRepository.findByUserIdAndName(eq(userEntity.getId()), anyString())) + .thenReturn(Optional.of(tag)); + + Task savedTask = new Task( + taskId, + userEntity.getId(), + "Test task updated", + Boolean.TRUE, + null, + LocalDate.parse(dueDate), + Boolean.FALSE, + Boolean.FALSE, + Boolean.FALSE + ); - TaskEntity savedTask = new TaskEntity(); - savedTask.setDescription("Test task updated"); - savedTask.setHighPriority(false); - savedTask.setDueDate(LocalDate.parse(dueDate)); - savedTask.setCompleted(true); - savedTask.setTags(taskEntity.getTags()); when(taskRepository.save(any())).thenReturn(savedTask); String url = "http://test.com"; @@ -436,7 +532,11 @@ void patchTask_withUrl_shouldSucceed() { TaskPatchRequest patch = new TaskPatchRequest(true, "Test task updated", List.of(url), dueDate, false, tags); - when(taskUrlRepository.saveAll(any())).thenReturn(List.of()); + TaskNoteTag taskTags = new TaskNoteTag(tag.id(), tag.name(), tag.userId(), + savedTask.id()); + when(tagRepository.findAllByUserIdAndTaskIdInList(USER_ID, List.of(savedTask.id()))) + .thenReturn(List.of(taskTags)); + TaskResponse patched = taskService.patchTask(taskId, patch); assertNotNull(patched); @@ -453,14 +553,14 @@ void patchTask_withUrl_shouldSucceed() { void patchTask_taskNotFound_shouldFail() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL)); - UserEntity userEntity = new UserEntity(); + User userEntity = new User(); userEntity.setId(USER_ID); userEntity.setEmail(USER_EMAIL); when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity)); Long taskId = 2525L; - when(taskRepository.findByIdAndUser_id(taskId, USER_ID)).thenReturn(Optional.empty()); + when(taskRepository.findByIdAndUserId(taskId, USER_ID)).thenReturn(Optional.empty()); List tags = List.of("test"); TaskPatchRequest patch = @@ -474,41 +574,56 @@ void patchTask_taskNotFound_shouldFail() { void patchTask_dueDateParseException_shouldFail() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL)); - UserEntity userEntity = new UserEntity(); + User userEntity = new User(); userEntity.setId(USER_ID); userEntity.setEmail(USER_EMAIL); when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity)); Long taskId = 2525L; - TaskEntity taskEntity = new TaskEntity(); - taskEntity.setId(taskId); - taskEntity.setDescription("Test task"); - taskEntity.setHighPriority(true); - taskEntity.setCompleted(false); - taskEntity.setTags(Set.of(new TagEntity("test", userEntity))); - taskEntity.setUser(userEntity); - when(taskRepository.findByIdAndUser_id(taskId, USER_ID)).thenReturn(Optional.of(taskEntity)); + Task taskEntity = new Task( + taskId, + userEntity.getId(), + "Test task", + Boolean.FALSE, + null, + null, + Boolean.FALSE, + Boolean.FALSE, + Boolean.TRUE + ); + + when(taskRepository.findByIdAndUserId(taskId, USER_ID)).thenReturn(Optional.of(taskEntity)); when(taskUrlRepository.findAllById_taskId(taskId)).thenReturn(List.of()); - TagEntity tagEntity = new TagEntity("test", userEntity); - when(tagRepository.findByNameAndUser_id(anyString(), anyLong())) - .thenReturn(Optional.of(tagEntity)); + Task savedTask = new Task( + taskId, + userEntity.getId(), + "Test task updated", + Boolean.TRUE, + null, + null, + Boolean.FALSE, + Boolean.FALSE, + Boolean.FALSE + ); - TaskEntity savedTask = new TaskEntity(); - savedTask.setDescription("Test task updated"); - savedTask.setHighPriority(false); - savedTask.setCompleted(true); - savedTask.setTags(taskEntity.getTags()); when(taskRepository.save(any())).thenReturn(savedTask); // wrong due date String dueDate = "2026-31-31"; - List tags = List.of("test"); + Tag tag = new Tag(616L, "test", userEntity.getId()); + List tags = List.of(tag.name()); + TaskNoteTag taskTags = new TaskNoteTag(tag.id(), tag.name(), tag.userId(), + savedTask.id()); + when(tagRepository.findAllByUserIdAndTaskIdInList(USER_ID, List.of(savedTask.id()))) + .thenReturn(List.of(taskTags)); + TaskPatchRequest patch = new TaskPatchRequest(true, "Test task updated", null, dueDate, false, tags); + TaskResponse patched = taskService.patchTask(taskId, patch); assertNotNull(patched); @@ -526,16 +641,22 @@ void patchTask_dueDateParseException_shouldFail() { void searchTasks_matchingTerm_shouldSucceed() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL)); - UserEntity userEntity = new UserEntity(); + User userEntity = new User(); userEntity.setId(USER_ID); userEntity.setEmail(USER_EMAIL); when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity)); - TaskEntity taskEntity = new TaskEntity(); - taskEntity.setId(1L); - taskEntity.setDescription("Write unit tests"); - taskEntity.setHighPriority(false); - taskEntity.setTags(Set.of(new TagEntity("development", userEntity))); + Task taskEntity = new Task( + 1L, + userEntity.getId(), + "Write unit tests", + Boolean.FALSE, + null, + null, + Boolean.FALSE, + Boolean.FALSE, + Boolean.FALSE + ); String searchTerm = "unit"; when(taskRepository.findAllBySearchTerm(searchTerm.toUpperCase(), USER_ID)) @@ -546,7 +667,7 @@ void searchTasks_matchingTerm_shouldSucceed() { assertNotNull(responses); assertFalse(responses.isEmpty()); assertEquals(1, responses.size()); - assertEquals(taskEntity.getDescription(), responses.get(0).description()); + assertEquals(taskEntity.description(), responses.get(0).description()); } @Test @@ -554,7 +675,7 @@ void searchTasks_matchingTerm_shouldSucceed() { void searchTasks_noMatchingTerm_shouldReturnEmptyList() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL)); - UserEntity userEntity = new UserEntity(); + User userEntity = new User(); userEntity.setId(USER_ID); userEntity.setEmail(USER_EMAIL); when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity)); @@ -575,7 +696,7 @@ void searchTasks_noMatchingTerm_shouldReturnEmptyList() { void searchTasks_nullSearchTerm_shouldReturnEmptyList() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL)); - UserEntity userEntity = new UserEntity(); + User userEntity = new User(); userEntity.setId(USER_ID); userEntity.setEmail(USER_EMAIL); when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity)); @@ -593,26 +714,41 @@ void searchTasks_nullSearchTerm_shouldReturnEmptyList() { void getTasksByFilter_all_shouldSucceed() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL)); - UserEntity userEntity = new UserEntity(); + User userEntity = new User(); userEntity.setId(USER_ID); userEntity.setEmail(USER_EMAIL); when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity)); - TaskEntity task1 = new TaskEntity(); - task1.setId(1L); - task1.setDescription("Task 1"); - task1.setHighPriority(false); - task1.setCompleted(false); - task1.setTags(Set.of(new TagEntity("tag1", userEntity))); - - TaskEntity task2 = new TaskEntity(); - task2.setId(2L); - task2.setDescription("Task 2"); - task2.setHighPriority(true); - task2.setCompleted(false); - task2.setTags(Set.of(new TagEntity("tag2", userEntity))); - - when(taskRepository.findAllByUser_id(USER_ID)).thenReturn(List.of(task1, task2)); + Task task1 = new Task( + 1L, + userEntity.getId(), + "Task 1", + Boolean.FALSE, + null, + null, + Boolean.FALSE, + Boolean.FALSE, + Boolean.FALSE + ); + + Task task2 = new Task( + 2L, + userEntity.getId(), + "Task 2", + Boolean.FALSE, + null, + null, + Boolean.FALSE, + Boolean.FALSE, + Boolean.TRUE + ); + + when(taskRepository.findAllByUserId(USER_ID)).thenReturn(List.of(task1, task2)); + + TaskNoteTag tag1 = new TaskNoteTag(null, "tag1", USER_ID, 1L); + TaskNoteTag tag2 = new TaskNoteTag(null, "tag2", USER_ID, 2L); + when(tagRepository.findAllByUserIdAndTaskIdInList(USER_ID, List.of(1L, 2L))) + .thenReturn(List.of(tag1, tag2)); List responses = taskService.getTasksByFilter("all"); @@ -626,26 +762,39 @@ void getTasksByFilter_all_shouldSucceed() { void getTasksByFilter_high_shouldSucceed() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL)); - UserEntity userEntity = new UserEntity(); + User userEntity = new User(); userEntity.setId(USER_ID); userEntity.setEmail(USER_EMAIL); when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity)); - TaskEntity task1 = new TaskEntity(); - task1.setId(1L); - task1.setDescription("Task 1"); - task1.setHighPriority(false); - task1.setCompleted(false); - task1.setTags(Set.of(new TagEntity("tag1", userEntity))); - - TaskEntity task2 = new TaskEntity(); - task2.setId(2L); - task2.setDescription("Task 2"); - task2.setHighPriority(true); - task2.setCompleted(false); - task2.setTags(Set.of(new TagEntity("tag2", userEntity))); - - when(taskRepository.findAllByUser_id(USER_ID)).thenReturn(List.of(task1, task2)); + Task task1 = new Task( + 1L, + userEntity.getId(), + "Task 1", + Boolean.FALSE, + null, + null, + Boolean.FALSE, + Boolean.FALSE, + Boolean.FALSE + ); + + Task task2 = new Task( + 2L, + userEntity.getId(), + "Task 2", + Boolean.FALSE, + null, + null, + Boolean.FALSE, + Boolean.FALSE, + Boolean.TRUE + ); + + when(taskRepository.findAllByUserId(USER_ID)).thenReturn(List.of(task1, task2)); + TaskNoteTag tag1 = new TaskNoteTag(22L, "tag2", USER_ID, 2L); + when(tagRepository.findAllByUserIdAndTaskIdInList(USER_ID, List.of(1L, 2L))) + .thenReturn(List.of(tag1)); List responses = taskService.getTasksByFilter("high"); @@ -658,26 +807,36 @@ void getTasksByFilter_high_shouldSucceed() { void getTasksByFilter_untagged_shouldSucceed() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL)); - UserEntity userEntity = new UserEntity(); + User userEntity = new User(); userEntity.setId(USER_ID); userEntity.setEmail(USER_EMAIL); when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity)); - TaskEntity task1 = new TaskEntity(); - task1.setId(1L); - task1.setDescription("Task 1"); - task1.setHighPriority(false); - task1.setCompleted(false); - task1.setTags(Set.of()); - - TaskEntity task2 = new TaskEntity(); - task2.setId(2L); - task2.setDescription("Task 2"); - task2.setHighPriority(true); - task2.setCompleted(false); - task2.setTags(Set.of()); - - when(taskRepository.findAllByUser_id(USER_ID)).thenReturn(List.of(task1, task2)); + Task task1 = new Task( + 1L, + userEntity.getId(), + "Task 1", + Boolean.FALSE, + null, + null, + Boolean.FALSE, + Boolean.FALSE, + Boolean.FALSE + ); + + Task task2 = new Task( + 2L, + userEntity.getId(), + "Task 2", + Boolean.FALSE, + null, + null, + Boolean.FALSE, + Boolean.FALSE, + Boolean.TRUE + ); + + when(taskRepository.findAllByUserId(USER_ID)).thenReturn(List.of(task1, task2)); List responses = taskService.getTasksByFilter("untagged"); @@ -691,26 +850,41 @@ void getTasksByFilter_untagged_shouldSucceed() { void getTasksByFilter_specificTag_shouldSucceed() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL)); - UserEntity userEntity = new UserEntity(); + User userEntity = new User(); userEntity.setId(USER_ID); userEntity.setEmail(USER_EMAIL); when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity)); - TaskEntity task1 = new TaskEntity(); - task1.setId(1L); - task1.setDescription("Task 1"); - task1.setHighPriority(false); - task1.setCompleted(false); - task1.setTags(Set.of(new TagEntity("tag1", userEntity))); - - TaskEntity task2 = new TaskEntity(); - task2.setId(2L); - task2.setDescription("Task 2"); - task2.setHighPriority(true); - task2.setCompleted(false); - task2.setTags(Set.of(new TagEntity("tag2", userEntity))); - - when(taskRepository.findAllByUser_id(USER_ID)).thenReturn(List.of(task1, task2)); + Task task1 = new Task( + 858L, + userEntity.getId(), + "Task 1", + Boolean.FALSE, + null, + null, + Boolean.FALSE, + Boolean.FALSE, + Boolean.FALSE + ); + + Task task2 = new Task( + 870L, + userEntity.getId(), + "Task 2", + Boolean.FALSE, + null, + null, + Boolean.FALSE, + Boolean.FALSE, + Boolean.TRUE + ); + + when(taskRepository.findAllByUserId(USER_ID)).thenReturn(List.of(task1, task2)); + + Tag tag1 = new Tag(883L, "tag1", USER_ID); + TaskNoteTag taskTags = new TaskNoteTag(tag1.id(), tag1.name(), tag1.userId(), task2.id()); + when(tagRepository.findAllByUserIdAndTaskIdInList(USER_ID, List.of(task1.id(), task2.id()))) + .thenReturn(List.of(taskTags)); List responses = taskService.getTasksByFilter("tag1"); @@ -723,12 +897,12 @@ void getTasksByFilter_specificTag_shouldSucceed() { void getTasksByFilter_noMatchingTasks_shouldReturnEmptyList() { when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(USER_EMAIL)); - UserEntity userEntity = new UserEntity(); + User userEntity = new User(); userEntity.setId(USER_ID); userEntity.setEmail(USER_EMAIL); when(authService.findByEmail(USER_EMAIL)).thenReturn(Optional.of(userEntity)); - when(taskRepository.findAllByUser_id(USER_ID)).thenReturn(List.of()); + when(taskRepository.findAllByUserId(USER_ID)).thenReturn(List.of()); List responses = taskService.getTasksByFilter("nonexistent"); diff --git a/server/src/test/java/br/com/tasknoteapp/server/service/UserSessionServiceTest.java b/server/src/test/java/br/com/tasknoteapp/server/service/UserSessionServiceTest.java index d4de1dd2..4c5cdab0 100644 --- a/server/src/test/java/br/com/tasknoteapp/server/service/UserSessionServiceTest.java +++ b/server/src/test/java/br/com/tasknoteapp/server/service/UserSessionServiceTest.java @@ -5,7 +5,7 @@ import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; -import br.com.tasknoteapp.server.entity.UserEntity; +import br.com.tasknoteapp.server.entity.User; import br.com.tasknoteapp.server.exception.UserNotFoundException; import br.com.tasknoteapp.server.repository.TagRepository; import br.com.tasknoteapp.server.response.TaskResponse; @@ -21,8 +21,6 @@ @ExtendWith(MockitoExtension.class) class UserSessionServiceTest { - // Test class generated by GitHub Copilot - @Mock private AuthService authService; @Mock private TaskService taskService; @Mock private NoteService noteService; @@ -38,8 +36,7 @@ void setUp() { @Test void deleteCurrentUserAccount_happyPath_shouldSucceed() { - // Arrange - UserEntity user = new UserEntity(); + User user = new User(); user.setId(1L); user.setEmail("user@domain.com"); @@ -53,10 +50,8 @@ void deleteCurrentUserAccount_happyPath_shouldSucceed() { new UserResponse( user.getId(), "user", user.getEmail(), false, null, null, null, null, "light")); - // Act UserResponse response = userSessionService.deleteCurrentUserAccount(); - // Assert assert response.userId() == 1L; assert response.email().equals("user@domain.com"); verify(taskService).deleteTask(task.id()); @@ -67,10 +62,8 @@ void deleteCurrentUserAccount_happyPath_shouldSucceed() { @Test void deleteCurrentUserAccount_userNotFound_shouldThrowException() { - // Arrange when(authService.getCurrentUser()).thenReturn(Optional.empty()); - // Act & Assert assertThrows( UserNotFoundException.class, () -> userSessionService.deleteCurrentUserAccount()); diff --git a/server/src/test/java/br/com/tasknoteapp/server/service/impl/JwtServiceImplTest.java b/server/src/test/java/br/com/tasknoteapp/server/service/impl/JwtServiceImplTest.java index b536b7ca..70427fd3 100644 --- a/server/src/test/java/br/com/tasknoteapp/server/service/impl/JwtServiceImplTest.java +++ b/server/src/test/java/br/com/tasknoteapp/server/service/impl/JwtServiceImplTest.java @@ -7,7 +7,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import br.com.tasknoteapp.server.entity.UserEntity; +import br.com.tasknoteapp.server.entity.User; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import java.time.LocalDateTime; @@ -47,7 +47,7 @@ void setUp() { @Test void generateToken_shouldCreateValidToken() { - UserEntity user = new UserEntity(); + User user = new User(); user.setId(testUserId); user.setEmail(testEmail); user.setAdmin(true); @@ -74,7 +74,7 @@ void generateToken_shouldCreateValidToken() { @Test void getEmailFromToken_shouldReturnCorrectEmail() { - UserEntity user = new UserEntity(); + User user = new User(); user.setId(testUserId); user.setEmail(testEmail); user.setAdmin(false); @@ -88,7 +88,7 @@ void getEmailFromToken_shouldReturnCorrectEmail() { @Test void extractExpiration_shouldReturnCorrectExpirationDate() { - UserEntity user = new UserEntity(); + User user = new User(); user.setId(testUserId); user.setEmail(testEmail); user.setAdmin(false); @@ -104,7 +104,7 @@ void extractExpiration_shouldReturnCorrectExpirationDate() { @Test void isTokenExpired_shouldReturnFalseForValidToken() { - UserEntity user = new UserEntity(); + User user = new User(); user.setId(testUserId); user.setEmail(testEmail); user.setAdmin(false); @@ -134,7 +134,7 @@ void isTokenExpired_shouldReturnTrueForExpiredToken() { @Test void validateTokenAndUser_shouldReturnTrueForValidTokenAndMatchingUser() { - UserEntity user = new UserEntity(); + User user = new User(); user.setId(testUserId); user.setEmail(testEmail); user.setAdmin(false); @@ -149,7 +149,7 @@ void validateTokenAndUser_shouldReturnTrueForValidTokenAndMatchingUser() { @Test void validateTokenAndUser_shouldReturnFalseForValidTokenButDifferentUser() { - UserEntity user = new UserEntity(); + User user = new User(); user.setId(testUserId); user.setEmail(testEmail); user.setAdmin(false); @@ -167,7 +167,7 @@ void validateTokenAndUser_shouldReturnFalseForValidTokenButDifferentUser() { @Test void validateTokenAndUser_shouldReturnFalseIfTokenIssuedBeforeLastPasswordChange() throws InterruptedException { - UserEntity user = new UserEntity(); + User user = new User(); user.setId(testUserId); user.setEmail(testEmail); user.setAdmin(false); diff --git a/server/src/test/java/br/com/tasknoteapp/server/templates/MailgunTemplateTest.java b/server/src/test/java/br/com/tasknoteapp/server/templates/MailgunTemplateTest.java index 90f470ef..a802aa69 100644 --- a/server/src/test/java/br/com/tasknoteapp/server/templates/MailgunTemplateTest.java +++ b/server/src/test/java/br/com/tasknoteapp/server/templates/MailgunTemplateTest.java @@ -17,7 +17,6 @@ void mailgunTemplateResetPwdTest() { @Test void mailgunTemplateResetPwdConfirmTest() { - // MailgunTemplateResetPwdConfirm MailgunTemplateResetPwdConfirm confirm = new MailgunTemplateResetPwdConfirm(); Assertions.assertNotNull(confirm.getName()); @@ -28,8 +27,6 @@ void mailgunTemplateResetPwdConfirmTest() { @Test void mailgunTemplateSignUpTest() { - // MailgunTemplateSignUp - MailgunTemplateSignUp signUp = new MailgunTemplateSignUp(); Assertions.assertNotNull(signUp.getName()); @@ -40,8 +37,6 @@ void mailgunTemplateSignUpTest() { @Test void mailgunTemplateEmailChangedTest() { - // MailgunTemplateEmailChanged - MailgunTemplateEmailChanged emailChanged = new MailgunTemplateEmailChanged(); Assertions.assertNotNull(emailChanged.getName()); diff --git a/server/src/test/resources/application.yml b/server/src/test/resources/application.yml index d55f1110..fe4dd329 100644 --- a/server/src/test/resources/application.yml +++ b/server/src/test/resources/application.yml @@ -25,16 +25,6 @@ spring: application: name: tasknote-api datasource: - driver-class-name: org.h2.Driver - initialization-mode: always - password: password - url: jdbc:h2:mem:tasknotedb;DB_CLOSE_DELAY=-1;NON_KEYWORDS=USER;DB_CLOSE_ON_EXIT=true - username: tasknote + url: jdbc:tc:postgresql:15.8-bookworm:///tasknote flyway: - enabled: false - jpa: - database-platform: org.hibernate.dialect.H2Dialect - hibernate: - ddl-auto: create-drop - generate-ddl: true - show-sql: true + enabled: true diff --git a/server/src/test/resources/sql/TaskRepositoryTest.sql b/server/src/test/resources/sql/TaskRepositoryTest.sql index 2efea894..95ee3df7 100644 --- a/server/src/test/resources/sql/TaskRepositoryTest.sql +++ b/server/src/test/resources/sql/TaskRepositoryTest.sql @@ -1,37 +1,37 @@ -- Create test user -insert into users (email, password, admin, created_at, inactivated_at, last_password_change) +insert into tasknote.users (email, password, admin, created_at, inactivated_at, last_password_change) select 'test@domain.com', 'a1b2c3d4f5g6', false, current_timestamp, null, current_timestamp -where not exists (select 1 from users where email = 'test@domain.com'); +where not exists (select 1 from tasknote.users where email = 'test@domain.com'); -- Create tags -insert into tags (name, user_id) -select 'refactoring', (select id from users where email='test@domain.com') -where not exists (select 1 from tags where name = 'refactoring' and user_id = (select id from users where email='test@domain.com')); +insert into tasknote.tags (name, user_id) +select 'refactoring', (select id from tasknote.users where email='test@domain.com') +where not exists (select 1 from tasknote.tags where name = 'refactoring' and user_id = (select id from tasknote.users where email='test@domain.com')); -insert into tags (name, user_id) -select 'cleaning', (select id from users where email='test@domain.com') -where not exists (select 1 from tags where name = 'cleaning' and user_id = (select id from users where email='test@domain.com')); +insert into tasknote.tags (name, user_id) +select 'cleaning', (select id from tasknote.users where email='test@domain.com') +where not exists (select 1 from tasknote.tags where name = 'cleaning' and user_id = (select id from tasknote.users where email='test@domain.com')); -- Create some tasks -insert into tasks (description, completed, user_id, last_update, due_date, high_priority) -select 'Refactor', false, (select id from users where email='test@domain.com'), current_timestamp, '2025-12-12', false -where not exists (select 1 from tasks where description = 'Refactor' and user_id = (select id from users where email='test@domain.com')); +insert into tasknote.tasks (description, completed, user_id, last_update, due_date, high_priority) +select 'Refactor', false, (select id from tasknote.users where email='test@domain.com'), current_timestamp, '2025-12-12', false +where not exists (select 1 from tasknote.tasks where description = 'Refactor' and user_id = (select id from tasknote.users where email='test@domain.com')); -insert into tasks (description, completed, user_id, last_update, due_date, high_priority) -select 'Cleanup', false, (select id from users where email='test@domain.com'), current_timestamp, '2025-12-12', false -where not exists (select 1 from tasks where description = 'Cleanup' and user_id = (select id from users where email='test@domain.com')); +insert into tasknote.tasks (description, completed, user_id, last_update, due_date, high_priority) +select 'Cleanup', false, (select id from tasknote.users where email='test@domain.com'), current_timestamp, '2025-12-12', false +where not exists (select 1 from tasknote.tasks where description = 'Cleanup' and user_id = (select id from tasknote.users where email='test@domain.com')); -- Associate tags with tasks -insert into task_tags (task_id, tag_id) +insert into tasknote.task_tags (task_id, tag_id) select t.id, tg.id -from tasks t -join tags tg on tg.name = 'refactoring' and tg.user_id = t.user_id -where t.description = 'Refactor' and t.user_id = (select id from users where email='test@domain.com') -and not exists (select 1 from task_tags where task_id = t.id and tag_id = tg.id); +from tasknote.tasks t +join tasknote.tags tg on tg.name = 'refactoring' and tg.user_id = t.user_id +where t.description = 'Refactor' and t.user_id = (select id from tasknote.users where email='test@domain.com') +and not exists (select 1 from tasknote.task_tags where task_id = t.id and tag_id = tg.id); -insert into task_tags (task_id, tag_id) +insert into tasknote.task_tags (task_id, tag_id) select t.id, tg.id -from tasks t -join tags tg on tg.name = 'cleaning' and tg.user_id = t.user_id -where t.description = 'Cleanup' and t.user_id = (select id from users where email='test@domain.com') -and not exists (select 1 from task_tags where task_id = t.id and tag_id = tg.id); +from tasknote.tasks t +join tasknote.tags tg on tg.name = 'cleaning' and tg.user_id = t.user_id +where t.description = 'Cleanup' and t.user_id = (select id from tasknote.users where email='test@domain.com') +and not exists (select 1 from tasknote.task_tags where task_id = t.id and tag_id = tg.id); diff --git a/server/src/test/resources/sql/TaskUrlRepositoryTest.sql b/server/src/test/resources/sql/TaskUrlRepositoryTest.sql index 70ea0333..b87e8ac2 100644 --- a/server/src/test/resources/sql/TaskUrlRepositoryTest.sql +++ b/server/src/test/resources/sql/TaskUrlRepositoryTest.sql @@ -1,49 +1,49 @@ -- Create test user -insert into users (email, password, admin, created_at, inactivated_at, last_password_change) +insert into tasknote.users (email, password, admin, created_at, inactivated_at, last_password_change) select 'test@domain.com', 'a1b2c3d4f5g6', false, current_timestamp, null, current_timestamp -where not exists (select 1 from users where email = 'test@domain.com'); +where not exists (select 1 from tasknote.users where email = 'test@domain.com'); -- Create tags -insert into tags (name, user_id) -select 'linux', (select id from users where email='test@domain.com') -where not exists (select 1 from tags where name = 'linux' and user_id = (select id from users where email='test@domain.com')); +insert into tasknote.tags (name, user_id) +select 'linux', (select id from tasknote.users where email='test@domain.com') +where not exists (select 1 from tasknote.tags where name = 'linux' and user_id = (select id from tasknote.users where email='test@domain.com')); -insert into tags (name, user_id) -select 'health', (select id from users where email='test@domain.com') -where not exists (select 1 from tags where name = 'health' and user_id = (select id from users where email='test@domain.com')); +insert into tasknote.tags (name, user_id) +select 'health', (select id from tasknote.users where email='test@domain.com') +where not exists (select 1 from tasknote.tags where name = 'health' and user_id = (select id from tasknote.users where email='test@domain.com')); -- Create a task -insert into tasks (description, completed, user_id, last_update, due_date, high_priority) -select 'Install Debian', false, (select id from users where email='test@domain.com'), current_timestamp, '2025-12-12', false -where not exists (select 1 from tasks where description = 'Install Debian' and user_id = (select id from users where email='test@domain.com')); +insert into tasknote.tasks (description, completed, user_id, last_update, due_date, high_priority) +select 'Install Debian', false, (select id from tasknote.users where email='test@domain.com'), current_timestamp, '2025-12-12', false +where not exists (select 1 from tasknote.tasks where description = 'Install Debian' and user_id = (select id from tasknote.users where email='test@domain.com')); -- Associate tag with task -insert into task_tags (task_id, tag_id) +insert into tasknote.task_tags (task_id, tag_id) select t.id, tg.id -from tasks t -join tags tg on tg.name = 'linux' and tg.user_id = t.user_id -where t.description = 'Install Debian' and t.user_id = (select id from users where email='test@domain.com') -and not exists (select 1 from task_tags where task_id = t.id and tag_id = tg.id); +from tasknote.tasks t +join tasknote.tags tg on tg.name = 'linux' and tg.user_id = t.user_id +where t.description = 'Install Debian' and t.user_id = (select id from tasknote.users where email='test@domain.com') +and not exists (select 1 from tasknote.task_tags where task_id = t.id and tag_id = tg.id); -- Create a task_url -insert into task_url (task_id, url) -select (select id from tasks where description = 'Install Debian'), 'debian.org' -where not exists (select 1 from task_url where url = 'debian.org'); +insert into tasknote.task_url (task_id, url) +select (select id from tasknote.tasks where description = 'Install Debian'), 'debian.org' +where not exists (select 1 from tasknote.task_url where url = 'debian.org'); -- Create another task -insert into tasks (description, completed, user_id, last_update, due_date, high_priority) -select 'Workout', false, (select id from users where email='test@domain.com'), current_timestamp, '2025-12-12', false -where not exists (select 1 from tasks where description = 'Workout' and user_id = (select id from users where email='test@domain.com')); +insert into tasknote.tasks (description, completed, user_id, last_update, due_date, high_priority) +select 'Workout', false, (select id from tasknote.users where email='test@domain.com'), current_timestamp, '2025-12-12', false +where not exists (select 1 from tasknote.tasks where description = 'Workout' and user_id = (select id from tasknote.users where email='test@domain.com')); -- Associate tag with task -insert into task_tags (task_id, tag_id) +insert into tasknote.task_tags (task_id, tag_id) select t.id, tg.id -from tasks t -join tags tg on tg.name = 'health' and tg.user_id = t.user_id -where t.description = 'Workout' and t.user_id = (select id from users where email='test@domain.com') -and not exists (select 1 from task_tags where task_id = t.id and tag_id = tg.id); +from tasknote.tasks t +join tasknote.tags tg on tg.name = 'health' and tg.user_id = t.user_id +where t.description = 'Workout' and t.user_id = (select id from tasknote.users where email='test@domain.com') +and not exists (select 1 from tasknote.task_tags where task_id = t.id and tag_id = tg.id); -- Create a task_url -insert into task_url (task_id, url) -select (select id from tasks where description = 'Workout'), 'healthier.com' -where not exists (select 1 from task_url where url = 'healthier.com'); +insert into tasknote.task_url (task_id, url) +select (select id from tasknote.tasks where description = 'Workout'), 'healthier.com' +where not exists (select 1 from tasknote.task_url where url = 'healthier.com'); diff --git a/server/src/test/resources/sql/UserRepositoryTest.sql b/server/src/test/resources/sql/UserRepositoryTest.sql index 9299d32c..2eaf088b 100644 --- a/server/src/test/resources/sql/UserRepositoryTest.sql +++ b/server/src/test/resources/sql/UserRepositoryTest.sql @@ -1,4 +1,4 @@ -- Create test user -insert into users (email, password, admin, created_at, inactivated_at, email_uuid, reset_token, last_password_change) +insert into tasknote.users (email, password, admin, created_at, inactivated_at, email_uuid, reset_token, last_password_change) select 'testuuid@domain.com', 'a1b2c3d4f5g6', false, current_timestamp, null, 'cc2b5506-83ed-5764-985e-611ad4ce8050', 'abc123456', current_timestamp -where not exists (select 1 from users where email = 'testuuid@domain.com'); +where not exists (select 1 from tasknote.users where email = 'testuuid@domain.com'); diff --git a/tools/api-tests/create-get-notes.sh b/tools/api-tests/create-get-notes.sh new file mode 100644 index 00000000..d8835b39 --- /dev/null +++ b/tools/api-tests/create-get-notes.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# Create note +echo "Creating note..." +curl -X POST -s \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ + http://localhost:8585/rest/notes \ + -d '{ + "title": "Note created from bash script", + "description": "note content from shell script testing api", + "url": "https://gentoo.org", + "tags": ["bash", "test"] + }' | jq + +echo "" +echo "" +echo "" +echo "Getting notes.." +# Get notes +curl -X GET -s \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ + http://localhost:8585/rest/notes | jq + +echo "" +echo "" +echo "" + +# Create note +#curl -X POST \ +# -H "Content-Type:application/json" \ +# http://localhost:8585/auth/sign-in \ + # -d '{"email":"email@email.com","password":"Teste@123","passwordAgain":"Teste@123","lang":"en"}' diff --git a/tools/api-tests/create-get-tasks.sh b/tools/api-tests/create-get-tasks.sh new file mode 100644 index 00000000..4f0accea --- /dev/null +++ b/tools/api-tests/create-get-tasks.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# Create task +echo "Creating task..." +curl -X POST -s \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ + http://localhost:8585/rest/tasks \ + -d '{ + "description": "task from shell script testing api", + "urls": ["https://gentoo.org"], + "dueDate": "2026-10-11", + "highPriority": true, + "tags": ["bash", "test"] + }' | jq + +echo "" +echo "" +echo "" +echo "Getting tasks.." +# Get tasks +curl -X GET -s \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ + http://localhost:8585/rest/tasks | jq + +echo "" +echo "" +echo "" + +# Create note +#curl -X POST \ +# -H "Content-Type:application/json" \ +# http://localhost:8585/auth/sign-in \ + # -d '{"email":"email@email.com","password":"Teste@123","passwordAgain":"Teste@123","lang":"en"}' diff --git a/tools/api-tests/sign-up-and-login.sh b/tools/api-tests/sign-up-and-login.sh new file mode 100644 index 00000000..64cb896b --- /dev/null +++ b/tools/api-tests/sign-up-and-login.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# Create user +curl -X PUT \ + -H "Content-Type:application/json" \ + http://localhost:8585/auth/sign-up \ + -d '{"email":"email@email.com","password":"Teste@123","passwordAgain":"Teste@123","lang":"en"}' + +# Confirm email +PGPASSWORD=default ./tools/confirm-user.sh + +# Login user +curl -X POST \ + -H "Content-Type:application/json" \ + http://localhost:8585/auth/sign-in \ + -d '{"email":"email@email.com","password":"Teste@123","passwordAgain":"Teste@123","lang":"en"}'