diff --git a/.env.example b/.env.example index ca10aa7..1d6a3db 100644 --- a/.env.example +++ b/.env.example @@ -1,19 +1,51 @@ -PG__DBNAME=db -PG__HOST=127.0.0.1 -PG__PORT=5432 RUST_LOG="INFO" BUILD_MODE=debug -ENABLE_REGISTRATION=true -SMTP_HOST=smtp.example.com -SMTP_USER=user -SMTP_PASS=password -SMTP_FROM=noreply@example.com -COOKIE_SECRET=supersecretkeymustbeatleast64byteslongforsecurityreasons1234567890 -FRONTEND_URL=http://localhost:3000 -MINIO_ACCESS_KEY=minioadmin -MINIO_SECRET_KEY=minioadmin -MINIO_URL=http://localhost:9000/ -S3_BUCKET_NAME=memory-map -SERVER_HOST=127.0.0.1 -SERVER_PORT=8000 -CORS_ALLOWED_ORIGINS=http://127.0.0.1:3000 + +MEMORY_MAP__PG__DBNAME=db +MEMORY_MAP__PG__HOST=127.0.0.1 +MEMORY_MAP__PG__PORT=5432 + +MEMORY_MAP__SERVER__HOST=127.0.0.1 +MEMORY_MAP__SERVER__PORT=8000 + +MEMORY_MAP__SMTP__HOST=smtp.example.com +MEMORY_MAP__SMTP__USER=user +MEMORY_MAP__SMTP__PASS=password +MEMORY_MAP__SMTP__FROM=noreply@example.com + +MEMORY_MAP__AUTH__COOKIE_SECRET=supersecretkeymustbeatleast64byteslongforsecurityreasons1234567890 +MEMORY_MAP__AUTH__ENABLE_REGISTRATION=true + +MEMORY_MAP__FRONTEND__URL=http://localhost:3000 + +MEMORY_MAP__CORS__ALLOWED_ORIGINS=http://127.0.0.1:3000 + +MEMORY_MAP__STORAGE__ENDPOINT_URL=http://127.0.0.1:9000/ +MEMORY_MAP__STORAGE__PUBLIC_ENDPOINT_URL=http://127.0.0.1:9000/ +MEMORY_MAP__STORAGE__ACCESS_KEY=memorymapdev +MEMORY_MAP__STORAGE__SECRET_KEY=memorymapdevsecret +MEMORY_MAP__STORAGE__BUCKET_NAME=memory-map +MEMORY_MAP__STORAGE__REGION=us-east-1 +MEMORY_MAP__STORAGE__FORCE_PATH_STYLE=true +MEMORY_MAP__STORAGE__PRESIGNED_URL_TTL_SECONDS=604800 + +MEMORY_MAP__OBJECT_LIFECYCLE__PENDING_UPLOAD_TIMEOUT_SECONDS=3600 +MEMORY_MAP__OBJECT_LIFECYCLE__UPLOAD_MAX_FILE_SIZE_BYTES=1073741824 +MEMORY_MAP__OBJECT_LIFECYCLE__UPLOAD_PART_SIZE_BYTES=8388608 +MEMORY_MAP__OBJECT_LIFECYCLE__UPLOAD_MAX_PART_COUNT=10000 +MEMORY_MAP__OBJECT_LIFECYCLE__UPLOAD_SESSION_TTL_SECONDS=3600 +MEMORY_MAP__OBJECT_LIFECYCLE__UPLOAD_SESSION_CLEANUP_RETRY_SECONDS=60 +MEMORY_MAP__OBJECT_LIFECYCLE__UPLOAD_SESSION_CLEANUP_LEASE_SECONDS=300 +MEMORY_MAP__OBJECT_LIFECYCLE__UPLOAD_SESSION_CLEANUP_MAX_ATTEMPTS=10 +MEMORY_MAP__OBJECT_LIFECYCLE__UPLOAD_SESSION_CLEANUP_BATCH_SIZE=1000 +MEMORY_MAP__OBJECT_LIFECYCLE__STORAGE_DELETION_RETRY_SECONDS=60 +MEMORY_MAP__OBJECT_LIFECYCLE__STORAGE_DELETION_LEASE_SECONDS=300 +MEMORY_MAP__OBJECT_LIFECYCLE__MAINTENANCE_INTERVAL_SECONDS=30 +MEMORY_MAP__OBJECT_LIFECYCLE__STORAGE_DELETION_BATCH_SIZE=1000 +MEMORY_MAP__OBJECT_LIFECYCLE__STORAGE_DELETION_MAX_ATTEMPTS=10 + +MEMORY_MAP__EMAIL_OUTBOX__RETRY_SECONDS=60 +MEMORY_MAP__EMAIL_OUTBOX__LEASE_SECONDS=300 +MEMORY_MAP__EMAIL_OUTBOX__WORKER_INTERVAL_SECONDS=30 +MEMORY_MAP__EMAIL_OUTBOX__BATCH_SIZE=100 +MEMORY_MAP__EMAIL_OUTBOX__MAX_ATTEMPTS=10 diff --git a/.github/actions/free-runner-space/action.yml b/.github/actions/free-runner-space/action.yml new file mode 100644 index 0000000..9440434 --- /dev/null +++ b/.github/actions/free-runner-space/action.yml @@ -0,0 +1,12 @@ +name: Free runner disk space +description: Remove unused hosted-runner tool caches before disk-heavy CI jobs. +runs: + using: composite + steps: + - name: Free runner disk space + shell: bash + run: | + df -h + sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL + docker system prune --all --force || true + df -h diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d2f62fc..bdf5470 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +permissions: + contents: read + env: CARGO_TERM_COLOR: always SKIP_DIRENV: "1" @@ -22,6 +25,9 @@ jobs: - uses: actions/checkout@v6 - uses: DeterminateSystems/nix-installer-action@main - uses: DeterminateSystems/magic-nix-cache-action@main + with: + use-flakehub: false + use-gha-cache: true - run: cd devenv && nix fmt -- --ci check: @@ -31,6 +37,9 @@ jobs: - uses: actions/checkout@v6 - uses: DeterminateSystems/nix-installer-action@main - uses: DeterminateSystems/magic-nix-cache-action@main + with: + use-flakehub: false + use-gha-cache: true - run: nix develop ./devenv/ --command just check clippy: @@ -40,6 +49,9 @@ jobs: - uses: actions/checkout@v6 - uses: DeterminateSystems/nix-installer-action@main - uses: DeterminateSystems/magic-nix-cache-action@main + with: + use-flakehub: false + use-gha-cache: true - run: nix develop ./devenv/ --command just clippy test: @@ -49,6 +61,9 @@ jobs: - uses: actions/checkout@v6 - uses: DeterminateSystems/nix-installer-action@main - uses: DeterminateSystems/magic-nix-cache-action@main + with: + use-flakehub: false + use-gha-cache: true - run: nix develop ./devenv/ --command just test doc: @@ -58,6 +73,9 @@ jobs: - uses: actions/checkout@v6 - uses: DeterminateSystems/nix-installer-action@main - uses: DeterminateSystems/magic-nix-cache-action@main + with: + use-flakehub: false + use-gha-cache: true - run: nix develop ./devenv/ --command just doc deny: @@ -67,6 +85,9 @@ jobs: - uses: actions/checkout@v6 - uses: DeterminateSystems/nix-installer-action@main - uses: DeterminateSystems/magic-nix-cache-action@main + with: + use-flakehub: false + use-gha-cache: true - run: nix develop ./devenv/ --command just deny frontend: @@ -76,4 +97,75 @@ jobs: - uses: actions/checkout@v6 - uses: DeterminateSystems/nix-installer-action@main - uses: DeterminateSystems/magic-nix-cache-action@main + with: + use-flakehub: false + use-gha-cache: true + - run: nix develop ./devenv/ --command just frontend-config - run: nix develop ./devenv/ --command just frontend-build + + storage-integration: + name: Storage Integration + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/free-runner-space + - uses: DeterminateSystems/nix-installer-action@main + - uses: DeterminateSystems/magic-nix-cache-action@main + with: + use-flakehub: false + use-gha-cache: true + - run: nix develop ./devenv/ --command just storage-ci + - name: Upload process-compose log + if: failure() + uses: actions/upload-artifact@v7 + with: + name: process-compose-log + path: process-compose.log + if-no-files-found: ignore + + backend-integration: + name: Backend Integration + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/free-runner-space + - uses: DeterminateSystems/nix-installer-action@main + - uses: DeterminateSystems/magic-nix-cache-action@main + with: + use-flakehub: false + use-gha-cache: true + - run: nix develop ./devenv/ --command just backend-integration + - name: Upload backend integration logs + if: failure() + uses: actions/upload-artifact@v7 + with: + name: backend-integration-logs + path: backend-integration-logs/ + if-no-files-found: ignore + + e2e: + name: E2E + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/free-runner-space + - uses: DeterminateSystems/nix-installer-action@main + - uses: DeterminateSystems/magic-nix-cache-action@main + with: + use-flakehub: false + use-gha-cache: true + - run: nix develop ./devenv/ --command just e2e + - name: Upload E2E logs and artifacts + if: failure() + uses: actions/upload-artifact@v7 + with: + name: e2e-artifacts + path: | + e2e-logs/ + frontend/playwright-report/ + frontend/test-results/ + frontend/blob-report/ + if-no-files-found: ignore diff --git a/.github/workflows/update-cargo-deps.yml b/.github/workflows/update-cargo-deps.yml index 244296a..df42a5c 100644 --- a/.github/workflows/update-cargo-deps.yml +++ b/.github/workflows/update-cargo-deps.yml @@ -15,6 +15,13 @@ jobs: contents: write pull-requests: write steps: + - name: Generate app token + id: app-token + uses: actions/create-github-app-token@v3 + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + - uses: actions/checkout@v6 - uses: DeterminateSystems/nix-installer-action@main - uses: DeterminateSystems/magic-nix-cache-action@main @@ -22,10 +29,24 @@ jobs: - name: Update Cargo dependencies run: nix develop ./devenv/ --command cargo update + - name: Check for changes + id: diff + run: | + if git diff --quiet Cargo.lock; then + echo "changed=false" >> "$GITHUB_OUTPUT" + else + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + + - name: Disable git hooks + if: steps.diff.outputs.changed == 'true' + run: git config --unset core.hooksPath || true + - name: Create pull request + if: steps.diff.outputs.changed == 'true' uses: peter-evans/create-pull-request@v7 with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ steps.app-token.outputs.token }} commit-message: "chore: update Cargo dependencies" branch: update-cargo-deps title: "chore: update Cargo dependencies" diff --git a/.github/workflows/update-nixpkgs.yml b/.github/workflows/update-nixpkgs.yml index 6703edc..b90acab 100644 --- a/.github/workflows/update-nixpkgs.yml +++ b/.github/workflows/update-nixpkgs.yml @@ -1,4 +1,4 @@ -name: Update nixpkgs +name: Update Nix flake inputs on: schedule: @@ -12,19 +12,40 @@ jobs: contents: write pull-requests: write steps: + - name: Generate app token + id: app-token + uses: actions/create-github-app-token@v3 + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + - uses: actions/checkout@v6 - uses: DeterminateSystems/nix-installer-action@main - uses: DeterminateSystems/magic-nix-cache-action@main - - name: Update nixpkgs input - run: nix flake update nixpkgs --flake ./devenv/ + - name: Update Nix flake inputs + run: nix flake update --flake ./devenv/ + + - name: Check for changes + id: diff + run: | + if git diff --quiet devenv/flake.lock; then + echo "changed=false" >> "$GITHUB_OUTPUT" + else + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + + - name: Disable git hooks + if: steps.diff.outputs.changed == 'true' + run: git config --unset core.hooksPath || true - name: Create pull request + if: steps.diff.outputs.changed == 'true' uses: peter-evans/create-pull-request@v7 with: - token: ${{ secrets.GITHUB_TOKEN }} - commit-message: "chore: update nixpkgs input" - branch: update-nixpkgs - title: "chore: update nixpkgs input" + token: ${{ steps.app-token.outputs.token }} + commit-message: "chore: update Nix flake inputs" + branch: update-nix-flake-inputs + title: "chore: update Nix flake inputs" body: | - Automated weekly update of the `nixpkgs` flake input. + Automated weekly update of moving Nix flake inputs. diff --git a/.gitignore b/.gitignore index 5a3e99a..093bb9a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,24 @@ target/ -data/ +data/minio1/ +data/pg1/ +data/postgres/ +data/rustfs/ .env +config.toml +config.local.toml +frontend/public/config.json .direnv/ -.cache/test-output/ +/result +/result-* +process-compose.log +backend-integration-logs/ +e2e-logs/ +frontend/playwright-report/ +frontend/test-results/ +frontend/blob-report/ .claude/worktrees/ .codex/worktrees/ .pre-commit-config.yaml .playwright-mcp/ +.cargo-deny/ +*.code-workspace diff --git a/AGENTS.md b/AGENTS.md index 5e84eaf..e1006b6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,17 +18,41 @@ just deny just doc just test just frontend-build +just verify-fast just verify ``` -Use `just filtered [args...]` for noisy verification output. +### Filtered Output + +Use `just filtered` when a `just` recipe is expected to produce noisy output. +Prefer it over hand-written shell pipelines such as `2>&1 | rg ...` because it +preserves the selected recipe's exit status, rejects unsupported recipes and +unsafe forwarded arguments, caps filtered matches, and prints the last captured +lines when a failing command has no filter matches. + +Examples: + +```sh +just filtered check '^(error|warning|[[:space:]]*-->)' +just filtered test '^(test .* \.\.\. FAILED|failures:|error)' +just filtered verify '^(Recipe|error|warning|failures:|FAILED|test result:)' +``` + +The first argument is the recipe, the second argument is the `rg` regex, and any +remaining arguments are forwarded to the selected recipe. Continue using +targeted `sed` ranges, `git diff --stat`, `git diff --name-only`, or +command-specific quiet flags for non-`just` output. Avoid dumping full logs, +full diffs, or broad command output unless explicitly requested. ## Project Shape -- `backend/` contains the Axum, GraphQL, PostgreSQL, MinIO, and Casbin backend. +- `backend/` contains the Axum, GraphQL, PostgreSQL, RustFS, and Casbin backend. - `frontend/` contains the Leptos CSR application built by Trunk. - `shared/` contains Rust code shared by the workspace crates. - `devenv/` contains the Nix development environment and service definitions. +- `docs/` contains long-form documentation (e.g. `deployment.md`). +- `scripts/` contains shared shell helpers used by `justfile` recipes + (e.g. `e2e-env.sh`, `service-graph.sh`). ## Editing Guidelines @@ -43,5 +67,8 @@ Use `just filtered [args...]` for noisy verification output ## Verification After changes, run the narrowest useful command first, then run `just verify` -before final handoff when feasible. If a command fails because dependencies or -network access are unavailable, report that directly. +before final handoff when feasible. `just verify` runs the full suite, including +the service-backed storage, backend-integration, and e2e tests that stand up the +local Postgres + RustFS service graph; use `just verify-fast` for a quick gate +when the service graph is unavailable or for fast iteration. If a command fails +because dependencies or network access are unavailable, report that directly. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 75ccc74..84fcc54 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,18 +28,30 @@ just check # Run cargo check just clippy # Run Clippy with warnings as errors just deny # Check dependency licenses and advisories just doc # Build docs and run ASCII/link checks -just test # Run tests with cached output +just test # Run tests just frontend-build # Build the Trunk frontend -just verify # Run the full verification suite +just verify-fast # Fast checks; skips the service-backed suites +just verify # Full suite, incl. service-backed integration and e2e ``` -## Tests +### Filtered Command Output -`just test` caches command output under `.cache/test-output/`. The cache key is -based on tracked file contents and the test arguments. +When a `just` recipe is expected to produce a large amount of output, use +`just filtered` instead of writing an ad-hoc shell pipeline. The first argument +is the `just` recipe to run, the second argument is a ripgrep regex used to +select output lines, and the remaining arguments are forwarded to the selected +recipe. -After creating a new source or test file, run `git add ` once so the cache -can see it. Use `just clean` to clear build artifacts and cached test output. +```sh +just filtered check '^(error|warning|[[:space:]]*-->)' +just filtered test '^(test .* \.\.\. FAILED|failures:|error)' +just filtered verify '^(Recipe|error|warning|failures:|FAILED|test result:)' +``` + +`just filtered` preserves the selected recipe's exit status, rejects unsupported +recipes and unsafe forwarded arguments, limits filtered matches so accidental +broad filters do not dump full logs, and prints the last captured lines when a +failing command has no filter matches. ## Documentation And Text @@ -54,5 +66,10 @@ Before opening a pull request, run: just verify ``` +This runs the full suite, including the service-backed storage, +backend-integration, and e2e tests, which stand up the local Postgres + RustFS +service graph and take several minutes. For faster iteration while developing, +`just verify-fast` runs everything except those service-backed suites. + Include tests for behavior changes. Avoid public API or user-visible behavior changes unless the issue or pull request explicitly calls for them. diff --git a/Cargo.lock b/Cargo.lock index 70fb8b1..271889b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,7 +14,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common", + "crypto-common 0.1.6", "generic-array", ] @@ -26,7 +26,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -66,6 +66,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -75,56 +81,6 @@ dependencies = [ "libc", ] -[[package]] -name = "anstream" -version = "0.6.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" - -[[package]] -name = "anstyle-parse" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.61.2", -] - [[package]] name = "any_spawner" version = "0.3.0" @@ -132,15 +88,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1384d3fe1eecb464229fcf6eebb72306591c56bf27b373561489458a7c73027d" dependencies = [ "futures", - "thiserror 2.0.17", + "thiserror 2.0.18", "wasm-bindgen-futures", ] [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "approx" @@ -159,7 +115,7 @@ checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", - "cpufeatures", + "cpufeatures 0.2.17", "password-hash", ] @@ -189,23 +145,23 @@ checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" [[package]] name = "async-graphql" -version = "7.1.0" +version = "7.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b75c5a43a58890d6dcc02d03952456570671332bb0a5a947b1f09c699912a5" +checksum = "1057a9f7ccf2404d94571dec3451ade1cb524790df6f1ada0d19c2a49f6b0f40" dependencies = [ "async-graphql-derive", "async-graphql-parser", "async-graphql-value", + "async-io", "async-trait", "asynk-strim", "base64", "bytes", "fast_chemail", "fnv", - "futures-timer", "futures-util", "handlebars", - "http", + "http 1.4.1", "indexmap", "mime", "multer", @@ -217,14 +173,14 @@ dependencies = [ "serde_urlencoded", "static_assertions_next", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] name = "async-graphql-axum" -version = "7.1.0" +version = "7.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "599e663e170f69baa0b9f18f52cdfd701e01ade0ac1baef2c4bc488cb68e35c1" +checksum = "a1e37c5532e4b686acf45e7162bc93da91fc2c702fb0d465efc2c20c8f973795" dependencies = [ "async-graphql", "axum", @@ -239,9 +195,9 @@ dependencies = [ [[package]] name = "async-graphql-derive" -version = "7.1.0" +version = "7.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c266ec9a094bbf2d088e016f71aa8d3be7f18c7343b2f0fe6d0e6c1e78977ea" +checksum = "2e6cbeadc8515e66450fba0985ce722192e28443697799988265d86304d7cc68" dependencies = [ "Inflector", "async-graphql-parser", @@ -250,15 +206,15 @@ dependencies = [ "proc-macro2", "quote", "strum", - "syn 2.0.111", - "thiserror 2.0.17", + "syn", + "thiserror 2.0.18", ] [[package]] name = "async-graphql-parser" -version = "7.1.0" +version = "7.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67e2188d3f1299087aa02cfb281f12414905ce63f425dbcfe7b589773468d771" +checksum = "e64ef70f77a1c689111e52076da1cd18f91834bcb847de0a9171f83624b07fbf" dependencies = [ "async-graphql-value", "pest", @@ -268,9 +224,9 @@ dependencies = [ [[package]] name = "async-graphql-value" -version = "7.1.0" +version = "7.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527a4c6022fc4dac57b4f03f12395e9a391512e85ba98230b93315f8f45f27fc" +checksum = "3e3ef112905abea9dea592fc868a6873b10ebd3f983e83308f995d6284e9ba41" dependencies = [ "bytes", "indexmap", @@ -278,11 +234,29 @@ dependencies = [ "serde_json", ] +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + [[package]] name = "async-lock" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ "event-listener", "event-listener-strategy", @@ -295,17 +269,6 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288f83726785267c6f2ef073a3d83dc3f9b81464e9f99898240cced85fce35a" -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - [[package]] name = "async-trait" version = "0.1.89" @@ -314,7 +277,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -344,7 +307,7 @@ dependencies = [ "manyhow", "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -360,14 +323,26 @@ dependencies = [ "proc-macro2", "quote", "quote-use", - "syn 2.0.111", + "syn", ] [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "aws-credential-types" +version = "1.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f20799b373a1be121fe3005fba0c2090af9411573878f224df44b42727fcaf7" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] [[package]] name = "aws-lc-rs" @@ -391,19 +366,334 @@ dependencies = [ "fs_extra", ] +[[package]] +name = "aws-runtime" +version = "1.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ed8e8c52d2dc2390ad9f15647fe663f71e9780b4262c190fbb823a32721566" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "bytes-utils", + "fastrand", + "http 0.2.12", + "http 1.4.1", + "http-body 0.4.6", + "http-body 1.0.1", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", +] + +[[package]] +name = "aws-sdk-s3" +version = "1.133.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "237aba2985e3c0a83e199cc7aa9a64a16c599875bc98170f00932f6199f19922" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-checksums", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "bytes", + "fastrand", + "hex", + "hmac 0.13.0", + "http 0.2.12", + "http 1.4.1", + "http-body 1.0.1", + "lru", + "percent-encoding", + "regex-lite", + "sha2 0.11.0", + "tracing", + "url", +] + +[[package]] +name = "aws-sigv4" +version = "1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7083fb918b38474ac65ffbf8a69fc8792d36879f4ac5f1667b43aec61efe9a5" +dependencies = [ + "aws-credential-types", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "crypto-bigint", + "form_urlencoded", + "hex", + "hmac 0.13.0", + "http 0.2.12", + "http 1.4.1", + "p256", + "percent-encoding", + "sha2 0.11.0", + "subtle", + "time", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffcaf626bdda484571968400c326a244598634dc75fd451325a54ad1a59acfc" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-checksums" +version = "0.64.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e8e65f4f81fcccdeb6c3eca2af17ac21d421a1786a26a394aecf421d616d3a" +dependencies = [ + "aws-smithy-http", + "aws-smithy-types", + "bytes", + "crc-fast", + "hex", + "http 1.4.1", + "http-body 1.0.1", + "http-body-util", + "md-5", + "pin-project-lite", + "sha1 0.11.0", + "sha2 0.11.0", + "tracing", +] + +[[package]] +name = "aws-smithy-eventstream" +version = "0.60.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf09d74e5e32f76b8762da505a3cd59303e367a664ca67295387baa8c1d7548" +dependencies = [ + "aws-smithy-types", + "bytes", + "crc32fast", +] + +[[package]] +name = "aws-smithy-http" +version = "0.63.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1ab2dc1c2c3749ead27180d333c42f11be8b0e934058fb4b2258ee8dbe5231" +dependencies = [ + "aws-smithy-eventstream", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "futures-util", + "http 1.4.1", + "http-body 1.0.1", + "http-body-util", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-http-client" +version = "1.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a2f165a7feee6f263028b899d0a181987f4fa7179a6411a32a439fba7c5f769" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "h2", + "http 1.4.1", + "hyper", + "hyper-rustls", + "hyper-util", + "pin-project-lite", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.62.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "517089205f18ab4adc5a3e02888cb139bbbbb2e168eac9f396216925d1fbeaf5" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-schema", + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-observability" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06c2315d173edbf1920da8ba3a7189695827002e4c0fc961973ab1c54abca9c" +dependencies = [ + "aws-smithy-runtime-api", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e6f5caf6fea86f8c2206541ab5857cfcda9013426cdbe8fa0098b9e2d32182" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-http-client", + "aws-smithy-observability", + "aws-smithy-runtime-api", + "aws-smithy-schema", + "aws-smithy-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.4.1", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "pin-project-lite", + "pin-utils", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc117c179ecf39a62a0a3f49f600e9ac26a7ad7dd172177999f83933af776c32" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api-macros", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.4.1", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-runtime-api-macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d7396fd9500589e62e460e987ecb671bad374934e55ec3b5f498cc7a8a8a7b7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "aws-smithy-schema" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7442cb268338f0eb8278140a107c046756aa01093d8ef5e99628d34ae09c94f5" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-types", + "http 1.4.1", +] + +[[package]] +name = "aws-smithy-types" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "056b66dbce2f81cc0c1e2b05bb402eb58f8a3530479d650efadd5bbae9a4050b" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.4.1", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", + "tokio", + "tokio-util", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce02add1aa3677d022f8adf81dcbe3046a95f17a1b1e8979c145cd21d3d22b3" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16bf10b03a3c01e6b3b7d47cd964e873ffe9e7d4e80fad16bd4c077cb068531" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-schema", + "aws-smithy-types", + "rustc_version", + "tracing", +] + [[package]] name = "axum" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ "axum-core", "base64", "bytes", "form_urlencoded", "futures-util", - "http", - "http-body", + "http 1.4.1", + "http-body 1.0.1", "http-body-util", "hyper", "hyper-util", @@ -411,14 +701,13 @@ dependencies = [ "matchit", "memchr", "mime", - "multer", "percent-encoding", "pin-project-lite", "serde_core", "serde_json", "serde_path_to_error", "serde_urlencoded", - "sha1", + "sha1 0.10.6", "sync_wrapper", "tokio", "tokio-tungstenite", @@ -430,14 +719,14 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.4.1", + "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", @@ -449,9 +738,9 @@ dependencies = [ [[package]] name = "axum-extra" -version = "0.12.5" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef252edff26ddba56bbcdf2ee3307b8129acb86f5749b68990c168a6fcc9c76" +checksum = "be44683b41ccb9ab2d23a5230015c9c3c55be97a25e4428366de8873103f7970" dependencies = [ "axum", "axum-core", @@ -459,8 +748,8 @@ dependencies = [ "cookie", "futures-core", "futures-util", - "http", - "http-body", + "http 1.4.1", + "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", @@ -469,17 +758,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "axum-macros" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - [[package]] name = "backend" version = "0.1.0" @@ -488,9 +766,10 @@ dependencies = [ "argon2", "async-graphql", "async-graphql-axum", + "aws-credential-types", + "aws-sdk-s3", "axum", "axum-extra", - "axum-macros", "blake3", "casbin", "config", @@ -503,15 +782,16 @@ dependencies = [ "hex", "jiff", "lettre", - "minio", "moka", + "parking_lot", "postgres-types", - "rand 0.8.6", + "rand 0.10.1", "refinery", + "reqwest", "serde", "serde_json", "shared", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tokio", "tokio-postgres", @@ -527,23 +807,39 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d27c3610c36aee21ce8ac510e6224498de4228ad772a171ed65643a24693a5a8" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + [[package]] name = "base64ct" -version = "1.8.1" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" dependencies = [ "serde_core", ] @@ -554,20 +850,21 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] name = "blake3" -version = "1.8.2" +version = "1.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", + "cpufeatures 0.3.0", ] [[package]] @@ -579,11 +876,20 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "by_address" @@ -606,17 +912,27 @@ dependencies = [ "serde", ] +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + [[package]] name = "camino" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" [[package]] name = "casbin" -version = "2.19.0" +version = "2.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b12705127ab9fcf4fbc22a0c93f441514fe7bd7a7248ce443e4bf531c54b7ee" +checksum = "c53f7476c2d0d9cd7ccc88c16ffc5c7889a0497b3462b10b12b5329adde69665" dependencies = [ "async-trait", "fixedbitset", @@ -645,9 +961,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.49" +version = "1.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" dependencies = [ "find-msvc-tools", "jobserver", @@ -655,12 +971,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.4" @@ -673,11 +983,22 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "js-sys", @@ -692,19 +1013,25 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", + "crypto-common 0.1.6", "inout", ] [[package]] name = "cmake" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ "cc", ] +[[package]] +name = "cmov" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" + [[package]] name = "codee" version = "0.3.5" @@ -713,7 +1040,7 @@ checksum = "a9dbbdc4b4d349732bc6690de10a9de952bd39ba6a065c586e26600b6b0b91f5" dependencies = [ "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -722,12 +1049,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2550f75b8cfac212855f6b1885455df8eaee8fe8e246b647d69146142e016084" -[[package]] -name = "colorchoice" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" - [[package]] name = "combine" version = "4.6.7" @@ -749,9 +1070,9 @@ dependencies = [ [[package]] name = "config" -version = "0.15.19" +version = "0.15.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30fa8254caad766fc03cb0ccae691e14bf3bd72bfff27f72802ce729551b3d6" +checksum = "f316c6237b2d38be61949ecd15268a4c6ca32570079394a2444d9ce2c72a72d8" dependencies = [ "async-trait", "convert_case 0.6.0", @@ -762,8 +1083,8 @@ dependencies = [ "serde-untagged", "serde_core", "serde_json", - "toml 0.9.8", - "winnow", + "toml 1.1.2+spec-1.1.0", + "winnow 1.0.3", "yaml-rust2", ] @@ -787,6 +1108,18 @@ dependencies = [ "web-sys", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "const-random" version = "0.1.18" @@ -802,24 +1135,25 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "once_cell", "tiny-keccak", ] [[package]] name = "const-str" -version = "0.6.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "451d0640545a0553814b4c646eb549343561618838e9b42495f466131fe3ad49" +checksum = "18f12cc9948ed9604230cdddc7c86e270f9401ccbe3c2e98a4378c5e7632212f" [[package]] name = "const_format" -version = "0.2.35" +version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" dependencies = [ "const_format_proc_macros", + "konst", ] [[package]] @@ -841,9 +1175,9 @@ checksum = "f67855af358fcb20fac58f9d714c94e2b228fe5694c1c9b4ead4a366343eda1b" [[package]] name = "constant_time_eq" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" [[package]] name = "convert_case" @@ -863,6 +1197,24 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "convert_case" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "convert_case_extras" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589c70f0faf8aa9d17787557d5eae854d7755cac50f5c3d12c81d3d57661cebb" +dependencies = [ + "convert_case 0.11.0", +] + [[package]] name = "cookie" version = "0.18.1" @@ -871,10 +1223,10 @@ checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ "aes-gcm", "base64", - "hmac", + "hmac 0.12.1", "percent-encoding", "rand 0.8.6", - "sha2", + "sha2 0.10.9", "subtle", "time", "version_check", @@ -916,19 +1268,32 @@ dependencies = [ ] [[package]] -name = "crc" -version = "3.4.0" +name = "cpufeatures" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" dependencies = [ - "crc-catalog", + "libc", ] [[package]] -name = "crc-catalog" -version = "2.4.0" +name = "crc-fast" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +checksum = "e75b2483e97a5a7da73ac68a05b629f9c53cff58d8ed1c77866079e18b00dba5" +dependencies = [ + "digest 0.10.7", + "spin 0.10.0", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] [[package]] name = "crossbeam-channel" @@ -960,17 +1325,38 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" -version = "0.1.7" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "rand_core 0.6.4", "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + [[package]] name = "ctr" version = "0.9.2" @@ -980,6 +1366,15 @@ dependencies = [ "cipher", ] +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + [[package]] name = "darling" version = "0.20.11" @@ -1011,7 +1406,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.111", + "syn", ] [[package]] @@ -1024,7 +1419,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.111", + "syn", ] [[package]] @@ -1035,7 +1430,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -1046,14 +1441,14 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core 0.23.0", "quote", - "syn 2.0.111", + "syn", ] [[package]] name = "dashmap" -version = "6.1.0" +version = "6.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" dependencies = [ "cfg-if", "crossbeam-utils", @@ -1065,9 +1460,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.9.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "deadpool" @@ -1090,7 +1485,7 @@ checksum = "3d697d376cbfa018c23eb4caab1fd1883dd9c906a8c034e8d9a3cb06a7e0bef9" dependencies = [ "async-trait", "deadpool", - "getrandom 0.2.16", + "getrandom 0.2.17", "serde", "tokio", "tokio-postgres", @@ -1107,35 +1502,35 @@ dependencies = [ ] [[package]] -name = "deranged" -version = "0.5.5" +name = "der" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "powerfmt", - "serde_core", + "const-oid 0.9.6", + "pem-rfc7468", + "zeroize", ] [[package]] -name = "derivative" -version = "2.2.0" +name = "deranged" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "powerfmt", + "serde_core", ] [[package]] name = "derive-where" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f" +checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -1156,7 +1551,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -1166,7 +1561,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.111", + "syn", ] [[package]] @@ -1175,11 +1570,24 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "const-oid 0.9.6", + "crypto-common 0.1.6", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.0", + "const-oid 0.10.2", + "crypto-common 0.2.2", + "ctutils", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -1188,7 +1596,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -1218,22 +1626,56 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "either_of" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216d23e0ec69759a17f05e1c553f3a6870e5ec73420fbb07807a6f34d5d1d5a4" +checksum = "5060e0a4cbf26a87550792688ade88e6b8aec9208613631a7a363bda7bc2d4cd" dependencies = [ "paste", "pin-project-lite", ] +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "email-encoding" version = "0.4.1" @@ -1262,29 +1704,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "env_filter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "jiff", - "log", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -1308,9 +1727,9 @@ dependencies = [ [[package]] name = "erased-serde" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" dependencies = [ "serde", "serde_core", @@ -1371,15 +1790,25 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "ff" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fixedbitset" @@ -1399,6 +1828,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" version = "0.3.2" @@ -1439,12 +1874,12 @@ dependencies = [ "log", "lucide-leptos", "mime", - "reqwest 0.13.1", + "reqwest", "serde", "serde_json", "shared", "thaw", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "wasm-bindgen", "wasm-bindgen-futures", @@ -1460,9 +1895,9 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -1475,9 +1910,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -1485,62 +1920,65 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", "futures-util", - "num_cpus", ] [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-lite" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "futures-core", + "pin-project-lite", +] [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-timer" -version = "3.0.3" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -1550,30 +1988,30 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -1586,8 +2024,24 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", "wasip2", + "wasip3", "wasm-bindgen", ] @@ -1600,7 +2054,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -1623,7 +2077,7 @@ dependencies = [ "futures-core", "futures-sink", "gloo-utils", - "http", + "http 1.4.1", "js-sys", "pin-project", "serde", @@ -1668,9 +2122,9 @@ dependencies = [ [[package]] name = "graphql_client" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83ce10ae0a8ba29e295f9296a4a400a5222b1aa0e65a72d66c5e489ed17fa217" +checksum = "b0f04840854efa7b06377d86fab117f598f5f5c95727067463417ccaf8aa7635" dependencies = [ "graphql_query_derive", "serde", @@ -1679,30 +2133,40 @@ dependencies = [ [[package]] name = "graphql_client_codegen" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61d9d9d96a7397130da7ce3664851f51ce850137e62b030ac78c8a529661151a" +checksum = "aa0141e66c8d0302f8a586df12ad5d0cf87c0fa8c391f2f5b5dc296312dce569" dependencies = [ "graphql-introspection-query", "graphql-parser", "heck", - "lazy_static", "proc-macro2", "quote", "serde", "serde_json", - "syn 2.0.111", + "syn", ] [[package]] name = "graphql_query_derive" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b684c77d1b5f9c6006068852e0e0e80c6df3ef85c24fe81ef26fbadbd595af77" +checksum = "e16ecf9bb87a6760cf5227f66cbe48bad7b89505aeb002aa9439ea090e6038a3" dependencies = [ "graphql_client_codegen", "proc-macro2", - "syn 2.0.111", + "syn", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", ] [[package]] @@ -1713,16 +2177,16 @@ checksum = "17e2ac29387b1aa07a1e448f7bb4f35b500787971e965b02842b900afa5c8f6f" [[package]] name = "h2" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http", + "http 1.4.1", "indexmap", "slab", "tokio", @@ -1732,9 +2196,9 @@ dependencies = [ [[package]] name = "handlebars" -version = "6.4.0" +version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b3f9296c208515b87bd915a2f5d1163d4b3f863ba83337d7713cf478055948e" +checksum = "d43ccdfe15a81ab0a8af639e90254227c9a46afd9c5f5b6ec7efaa345c4b0f00" dependencies = [ "derive_builder", "log", @@ -1743,7 +2207,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1761,7 +2225,7 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash", + "foldhash 0.1.5", ] [[package]] @@ -1769,6 +2233,17 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "hashlink" @@ -1781,11 +2256,11 @@ dependencies = [ [[package]] name = "hashlink" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230" dependencies = [ - "hashbrown 0.15.5", + "hashbrown 0.16.1", ] [[package]] @@ -1812,7 +2287,16 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", +] + +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", ] [[package]] @@ -1837,14 +2321,36 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" dependencies = [ "bytes", "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.1" @@ -1852,7 +2358,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.4.1", ] [[package]] @@ -1863,8 +2369,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.4.1", + "http-body 1.0.1", "pin-project-lite", ] @@ -1880,6 +2386,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hybrid-array" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +dependencies = [ + "typenum", +] + [[package]] name = "hydration_context" version = "0.3.0" @@ -1896,22 +2411,21 @@ dependencies = [ [[package]] name = "hyper" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", "h2", - "http", - "http-body", + "http 1.4.1", + "http-body 1.0.1", "httparse", "httpdate", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -1919,49 +2433,32 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "http", + "http 1.4.1", "hyper", "hyper-util", "rustls", - "rustls-pki-types", + "rustls-native-certs", "tokio", "tokio-rustls", "tower-service", ] -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - [[package]] name = "hyper-util" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "base64", "bytes", "futures-channel", - "futures-core", "futures-util", - "http", - "http-body", + "http 1.4.1", + "http-body 1.0.1", "hyper", "ipnet", "libc", @@ -1977,9 +2474,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -2016,12 +2513,13 @@ checksum = "6c97be924215abd5e630d84e95a47c710138a6559b4c55039f4f33aa897fa859" [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -2029,9 +2527,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -2042,9 +2540,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -2056,15 +2554,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -2076,15 +2574,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -2095,6 +2593,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -2114,9 +2618,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -2124,12 +2628,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -2143,15 +2647,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - [[package]] name = "interpolator" version = "0.5.0" @@ -2160,19 +2655,9 @@ checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8" [[package]] name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "iri-string" -version = "0.7.9" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" -dependencies = [ - "memchr", - "serde", -] +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "is-terminal" @@ -2185,12 +2670,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - [[package]] name = "itertools" version = "0.14.0" @@ -2202,15 +2681,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.16" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" +checksum = "392c70591e8749fe235ddaf513e6f58b26bce3dcc16524cecc8936f75afa161e" dependencies = [ "jiff-static", "jiff-tzdb-platform", @@ -2218,25 +2697,25 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.61.2", + "windows-link", ] [[package]] name = "jiff-static" -version = "0.2.16" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" +checksum = "47b605b0c050d845fc355bb11eb3f9a8deddc218ea60c76e61aa1f2adfb2c96a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] name = "jiff-tzdb" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68971ebff725b9e2ca27a601c5eb38a4c5d64422c4cbab0c535f248087eda5c2" +checksum = "c900ef84826f1338a557697dc8fc601df9ca9af4ac137c7fb61d4c6f2dfd3076" [[package]] name = "jiff-tzdb-platform" @@ -2249,25 +2728,52 @@ dependencies = [ [[package]] name = "jni" -version = "0.21.1" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" dependencies = [ - "cesu8", "cfg-if", "combine", + "jni-macros", "jni-sys", "log", - "thiserror 1.0.69", + "simd_cesu8", + "thiserror 2.0.18", "walkdir", - "windows-sys 0.45.0", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", ] [[package]] name = "jni-sys" -version = "0.3.0" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] [[package]] name = "jobserver" @@ -2281,10 +2787,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -2300,6 +2808,21 @@ dependencies = [ "serde", ] +[[package]] +name = "konst" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" +dependencies = [ + "konst_macro_rules", +] + +[[package]] +name = "konst_macro_rules" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" + [[package]] name = "lazy_static" version = "1.5.0" @@ -2319,17 +2842,23 @@ dependencies = [ "web-sys", ] +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "leptos" -version = "0.8.14" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87c98f6d751e524ff425ad9d63d53e120ed68311ffbc22bbd9c0b3c4005a421e" +checksum = "efa3982e7fe36c1de68f91f3c9083124f389a975523881f3d7e3363362feda41" dependencies = [ "any_spawner", "cfg-if", "either_of", "futures", - "getrandom 0.3.4", + "getrandom 0.4.2", "hydration_context", "leptos_config", "leptos_dom", @@ -2349,10 +2878,10 @@ dependencies = [ "server_fn", "slotmap", "tachys", - "thiserror 2.0.17", + "thiserror 2.0.18", "throw_error", - "typed-builder 0.22.0", - "typed-builder-macro 0.22.0", + "typed-builder", + "typed-builder-macro", "wasm-bindgen", "wasm-bindgen-futures", "wasm_split_helpers", @@ -2378,22 +2907,22 @@ dependencies = [ [[package]] name = "leptos_config" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071fc40aeb9fcab885965bad1887990477253ad51f926cd19068f45a44c59e89" +checksum = "0c06f751315bccc0d193fab302ac01d25bcfcd97474d4676440e7e3250dc3fc3" dependencies = [ "config", "regex", "serde", - "thiserror 2.0.17", - "typed-builder 0.21.2", + "thiserror 2.0.18", + "typed-builder", ] [[package]] name = "leptos_dom" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78f4330c88694c5575e0bfe4eecf81b045d14e76a4f8b00d5fd2a63f8779f895" +checksum = "35742e9ed8f8aaf9e549b454c68a7ac0992536e06856365639b111f72ab07884" dependencies = [ "js-sys", "or_poisoned", @@ -2406,31 +2935,32 @@ dependencies = [ [[package]] name = "leptos_hot_reload" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d61ec3e1ff8aaee8c5151688550c0363f85bc37845450764c31ff7584a33f38" +checksum = "9d2a0f220c8a5ef3c51199dfb9cdd702bc0eb80d52fbe70c7890adfaaae8a4b1" dependencies = [ "anyhow", "camino", "indexmap", - "parking_lot", + "or_poisoned", "proc-macro2", "quote", "rstml", "serde", - "syn 2.0.111", + "syn", "walkdir", ] [[package]] name = "leptos_macro" -version = "0.8.12" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d199fafe14b7925fd392764fdd851215909604955acfda7c0263b8722d57c23" +checksum = "9360df573fb57582384a8b7640a3de94ce6501d49be3b69f637cf11a42da484b" dependencies = [ "attribute-derive", "cfg-if", - "convert_case 0.8.0", + "convert_case 0.11.0", + "convert_case_extras", "html-escape", "itertools", "leptos_hot_reload", @@ -2441,15 +2971,15 @@ dependencies = [ "rstml", "rustc_version", "server_fn_macro", - "syn 2.0.111", + "syn", "uuid", ] [[package]] name = "leptos_meta" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d489e38d3f541e9e43ecc2e3a815527840345a2afca629b3e23fcc1dd254578" +checksum = "6c3efe657b4c55ed2e078922786ffe20acfb71767c3dd913767b09a35c75c890" dependencies = [ "futures", "indexmap", @@ -2462,9 +2992,9 @@ dependencies = [ [[package]] name = "leptos_router" -version = "0.8.10" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b824cae28db1551b71f8c2a45eab7bb98d61407f5adcc368cfe7b671e4a71d" +checksum = "c15158449162e099e2273442f7fd9b924f5cefd935d52af5755ec62aa819fa52" dependencies = [ "any_spawner", "either_of", @@ -2478,7 +3008,7 @@ dependencies = [ "rustc_version", "send_wrapper", "tachys", - "thiserror 2.0.17", + "thiserror 2.0.18", "url", "wasm-bindgen", "web-sys", @@ -2493,14 +3023,14 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] name = "leptos_server" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf1045af93050bf3388d1c138426393fc131f6d9e46a65519da884c033ed730" +checksum = "da974775c5ccbb6bd64be7f53f75e8321542e28f21563a416574dbe4d5447eae" dependencies = [ "any_spawner", "base64", @@ -2556,44 +3086,36 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.178" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.10" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ - "bitflags", "libc", - "redox_syscall", ] -[[package]] -name = "linear-map" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfae20f6b19ad527b550c223fddc3077a547fc70cda94b9b566575423fd303ee" - [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "lock_api" @@ -2606,9 +3128,18 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" + +[[package]] +name = "lru" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" +dependencies = [ + "hashbrown 0.16.1", +] [[package]] name = "lru-slab" @@ -2618,9 +3149,9 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "lucide-leptos" -version = "2.562.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b9d26634ee1f15a7c025695f109b33fa6a7bd5d8727c8890bf2e0f0049b70e1" +checksum = "36daed62ba36289baeb7eb4690a71909f465155ec3b3a9dc3ca2daf143c333cd" dependencies = [ "leptos", ] @@ -2634,7 +3165,7 @@ dependencies = [ "manyhow-macros", "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -2665,25 +3196,19 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "md-5" -version = "0.10.6" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" dependencies = [ "cfg-if", - "digest", + "digest 0.11.3", ] -[[package]] -name = "md5" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" - [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "mime" @@ -2692,71 +3217,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] -name = "minicov" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4869b6a491569605d66d3952bcdf03df789e5b536e5f0cf7758a7f08a55ae24d" -dependencies = [ - "cc", - "walkdir", -] - -[[package]] -name = "minio" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3824101357fa899d01c729e4a245776e20a03f2f6645979e86b9d3d5d9c42741" -dependencies = [ - "async-recursion", - "async-trait", - "base64", - "byteorder", - "bytes", - "chrono", - "crc", - "dashmap", - "derivative", - "env_logger", - "futures", - "futures-util", - "hex", - "hmac", - "http", - "hyper", - "lazy_static", - "log", - "md5", - "multimap", - "percent-encoding", - "rand 0.8.6", - "regex", - "reqwest 0.12.25", - "serde", - "serde_json", - "sha2", - "tokio", - "tokio-stream", - "tokio-util", - "urlencoding", - "xmltree", +name = "minicov" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4869b6a491569605d66d3952bcdf03df789e5b536e5f0cf7758a7f08a55ae24d" +dependencies = [ + "cc", + "walkdir", ] [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.61.2", ] [[package]] name = "moka" -version = "0.12.12" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3dec6bd31b08944e08b58fd99373893a6c17054d6f3ea5006cc894f4f4eee2a" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" dependencies = [ "async-lock", "crossbeam-channel", @@ -2781,7 +3266,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http", + "http 1.4.1", "httparse", "memchr", "mime", @@ -2789,28 +3274,19 @@ dependencies = [ "version_check", ] -[[package]] -name = "multimap" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" -dependencies = [ - "serde", -] - [[package]] name = "native-tls" -version = "0.2.14" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" dependencies = [ "libc", "log", "openssl", - "openssl-probe 0.1.6", + "openssl-probe", "openssl-sys", "schannel", - "security-framework 2.11.1", + "security-framework", "security-framework-sys", "tempfile", ] @@ -2850,9 +3326,18 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] [[package]] name = "num-modular" @@ -2889,6 +3374,24 @@ dependencies = [ "libc", ] +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", +] + +[[package]] +name = "objc2-system-configuration" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396" +dependencies = [ + "objc2-core-foundation", +] + [[package]] name = "oco_ref" version = "0.2.1" @@ -2896,24 +3399,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed0423ff9973dea4d6bd075934fdda86ebb8c05bdf9d6b0507067d4a1226371d" dependencies = [ "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" dependencies = [ "portable-atomic", ] -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - [[package]] name = "oorandom" version = "11.1.5" @@ -2948,20 +3445,14 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - -[[package]] -name = "openssl-probe" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" @@ -2991,6 +3482,24 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.9", +] + [[package]] name = "palette" version = "0.7.6" @@ -3012,7 +3521,7 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -3067,6 +3576,15 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -3075,9 +3593,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.4" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" dependencies = [ "memchr", "ucd-trie", @@ -3085,9 +3603,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.4" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f" +checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" dependencies = [ "pest", "pest_generator", @@ -3095,25 +3613,25 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.4" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625" +checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] name = "pest_meta" -version = "2.8.4" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82" +checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" dependencies = [ "pest", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -3166,7 +3684,7 @@ dependencies = [ "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -3189,29 +3707,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.10" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.10" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -3219,11 +3737,35 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "polling" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] [[package]] name = "polyval" @@ -3232,43 +3774,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.4" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ "portable-atomic", ] [[package]] name = "postgres-derive" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56df96f5394370d1b20e49de146f9e6c25aa9ae750f449c9d665eafecb3ccae6" +checksum = "ca1dad89d9ffdbf78502fde418eeede499b87772d88be780478f7f76dc8d471f" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] name = "postgres-native-tls" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac73153d92e4bde922bd6f1dfba7f1ab8132266c031153b55e20a1521cd36d49" +checksum = "fef4de47bb81477e0c3deaf153a1b10ae176484713ff1640969f4cb96b653ebc" dependencies = [ "native-tls", "tokio", @@ -3278,27 +3820,27 @@ dependencies = [ [[package]] name = "postgres-protocol" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbef655056b916eb868048276cfd5d6a7dea4f81560dfd047f97c8c6fe3fcfd4" +checksum = "56201207dac53e2f38e848e31b4b91616a6bb6e0c7205b77718994a7f49e70fc" dependencies = [ "base64", "byteorder", "bytes", "fallible-iterator", - "hmac", + "hmac 0.13.0", "md-5", "memchr", - "rand 0.9.4", - "sha2", + "rand 0.10.1", + "sha2 0.11.0", "stringprep", ] [[package]] name = "postgres-types" -version = "0.2.11" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4605b7c057056dd35baeb6ac0c0338e4975b1f2bef0f65da953285eb007095" +checksum = "8dc729a129e682e8d24170cd30ae1aa01b336b096cbb56df6d534ffec133d186" dependencies = [ "bytes", "fallible-iterator", @@ -3309,9 +3851,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -3338,16 +3880,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.111", + "syn", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", ] [[package]] name = "proc-macro-crate" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.23.9", + "toml_edit 0.25.11+spec-1.1.0", ] [[package]] @@ -3369,7 +3920,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -3385,9 +3936,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -3400,7 +3951,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", "version_check", "yansi", ] @@ -3419,7 +3970,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -3441,7 +3992,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -3463,9 +4014,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.42" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -3489,14 +4040,14 @@ dependencies = [ "proc-macro-utils", "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] name = "quoted_printable" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73" +checksum = "478e0585659a122aa407eb7e3c0e1fa51b1d8a870038bd29f0cf4a8551eea972" [[package]] name = "r-efi" @@ -3504,6 +4055,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.8.6" @@ -3522,7 +4079,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", ] [[package]] @@ -3542,7 +4110,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -3551,23 +4119,29 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "reactive_graph" -version = "0.2.11" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4043190442021086719fb9183daacb050f44d4ed8d3a1c8534e366d45dd95c29" +checksum = "00c5a025366836190c7030e883cc2bcd9e384ff555336e3c7954741ca411b177" dependencies = [ "any_spawner", "async-lock", @@ -3583,7 +4157,7 @@ dependencies = [ "send_wrapper", "serde", "slotmap", - "thiserror 2.0.17", + "thiserror 2.0.18", "web-sys", ] @@ -3599,24 +4173,24 @@ dependencies = [ "or_poisoned", "paste", "reactive_graph", - "reactive_stores_macro", + "reactive_stores_macro 0.2.6", "rustc-hash", "send_wrapper", ] [[package]] name = "reactive_stores" -version = "0.3.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b73d94139821e0a2f31fb4e0eaf6ebbcf4d15c5e2fb353dc3babd4f6d35674" +checksum = "c30fd35b7d299c591293bb69fed47a703eb2703b1cff0493e78b16ed007e5382" dependencies = [ - "dashmap", "guardian", + "indexmap", "itertools", "or_poisoned", "paste", "reactive_graph", - "reactive_stores_macro", + "reactive_stores_macro 0.4.2", "rustc-hash", "send_wrapper", ] @@ -3631,7 +4205,20 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.111", + "syn", +] + +[[package]] +name = "reactive_stores_macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d8e790a5ae5ddf9b7fa380c728375b06858e0cca7d063a73b3408320c523e1" +dependencies = [ + "convert_case 0.11.0", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -3645,9 +4232,9 @@ dependencies = [ [[package]] name = "refinery" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52c427f2572afe5c6cbfa2b1bf40071c89bf1a8539e958ea582842f6f38dcfae" +checksum = "ee5133e5b207e5703c2a4a9dc9bd8c8f2cc74c4ac04ca5510acaa907012c77ac" dependencies = [ "refinery-core", "refinery-macros", @@ -3655,9 +4242,9 @@ dependencies = [ [[package]] name = "refinery-core" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702655abfc67f93a6f735e9fa4ace7d2e580633f8961f28acbfd7583ddce936c" +checksum = "023a2a96d959c9b5b5da78e965bfdb1363b365bf5e84531a67d0eee827a702a3" dependencies = [ "async-trait", "cfg-if", @@ -3667,7 +4254,7 @@ dependencies = [ "regex", "serde", "siphasher", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tokio", "tokio-postgres", @@ -3678,22 +4265,22 @@ dependencies = [ [[package]] name = "refinery-macros" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5145756cdf293b5089dc6b4f103f1a1229cc55d67082c866f8c8289531c4b983" +checksum = "c56c2e960c8e47c7c5c30ad334afea8b5502da796a59e34d640d6239d876d924" dependencies = [ "proc-macro2", "quote", "refinery-core", "regex", - "syn 2.0.111", + "syn", ] [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -3703,9 +4290,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -3713,63 +4300,30 @@ dependencies = [ ] [[package]] -name = "regex-syntax" -version = "0.8.8" +name = "regex-lite" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" [[package]] -name = "reqwest" -version = "0.12.25" +name = "regex-syntax" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6eff9328d40131d43bd911d42d79eb6a47312002a4daefc9e37f17e74a7701a" -dependencies = [ - "base64", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-tls", - "hyper-util", - "js-sys", - "log", - "native-tls", - "percent-encoding", - "pin-project-lite", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-native-tls", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", -] +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.13.1" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64", "bytes", "encoding_rs", "futures-core", "h2", - "http", - "http-body", + "http 1.4.1", + "http-body 1.0.1", "http-body-util", "hyper", "hyper-rustls", @@ -3797,15 +4351,24 @@ dependencies = [ "web-sys", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac 0.12.1", + "subtle", +] + [[package]] name = "rhai" -version = "1.23.6" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4e35aaaa439a5bda2f8d15251bc375e4edfac75f9865734644782c9701b5709" +checksum = "5cdc552c1c198f0565e1deb9d85d380caedcc488e6ac3cb6f947c7ae77041e08" dependencies = [ "ahash", "bitflags", - "instant", "no-std-compat", "num-traits", "once_cell", @@ -3814,17 +4377,18 @@ dependencies = [ "smallvec", "smartstring", "thin-vec", + "web-time", ] [[package]] name = "rhai_codegen" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4322a2a4e8cf30771dd9f27f7f37ca9ac8fe812dddd811096a98483080dabe6" +checksum = "3cd3a7535e50bf36857e7be7bec276d334e8c2dfa469c2201226fd01638ea5ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -3835,7 +4399,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -3843,9 +4407,9 @@ dependencies = [ [[package]] name = "ron" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32" +checksum = "4147b952f3f819eca0e99527022f7d6a8d05f111aeb0a62960c74eb283bec8fc" dependencies = [ "bitflags", "once_cell", @@ -3865,9 +4429,9 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.111", + "syn", "syn_derive", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -3882,9 +4446,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc_version" @@ -3897,9 +4461,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags", "errno", @@ -3928,17 +4492,17 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe 0.2.0", + "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.5.1", + "security-framework", ] [[package]] name = "rustls-pki-types" -version = "1.13.1" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "web-time", "zeroize", @@ -3946,9 +4510,9 @@ dependencies = [ [[package]] name = "rustls-platform-verifier" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ "core-foundation 0.10.1", "core-foundation-sys", @@ -3959,7 +4523,7 @@ dependencies = [ "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki", - "security-framework 3.5.1", + "security-framework", "security-framework-sys", "webpki-root-certs", "windows-sys 0.61.2", @@ -3991,9 +4555,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "same-file" @@ -4006,9 +4570,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ "windows-sys 0.61.2", ] @@ -4020,23 +4584,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "security-framework" -version = "2.11.1" +name = "sec1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ - "bitflags", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", ] [[package]] name = "security-framework" -version = "3.5.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags", "core-foundation 0.10.1", @@ -4047,9 +4612,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", @@ -4057,9 +4622,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "send_wrapper" @@ -4086,7 +4651,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" dependencies = [ - "erased-serde 0.4.9", + "erased-serde 0.4.10", "serde", "serde_core", "typeid", @@ -4120,20 +4685,20 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -4155,7 +4720,7 @@ checksum = "f3faaf9e727533a19351a43cc5a8de957372163c7d35cc48c90b75cdda13c352" dependencies = [ "percent-encoding", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -4169,9 +4734,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.3" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] @@ -4190,19 +4755,19 @@ dependencies = [ [[package]] name = "server_fn" -version = "0.8.8" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc30228718f62d80a376964baf990edbcb5e97688fdc71183a8ef3d44cb6c89" +checksum = "5d60e4c1dfccd91fe0990141f69f1d5cf5679797ad53aa1b45e5bd658eb119f0" dependencies = [ "base64", "bytes", "const-str", "const_format", - "dashmap", "futures", "gloo-net", - "http", + "http 1.4.1", "js-sys", + "or_poisoned", "pin-project-lite", "rustc_version", "rustversion", @@ -4211,7 +4776,7 @@ dependencies = [ "serde_json", "serde_qs", "server_fn_macro_default", - "thiserror 2.0.17", + "thiserror 2.0.18", "throw_error", "url", "wasm-bindgen", @@ -4223,16 +4788,16 @@ dependencies = [ [[package]] name = "server_fn_macro" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "950b8cfc9ff5f39ca879c5a7c5e640de2695a199e18e424c3289d0964cabe642" +checksum = "1295b54815397d30d986b63f93cfd515fa86d5e528e0bb589ce9d530502f9e0f" dependencies = [ "const_format", - "convert_case 0.8.0", + "convert_case 0.11.0", "proc-macro2", "quote", "rustc_version", - "syn 2.0.111", + "syn", "xxhash-rust", ] @@ -4243,7 +4808,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63eb08f80db903d3c42f64e60ebb3875e0305be502bdc064ec0a0eab42207f00" dependencies = [ "server_fn_macro", - "syn 2.0.111", + "syn", ] [[package]] @@ -4253,8 +4818,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -4264,8 +4840,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -4289,24 +4876,51 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" -dependencies = [ - "libc", -] +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "siphasher" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "slog" @@ -4378,12 +4992,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4398,6 +5012,22 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -4451,7 +5081,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -4462,20 +5092,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.111" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -4491,7 +5110,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -4511,14 +5130,14 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] name = "system-configuration" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ "bitflags", "core-foundation 0.9.4", @@ -4537,9 +5156,9 @@ dependencies = [ [[package]] name = "tachys" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b2db11e455f7e84e2cc3e76f8a3f3843f7956096265d5ecff781eabe235077" +checksum = "2989c94c59db8497727875aa561d4d0daa3cc79b5774d5ced48263f7091beff1" dependencies = [ "any_spawner", "async-trait", @@ -4552,14 +5171,12 @@ dependencies = [ "indexmap", "itertools", "js-sys", - "linear-map", "next_tuple", "oco_ref", "or_poisoned", - "parking_lot", "paste", "reactive_graph", - "reactive_stores 0.3.0", + "reactive_stores 0.4.3", "rustc-hash", "rustc_version", "send_wrapper", @@ -4583,12 +5200,12 @@ checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" [[package]] name = "tempfile" -version = "3.23.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.2", "once_cell", "rustix", "windows-sys 0.61.2", @@ -4650,7 +5267,7 @@ checksum = "5af50a5123e480e7ee8961e6456f63530816875d0a19cb73060b8e61dd2a95fb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -4670,9 +5287,9 @@ dependencies = [ [[package]] name = "thin-vec" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "259cdf8ed4e4aca6f1e9d011e10bd53f524a2d0637d7b28450f6c64ac298c4c6" +checksum = "b0f7e269b48f0a7dd0146680fa24b50cc67fc0373f086a5b2f99bd084639b482" dependencies = [ "serde", ] @@ -4688,11 +5305,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -4703,18 +5320,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -4777,9 +5394,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -4787,9 +5404,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -4802,9 +5419,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.49.0" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -4819,13 +5436,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -4840,9 +5457,9 @@ dependencies = [ [[package]] name = "tokio-postgres" -version = "0.7.15" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b40d66d9b2cfe04b628173409368e58247e8eddbbd3b0e6c6ba1d09f20f6c9e" +checksum = "4dd8df5ef180f6364759a6f00f7aadda4fbbac86cdee37480826a6ff9f3574ce" dependencies = [ "async-trait", "byteorder", @@ -4857,7 +5474,7 @@ dependencies = [ "pin-project-lite", "postgres-protocol", "postgres-types", - "rand 0.9.4", + "rand 0.10.1", "socket2", "tokio", "tokio-util", @@ -4876,9 +5493,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -4887,9 +5504,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" +checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" dependencies = [ "futures-util", "log", @@ -4899,9 +5516,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.17" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -4925,15 +5542,15 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.8" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ "serde_core", - "serde_spanned 1.0.3", - "toml_datetime 0.7.3", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow", + "winnow 1.0.3", ] [[package]] @@ -4947,9 +5564,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] @@ -4965,28 +5582,28 @@ dependencies = [ "serde_spanned 0.6.9", "toml_datetime 0.6.11", "toml_write", - "winnow", + "winnow 0.7.15", ] [[package]] name = "toml_edit" -version = "0.23.9" +version = "0.25.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ "indexmap", - "toml_datetime 0.7.3", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow", + "winnow 1.0.3", ] [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow", + "winnow 1.0.3", ] [[package]] @@ -4997,9 +5614,9 @@ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", @@ -5013,20 +5630,21 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "bitflags", "bytes", "futures-util", - "http", - "http-body", - "iri-string", + "http 1.4.1", + "http-body 1.0.1", + "http-body-util", "pin-project-lite", "tower", "tower-layer", "tower-service", + "url", ] [[package]] @@ -5043,9 +5661,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -5061,14 +5679,14 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -5087,9 +5705,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "matchers", "nu-ansi-term", @@ -5111,59 +5729,38 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" dependencies = [ "bytes", "data-encoding", - "http", + "http 1.4.1", "httparse", "log", "rand 0.9.4", - "sha1", - "thiserror 2.0.17", - "utf-8", -] - -[[package]] -name = "typed-builder" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef81aec2ca29576f9f6ae8755108640d0a86dd3161b2e8bca6cfa554e98f77d" -dependencies = [ - "typed-builder-macro 0.21.2", + "sha1 0.10.6", + "thiserror 2.0.18", ] [[package]] name = "typed-builder" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "398a3a3c918c96de527dc11e6e846cd549d4508030b8a33e1da12789c856b81a" -dependencies = [ - "typed-builder-macro 0.22.0", -] - -[[package]] -name = "typed-builder-macro" -version = "0.21.2" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecb9ecf7799210407c14a8cfdfe0173365780968dc57973ed082211958e0b18" +checksum = "31aa81521b70f94402501d848ccc0ecaa8f93c8eb6999eb9747e72287757ffda" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", + "typed-builder-macro", ] [[package]] name = "typed-builder-macro" -version = "0.22.0" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e48cea23f68d1f78eb7bc092881b6bb88d3d6b5b7e6234f6f9c911da1ffb221" +checksum = "076a02dc54dd46795c2e9c8282ed40bcfb1e22747e955de9389a1de28190fb26" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -5174,9 +5771,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "ucd-trie" @@ -5192,9 +5789,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-normalization" @@ -5213,9 +5810,9 @@ checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-xid" @@ -5229,7 +5826,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common", + "crypto-common 0.1.6", "subtle", ] @@ -5241,9 +5838,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", @@ -5251,18 +5848,6 @@ dependencies = [ "serde", ] -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - [[package]] name = "utf8-width" version = "0.1.8" @@ -5275,19 +5860,13 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - [[package]] name = "uuid" -version = "1.19.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.2", "js-sys", "wasm-bindgen", ] @@ -5310,6 +5889,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "walkdir" version = "2.5.0" @@ -5335,26 +5920,47 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasi" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] name = "wasite" -version = "0.1.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" +checksum = "66fe902b4a6b8028a753d5424909b764ccf79b7a209eac9bf97e59cda9f71a42" +dependencies = [ + "wasi 0.14.7+wasi-0.2.4", +] [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ "cfg-if", "once_cell", @@ -5365,22 +5971,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.56" +version = "0.4.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" dependencies = [ - "cfg-if", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5388,31 +5991,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.111", + "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-bindgen-test" -version = "0.3.56" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e90e66d265d3a1efc0e72a54809ab90b9c0c515915c67cdf658689d2c22c6c" +checksum = "6bb55e2540ad1c56eec35fd63e2aea15f83b11ce487fd2de9ad11578dfc047ea" dependencies = [ "async-trait", "cast", @@ -5427,24 +6030,53 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test-macro", + "wasm-bindgen-test-shared", ] [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.56" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7150335716dce6028bead2b848e72f47b45e7b9422f64cccdc23bedca89affc1" +checksum = "caf0ca1bd612b988616bac1ab34c4e4290ef18f7148a1d8b7f31c150080e9295" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", +] + +[[package]] +name = "wasm-bindgen-test-shared" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cda5ecc67248c48d3e705d3e03e00af905769b78b9d2a1678b663b8b9d4472" + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", ] [[package]] name = "wasm-streams" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" dependencies = [ "futures-util", "js-sys", @@ -5455,9 +6087,9 @@ dependencies = [ [[package]] name = "wasm_split_helpers" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a114b3073258dd5de3d812cdd048cca6842342755e828a14dbf15f843f2d1b84" +checksum = "d0cb6d1008be3c4c5abc31a407bfb8c8449ae14efc8561c1db821f79b9614b0a" dependencies = [ "async-once-cell", "wasm_split_macros", @@ -5465,21 +6097,33 @@ dependencies = [ [[package]] name = "wasm_split_macros" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56481f8ed1a9f9ae97ea7b08a5e2b12e8adf9a7818a6ba952b918e09c7be8bf0" +checksum = "d0a659ffe5c7f4538aa6357c07e3d73221cc61eba03bd9a081e14bc91ed09b8c" dependencies = [ "base16", "quote", - "sha2", - "syn 2.0.111", + "sha2 0.10.9", + "syn", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", ] [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" dependencies = [ "js-sys", "wasm-bindgen", @@ -5497,20 +6141,22 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" dependencies = [ "rustls-pki-types", ] [[package]] name = "whoami" -version = "1.6.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +checksum = "998767ef88740d1f5b0682a9c53c24431453923962269c2db68ee43788c5a40d" dependencies = [ + "libc", "libredox", + "objc2-system-configuration", "wasite", "web-sys", ] @@ -5545,7 +6191,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -5556,7 +6202,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -5594,15 +6240,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -5630,21 +6267,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -5678,12 +6300,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -5696,12 +6312,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -5714,12 +6324,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -5744,12 +6348,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -5762,12 +6360,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -5780,12 +6372,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -5798,12 +6384,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -5818,40 +6398,128 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] [[package]] -name = "writeable" -version = "0.6.2" +name = "wit-bindgen" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] -name = "xml-rs" -version = "0.8.28" +name = "wit-bindgen-core" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] [[package]] -name = "xmltree" -version = "0.11.0" +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b619f8c85654798007fb10afa5125590b43b088c225a25fc2fec100a9fad0fc6" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ - "xml-rs", + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", ] +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + [[package]] name = "xxhash-rust" version = "0.8.15" @@ -5860,13 +6528,13 @@ checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" [[package]] name = "yaml-rust2" -version = "0.10.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2462ea039c445496d8793d052e13787f2b90e750b833afee748e601c17621ed9" +checksum = "631a50d867fafb7093e709d75aaee9e0e0d5deb934021fcea25ac2fe09edc51e" dependencies = [ "arraydeque", "encoding_rs", - "hashlink 0.10.0", + "hashlink 0.11.0", ] [[package]] @@ -5877,9 +6545,9 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -5888,54 +6556,54 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", "synstructure", ] @@ -5947,9 +6615,9 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -5958,9 +6626,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -5969,11 +6637,17 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/README.md b/README.md index d8a8c0c..c8196d2 100644 --- a/README.md +++ b/README.md @@ -53,16 +53,37 @@ cd memory-map ```sh cp .env.example .env +just frontend-config direnv allow ``` This installs all dependencies and auto-loads the development shell whenever you enter the directory. +`just frontend-config` creates the ignored local runtime config at +`frontend/public/config.json` from `frontend/config.example.json`. + You can optionally configure the build mode and other settings by editing `.env`: - `BUILD_MODE="debug"` (default): Faster compilation, includes debug info. - `BUILD_MODE="release"`: Optimised build, smaller binaries, slower compilation. - Database, SMTP, and S3 storage configurations. +- All backend environment variables share the `MEMORY_MAP__` prefix, with `__` + as the nested-path separator (e.g. + `MEMORY_MAP__STORAGE__ENDPOINT_URL`). See `docs/deployment.md` for the full + list. +- Local storage settings are `MEMORY_MAP__STORAGE__ENDPOINT_URL`, + `MEMORY_MAP__STORAGE__ACCESS_KEY`, `MEMORY_MAP__STORAGE__SECRET_KEY`, + `MEMORY_MAP__STORAGE__BUCKET_NAME`, `MEMORY_MAP__STORAGE__REGION`, + `MEMORY_MAP__STORAGE__FORCE_PATH_STYLE`, and + `MEMORY_MAP__STORAGE__PRESIGNED_URL_TTL_SECONDS`. +- The default local S3 API endpoint is `http://127.0.0.1:9000/`, with region + `us-east-1`, bucket `memory-map`, and path-style addressing enabled for + RustFS. Presigned media URLs default to a seven-day lifetime. +- Object storage cleanup settings live under + `MEMORY_MAP__OBJECT_LIFECYCLE__*`, including + `PENDING_UPLOAD_TIMEOUT_SECONDS`, `STORAGE_DELETION_RETRY_SECONDS`, + `STORAGE_DELETION_LEASE_SECONDS`, `MAINTENANCE_INTERVAL_SECONDS`, + `STORAGE_DELETION_BATCH_SIZE`, and `STORAGE_DELETION_MAX_ATTEMPTS`. ### 4. Start database & storage @@ -70,10 +91,10 @@ You can optionally configure the build mode and other settings by editing `.env` just servers ``` -MinIO object storage becomes available at: [http://localhost:9001/login](http://localhost:9001/login) +RustFS S3-compatible storage becomes available at: [http://localhost:9001/login](http://localhost:9001/login) -- **Username:** `minioadmin` -- **Password:** `minioadmin` +- **Username:** `memorymapdev` +- **Password:** `memorymapdevsecret` ### 5. Start backend @@ -99,20 +120,34 @@ Frontend app: [http://localhost:3000/](http://localhost:3000/) The project uses [Just](https://github.com/casey/just) as a task runner. -- `just servers`: Start PostgreSQL and MinIO via Nix. +- `just servers`: Start PostgreSQL and RustFS via Nix. +- `just clean-service-state`: Remove local PostgreSQL and storage service state. - `just backend`: Start the Axum backend with hot-reloading (via Bacon). +- `just frontend-config`: Create local frontend runtime config when missing. - `just frontend`: Start the Leptos frontend (via Trunk). - `just fmt`: Format Rust, Nix, Markdown, YAML, and TOML files. - `just check`: Run `cargo check` for the workspace. - `just clippy`: Run Clippy with warnings treated as errors. - `just deny`: Check Rust dependencies with `cargo-deny`. - `just doc`: Build documentation with warnings treated as errors and run ASCII/link checks. -- `just test`: Run the workspace test suite with cached output. -- `just frontend-build`: Build the frontend with Trunk. -- `just verify`: Run the full verification suite before submitting a PR. +- `just test`: Run the workspace test suite. +- `just storage-test`: Run ignored storage integration tests against the + configured S3-compatible endpoint. Missing local storage skips by default; set + `BACKEND_TEST_REQUIRE_SERVICE=true` to fail instead. +- `just frontend-build`: Build the frontend with Trunk using the existing + `frontend/public/config.json` runtime config. +- `just verify-fast`: Run every check that does not need the local service graph. +- `just verify`: Run the full verification suite (adds the service-backed storage, + backend-integration, and e2e tests) before submitting a PR. - `just regenerate-schema`: Introspect the backend and update the frontend GraphQL schema. - `just scan-hardcoded`: Scan the codebase for hardcoded secrets or values. +## Production Deployment + +Production uses real PostgreSQL, SMTP, S3-compatible storage, runtime secrets, +and a deployment-supplied frontend `/config.json`. See +[Production Deployment](docs/deployment.md). + ## Tech Stack | Layer | Technology | @@ -121,7 +156,7 @@ The project uses [Just](https://github.com/casey/just) as a task runner. | | [UnoCSS](https://unocss.dev/) | | Backend | [Axum](https://github.com/tokio-rs/axum) | | | [GraphQL](https://graphql.org) | -| Storage | [MinIO](https://min.io) | +| Storage | [RustFS](https://rustfs.com/) | | Database | [PostgreSQL](https://www.postgresql.org) | | Development Environment | [Nix package manager](https://nixos.org) | | | [nix-direnv](https://github.com/nix-community/nix-direnv) | @@ -131,17 +166,23 @@ The project uses [Just](https://github.com/casey/just) as a task runner. ``` memory-map/ -|-- .direnv/ # Direnv environment cache -|-- backend/ # Axum and GraphQL backend -|-- data/ # Database and storage volumes -|-- devenv/ # Nix development environment -|-- frontend/ # Leptos and UnoCSS frontend -|-- shared/ # Shared utilities and types -|-- .env.example # Environment configuration template -|-- justfile # Development commands -|-- Cargo.toml # Rust workspace configuration -|-- Cargo.lock # Rust dependency lock file -`-- README.md # Project documentation +|-- backend/ # Axum and GraphQL backend +|-- data/ # Database and storage volumes +|-- devenv/ # Nix development environment +|-- docs/ # Long-form documentation +|-- frontend/ # Leptos and UnoCSS frontend +|-- screenshots/ # README image assets +|-- scripts/ # Shared shell helpers for justfile recipes +|-- shared/ # Shared utilities and types +|-- .env.example # Environment configuration template +|-- AGENTS.md # AI coding assistant guidance +|-- Cargo.lock # Rust dependency lock file +|-- Cargo.toml # Rust workspace configuration +|-- config.example.toml # Optional TOML configuration template +|-- CONTRIBUTING.md # Contributor documentation +|-- justfile # Development commands +|-- LICENSE # Project license +`-- README.md # Project documentation ``` ## Contributing @@ -160,6 +201,10 @@ This command will: - Generate documentation - Run tests - Build the frontend +- Run the service-backed storage, backend-integration, and Playwright e2e suites + against the local Postgres + RustFS service graph + +For faster iteration that skips the service-backed suites, use `just verify-fast`. ## License diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 858f1d3..c8bcbdc 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -7,17 +7,23 @@ license = "BlueOak-1.0.0" [dependencies] anyhow = "1.0" argon2 = "0.5" -async-graphql = "7.1" -async-graphql-axum = "7.1" -axum = { version = "0.8", features = ["multipart"] } +async-graphql = "7.2" +async-graphql-axum = "7.2" +aws-credential-types = "1.2.14" +aws-sdk-s3 = { version = "1.133.0", default-features = false, features = [ + "default-https-client", + "http-1x", + "rt-tokio", + "sigv4a" +] } +axum = "0.8" axum-extra = { version = "0.12", features = [ "cookie", "cookie-private", "cookie-signed" ] } -axum-macros = "0.5" blake3 = "1.8" -casbin = { version = "2.19", features = ["logging", "runtime-tokio"] } +casbin = { version = "2.20", features = ["logging", "runtime-tokio"] } config = "0.15" deadpool = "0.12" deadpool-postgres = { version = "0.14", features = ["rt_tokio_1", "serde"] } @@ -32,20 +38,21 @@ lettre = { version = "0.11", features = [ "tokio1", "tokio1-native-tls" ] } -minio = "0.3" moka = { version = "0.12", features = ["future"] } +parking_lot = "0.12" postgres-types = { version = "0.2", features = ["derive"] } -rand = "0.8" +rand = "0.10" refinery = { version = "0.9", features = ["tokio-postgres"] } +reqwest = { version = "0.13", default-features = false, features = ["rustls"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" shared = { path = "../shared" } thiserror = "2.0" time = "0.3" -tokio = { version = "1.49", features = ["full"] } +tokio = { version = "1.52", features = ["full"] } tokio-postgres = { version = "0.7", features = ["with-jiff-0_2"] } tower = { version = "0.5", features = ["util"] } -tower-http = { version = "0.6", features = ["cors"] } +tower-http = { version = "0.6", features = ["cors", "limit"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/backend/migrations/V10__add_object_upload_sessions.sql b/backend/migrations/V10__add_object_upload_sessions.sql new file mode 100644 index 0000000..5b68187 --- /dev/null +++ b/backend/migrations/V10__add_object_upload_sessions.sql @@ -0,0 +1,21 @@ +CREATE TABLE object_upload_sessions ( + object_id BIGINT PRIMARY KEY REFERENCES objects(id) ON DELETE CASCADE, + storage_key TEXT NOT NULL UNIQUE CHECK (storage_key <> ''), + upload_id TEXT NOT NULL CHECK (upload_id <> ''), + content_type TEXT NOT NULL CHECK (content_type <> ''), + file_size BIGINT NOT NULL CHECK (file_size > 0), + part_size_bytes BIGINT NOT NULL CHECK (part_size_bytes >= 5 * 1024 * 1024), + expires_at timestamptz NOT NULL, + cleanup_attempts INTEGER NOT NULL DEFAULT 0 CHECK (cleanup_attempts >= 0), + cleanup_last_attempt_at timestamptz, + cleanup_next_attempt_at timestamptz NOT NULL DEFAULT now(), + cleanup_last_error TEXT, + created_at timestamptz NOT NULL DEFAULT now(), + CHECK (expires_at > created_at) +); + +CREATE INDEX object_upload_sessions_created_at_idx + ON object_upload_sessions (created_at); + +CREATE INDEX object_upload_sessions_cleanup_idx + ON object_upload_sessions (expires_at, cleanup_next_attempt_at, created_at); diff --git a/backend/migrations/V11__add_available_objects_view.sql b/backend/migrations/V11__add_available_objects_view.sql new file mode 100644 index 0000000..1e65c37 --- /dev/null +++ b/backend/migrations/V11__add_available_objects_view.sql @@ -0,0 +1,17 @@ +CREATE VIEW available_objects_with_users AS +SELECT + o.id, + o.name, + o.storage_key, + o.content_type, + o.made_on, + ST_Y(o.location::geometry) AS latitude, + ST_X(o.location::geometry) AS longitude, + o.user_id, + o.publicity, + COALESCE(array_agg(u.email) FILTER (WHERE u.email IS NOT NULL), '{}') AS allowed_users +FROM objects o +LEFT JOIN object_allowed_users oau ON o.id = oau.object_id +LEFT JOIN users u ON oau.user_id = u.id +WHERE o.storage_state = 'available' +GROUP BY o.id; diff --git a/backend/migrations/V12__add_email_outbox.sql b/backend/migrations/V12__add_email_outbox.sql new file mode 100644 index 0000000..997af5d --- /dev/null +++ b/backend/migrations/V12__add_email_outbox.sql @@ -0,0 +1,13 @@ +CREATE TABLE email_outbox ( + id BIGSERIAL PRIMARY KEY, + kind TEXT NOT NULL CHECK (kind <> ''), + payload JSONB NOT NULL, + created_at timestamptz NOT NULL DEFAULT now(), + attempts INTEGER NOT NULL DEFAULT 0 CHECK (attempts >= 0), + last_attempt_at timestamptz, + next_attempt_at timestamptz NOT NULL DEFAULT now(), + last_error TEXT +); + +CREATE INDEX email_outbox_claim_idx + ON email_outbox (next_attempt_at, created_at); diff --git a/backend/migrations/V8__add_object_storage_deletions.sql b/backend/migrations/V8__add_object_storage_deletions.sql new file mode 100644 index 0000000..153ecfb --- /dev/null +++ b/backend/migrations/V8__add_object_storage_deletions.sql @@ -0,0 +1,58 @@ +CREATE TYPE object_storage_state AS ENUM ('pending_upload', 'available', 'delete_pending'); + +ALTER TABLE objects ADD COLUMN storage_key TEXT; +UPDATE objects SET storage_key = name; +ALTER TABLE objects ALTER COLUMN storage_key SET NOT NULL; +ALTER TABLE objects ADD CONSTRAINT objects_storage_key_key UNIQUE (storage_key); + +ALTER TABLE objects ADD COLUMN content_type TEXT; +UPDATE objects +SET content_type = CASE + WHEN lower(name) LIKE '%.png' THEN 'image/png' + WHEN lower(name) LIKE '%.jpg' OR lower(name) LIKE '%.jpeg' THEN 'image/jpeg' + WHEN lower(name) LIKE '%.gif' THEN 'image/gif' + WHEN lower(name) LIKE '%.webp' THEN 'image/webp' + WHEN lower(name) LIKE '%.svg' THEN 'image/svg+xml' + WHEN lower(name) LIKE '%.avif' THEN 'image/avif' + WHEN lower(name) LIKE '%.apng' THEN 'image/apng' + WHEN lower(name) LIKE '%.mp4' THEN 'video/mp4' + WHEN lower(name) LIKE '%.webm' THEN 'video/webm' + WHEN lower(name) LIKE '%.ogv' OR lower(name) LIKE '%.ogg' THEN 'video/ogg' + WHEN lower(name) LIKE '%.mp3' THEN 'audio/mpeg' + WHEN lower(name) LIKE '%.wav' THEN 'audio/wav' + WHEN lower(name) LIKE '%.oga' THEN 'audio/ogg' + WHEN lower(name) LIKE '%.flac' THEN 'audio/flac' + WHEN lower(name) LIKE '%.aac' THEN 'audio/aac' + WHEN lower(name) LIKE '%.m4a' THEN 'audio/mp4' + ELSE 'application/octet-stream' +END; +ALTER TABLE objects ALTER COLUMN content_type SET NOT NULL; + +ALTER TABLE objects + ADD COLUMN storage_state object_storage_state NOT NULL DEFAULT 'available', + ADD COLUMN storage_state_updated_at timestamptz NOT NULL DEFAULT now(); + +ALTER TABLE objects DROP CONSTRAINT objects_name_key; +CREATE UNIQUE INDEX objects_active_name_key + ON objects (name) + WHERE storage_state <> 'delete_pending'; + +CREATE TABLE object_storage_deletions ( + storage_key TEXT PRIMARY KEY, + object_id BIGINT, + created_at timestamptz NOT NULL DEFAULT now(), + attempts INTEGER NOT NULL DEFAULT 0, + last_attempt_at timestamptz, + next_attempt_at timestamptz NOT NULL DEFAULT now(), + last_error TEXT +); + +CREATE INDEX object_storage_deletions_claim_idx + ON object_storage_deletions (next_attempt_at, created_at); + +CREATE INDEX objects_pending_upload_age_idx + ON objects (storage_state_updated_at) + WHERE storage_state = 'pending_upload'; + +CREATE INDEX objects_user_state_idx + ON objects (user_id, storage_state); diff --git a/backend/migrations/V9__add_password_reset_token_created_at.sql b/backend/migrations/V9__add_password_reset_token_created_at.sql new file mode 100644 index 0000000..2c06dde --- /dev/null +++ b/backend/migrations/V9__add_password_reset_token_created_at.sql @@ -0,0 +1,4 @@ +ALTER TABLE password_reset_tokens + ADD COLUMN created_at timestamptz NOT NULL DEFAULT now(); + +CREATE INDEX password_reset_tokens_user_id_idx ON password_reset_tokens (user_id); diff --git a/backend/src/app.rs b/backend/src/app.rs new file mode 100644 index 0000000..a4d4999 --- /dev/null +++ b/backend/src/app.rs @@ -0,0 +1,569 @@ +use { + crate::{ + AppState, + CachedGraphqlResponse, + CallerIdentity, + CasbinUser, + Config, + GraphqlMutationCacheEffect, + GraphqlResponseCacheKey, + SharedState, + constants::{ + GRAPHQL_BODY_LIMIT_BYTES, + GRAPHQL_RESPONSE_CACHE_MAX_CAPACITY_BYTES, + GRAPHQL_RESPONSE_CACHE_TTL_SECONDS, + }, + db::queries::SELECT_USER_BY_ID_QUERY, + errors::AppError, + graphiql, + graphql::queries::{ + mutation::Mutation, + query::Query, + }, + storage::StorageClient, + }, + async_graphql::{ + BatchResponse, + EmptySubscription, + Request as GraphqlRequestInner, + Response as GraphqlResponseBody, + Schema, + parser::types::{ + DocumentOperations, + OperationType, + Selection, + }, + }, + async_graphql_axum::GraphQLRequest, + axum::{ + Router, + body::{ + Body, + Bytes, + }, + extract::{ + Extension, + State, + }, + http::{ + HeaderMap, + HeaderValue, + Method, + StatusCode, + header, + request::Parts, + }, + response::{ + IntoResponse, + Response, + }, + routing::{ + get, + post, + }, + }, + axum_extra::extract::cookie::{ + Cookie, + Key, + PrivateCookieJar, + }, + casbin::Enforcer, + deadpool::managed::{ + Object, + Pool, + }, + deadpool_postgres::Manager, + moka::future::Cache, + std::{ + sync::{ + Arc, + atomic::AtomicU64, + }, + time::Duration, + }, + tokio::sync::RwLock, + tower_http::{ + cors::{ + AllowOrigin, + CorsLayer, + }, + limit::RequestBodyLimitLayer, + }, +}; + +type BackendState = AppState>; +type BackendSharedState = SharedState>; +type BackendSchema = Schema; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +struct GraphqlRequestOperationDetails { + operation_type: OperationType, + mutation_root_field_count: Option, +} + +pub fn build_shared_state( + cfg: Config, + pool: Pool, + storage: StorageClient, + enforcer: Arc>, +) -> Arc { + let graphql_response_cache = Cache::builder() + .max_capacity(GRAPHQL_RESPONSE_CACHE_MAX_CAPACITY_BYTES) + .weigher(|_key, value: &CachedGraphqlResponse| value.weight()) + .time_to_live(Duration::from_secs(GRAPHQL_RESPONSE_CACHE_TTL_SECONDS)) + .build(); + let key = Key::from(cfg.auth.cookie_secret.as_bytes()); + + Arc::new(SharedState { + pool, + storage, + graphql_response_cache_epoch: AtomicU64::new(0), + graphql_response_cache, + key, + config: cfg, + enforcer, + }) +} + +pub fn build_app(shared_state: Arc) -> Router { + let app_state = AppState { + inner: shared_state.clone(), + }; + let schema = build_schema(shared_state.clone()); + let cors = cors_layer(&shared_state.config); + let key = shared_state.key.clone(); + + Router::new() + .route("/", get(graphiql)) + .route( + "/", + post(graphql_handler) + .route_layer(RequestBodyLimitLayer::new(GRAPHQL_BODY_LIMIT_BYTES)) + .with_state(app_state.clone()), + ) + .layer(Extension(schema)) + .layer(Extension(key)) + .route_layer(cors) +} + +fn build_schema(shared_state: Arc) -> BackendSchema { + Schema::build(Query, Mutation, EmptySubscription) + .data(shared_state.key.clone()) + .data(shared_state) + .finish() +} + +fn cors_layer(config: &Config) -> CorsLayer { + let frontend_url = config.frontend.url.clone(); + let cors_allowed_origins = config.cors.allowed_origins.clone(); + + CorsLayer::new() + .allow_origin(AllowOrigin::predicate( + move |origin: &HeaderValue, _request_parts: &Parts| { + let origin_bytes = origin.as_bytes(); + origin_bytes == frontend_url.as_bytes() || + origin_bytes == cors_allowed_origins.as_bytes() + }, + )) + .allow_methods([Method::GET, Method::POST]) + .allow_headers([header::CONTENT_TYPE, header::AUTHORIZATION]) + .allow_credentials(true) +} + +async fn authenticated_caller_identity( + state: &BackendState, + jar: &PrivateCookieJar, +) -> Result, AppError> { + let Some(user_id) = jar.get("auth_token").and_then(|cookie| cookie.value().parse::().ok()) + else { + return Ok(None); + }; + let client = state.inner.pool.get().await?; + let statement = client.prepare_cached(SELECT_USER_BY_ID_QUERY).await?; + let Some(row) = client.query_opt(&statement, &[&user_id]).await? else { + return Ok(None); + }; + let role = row.try_get("role")?; + Ok(Some(CallerIdentity { + user_id, + casbin_user: CasbinUser { + id: user_id, + role, + }, + })) +} + +fn graphql_request_operation_details( + request: &mut GraphqlRequestInner +) -> Option { + let operation_name = request.operation_name.clone(); + let document = request.parsed_query().ok()?; + + let operation = match &document.operations { + DocumentOperations::Single(operation) => operation, + DocumentOperations::Multiple(operations) => { + let operation_name = operation_name?; + operations.get(operation_name.as_str())? + } + }; + let operation_type = operation.node.ty; + let mutation_root_field_count = if matches!(operation_type, OperationType::Mutation) { + mutation_root_field_count(operation) + } else { + None + }; + + Some(GraphqlRequestOperationDetails { + operation_type, + mutation_root_field_count, + }) +} + +#[cfg(test)] +fn graphql_request_operation_type(request: &mut GraphqlRequestInner) -> Option { + graphql_request_operation_details(request).map(|details| details.operation_type) +} + +fn mutation_root_field_count( + operation: &async_graphql::Positioned +) -> Option { + let mut field_count = 0; + for selection in &operation.node.selection_set.node.items { + match &selection.node { + Selection::Field(_) => { + field_count += 1; + } + Selection::FragmentSpread(_) | Selection::InlineFragment(_) => return None, + } + } + Some(field_count) +} + +fn graphql_mutation_should_invalidate_cache( + operation_type: Option, + mutation_root_field_count: Option, + cache_effect: &GraphqlMutationCacheEffect, +) -> bool { + if !matches!(operation_type, Some(OperationType::Mutation)) { + return false; + } + + !matches!( + mutation_root_field_count, + Some(field_count) + if field_count > 0 && cache_effect.non_invalidating_field_count() == field_count + ) +} + +fn hash_cache_component( + hasher: &mut blake3::Hasher, + bytes: &[u8], +) { + hasher.update(&(bytes.len() as u64).to_le_bytes()); + hasher.update(bytes); +} + +fn graphql_response_cache_key( + request: &GraphqlRequestInner, + user_id: Option, + authorization: Option<&HeaderValue>, + cache_epoch: u64, +) -> Option { + if !request.uploads.is_empty() || !request.extensions.is_empty() { + return None; + } + + let mut hasher = blake3::Hasher::new(); + hash_cache_component(&mut hasher, b"memory-map/graphql-response-cache/v1"); + hasher.update(&cache_epoch.to_le_bytes()); + + match user_id { + Some(user_id) => { + hasher.update(&[1]); + hasher.update(&user_id.to_le_bytes()); + } + None => { + hasher.update(&[0]); + } + }; + if let Some(authorization) = authorization { + hasher.update(&[1]); + hash_cache_component(&mut hasher, authorization.as_bytes()); + } else { + hasher.update(&[0]); + } + + hash_cache_component(&mut hasher, request.query.as_bytes()); + if let Some(operation_name) = &request.operation_name { + hasher.update(&[1]); + hash_cache_component(&mut hasher, operation_name.as_bytes()); + } else { + hasher.update(&[0]); + } + let variables = serde_json::to_vec(&request.variables).ok()?; + hash_cache_component(&mut hasher, &variables); + + let mut bytes = [0; 32]; + bytes.copy_from_slice(hasher.finalize().as_bytes()); + Some(GraphqlResponseCacheKey::new(bytes)) +} + +fn graphql_response_to_cache_entry( + response: GraphqlResponseBody +) -> Result { + let batch_response = BatchResponse::from(response); + let mut headers = HeaderMap::new(); + headers.insert( + header::CONTENT_TYPE, + HeaderValue::from_static("application/graphql-response+json"), + ); + if batch_response.is_ok() && + let Some(cache_control) = batch_response.cache_control().value() && + let Ok(value) = HeaderValue::from_str(&cache_control) + { + headers.insert(header::CACHE_CONTROL, value); + } + headers.extend(batch_response.http_headers()); + let bytes = Bytes::from(serde_json::to_vec(&batch_response)?); + + Ok(CachedGraphqlResponse { + status: StatusCode::OK, + headers, + bytes, + }) +} + +fn cache_entry_to_response(cached: CachedGraphqlResponse) -> Response { + let mut response = Response::new(Body::from(cached.bytes)); + *response.status_mut() = cached.status; + *response.headers_mut() = cached.headers; + response +} + +async fn graphql_handler( + State(state): State, + Extension(schema): Extension, + headers: HeaderMap, + jar: PrivateCookieJar, + req: GraphQLRequest, +) -> (PrivateCookieJar, Response) { + let mut req = req.into_inner(); + + let caller_identity = match authenticated_caller_identity(&state, &jar).await { + Ok(caller_identity) => caller_identity, + Err(error) => return (jar, error.into_response()), + }; + let user_id = caller_identity.as_ref().map(|identity| identity.user_id); + if let Some(caller_identity) = caller_identity { + req = req.data(caller_identity); + } + + let operation_details = graphql_request_operation_details(&mut req); + let operation_type = operation_details.map(|details| details.operation_type); + let mutation_root_field_count = + operation_details.and_then(|details| details.mutation_root_field_count); + let cache_epoch = state.inner.graphql_response_cache_epoch(); + let cache_key = if matches!(operation_type, Some(OperationType::Query)) { + graphql_response_cache_key(&req, user_id, headers.get(header::AUTHORIZATION), cache_epoch) + } else { + None + }; + if let Some(cache_key) = &cache_key && + let Some(cached_response) = state.inner.graphql_response_cache.get(cache_key).await + { + return (jar, cache_entry_to_response(cached_response)); + } + + // parking_lot::Mutex has no poisoning and is held only briefly around a vector + // push during resolver execution, so the lock acquisition is infallible. + let cookies = Arc::new(parking_lot::Mutex::new(Vec::>::new())); + req = req.data(cookies.clone()); + let mutation_cache_effect = Arc::new(GraphqlMutationCacheEffect::default()); + req = req.data(mutation_cache_effect.clone()); + + let response = schema.execute(req).await; + let response_is_ok = response.is_ok(); + let cached_response = match graphql_response_to_cache_entry(response) { + Ok(cached_response) => cached_response, + Err(error) => { + tracing::error!("Failed to serialize GraphQL response: {:?}", error); + return (jar, StatusCode::INTERNAL_SERVER_ERROR.into_response()); + } + }; + + if graphql_mutation_should_invalidate_cache( + operation_type, + mutation_root_field_count, + &mutation_cache_effect, + ) { + state.inner.invalidate_graphql_response_cache(); + } + + let mut jar = jar; + let issued_cookies = cookies.lock().clone(); + for cookie in &issued_cookies { + jar = jar.add(cookie.clone()); + } + + if let Some(cache_key) = cache_key && + response_is_ok && + issued_cookies.is_empty() && + !cached_response.headers.contains_key(header::SET_COOKIE) && + state.inner.graphql_response_cache_epoch() == cache_epoch + { + state.inner.graphql_response_cache.insert(cache_key, cached_response.clone()).await; + } + + (jar, cache_entry_to_response(cached_response)) +} + +#[cfg(test)] +mod tests { + use { + super::*, + async_graphql::{ + Value, + Variables, + }, + serde_json::json, + }; + + fn request_with_variable(id: i64) -> GraphqlRequestInner { + GraphqlRequestInner::new("query Object($id: Int!) { s3ObjectById(id: $id) { id } }") + .variables(Variables::from_json(json!({ "id": id }))) + } + + fn cache_key( + request: &GraphqlRequestInner, + user_id: Option, + cache_epoch: u64, + ) -> anyhow::Result { + graphql_response_cache_key(request, user_id, None, cache_epoch) + .ok_or_else(|| anyhow::anyhow!("request should be cacheable")) + } + + #[test] + fn graphql_operation_type_uses_selected_named_operation() { + let mut query = GraphqlRequestInner::new( + "query Read { config { enableRegistration } } mutation Write { logout }", + ) + .operation_name("Read"); + assert_eq!(graphql_request_operation_type(&mut query), Some(OperationType::Query)); + + let mut mutation = GraphqlRequestInner::new( + "query Read { config { enableRegistration } } mutation Write { logout }", + ) + .operation_name("Write"); + assert_eq!(graphql_request_operation_type(&mut mutation), Some(OperationType::Mutation)); + } + + #[test] + fn graphql_operation_type_skips_ambiguous_multi_operation_documents() { + let mut request = GraphqlRequestInner::new( + "query Read { config { enableRegistration } } mutation Write { logout }", + ); + assert_eq!(graphql_request_operation_type(&mut request), None); + } + + #[test] + fn graphql_operation_details_count_mutation_root_fields() { + let mut request = GraphqlRequestInner::new("mutation { a: logout b: logout }"); + + assert_eq!( + graphql_request_operation_details(&mut request), + Some(GraphqlRequestOperationDetails { + operation_type: OperationType::Mutation, + mutation_root_field_count: Some(2), + }) + ); + } + + #[test] + fn graphql_operation_details_treat_fragment_root_mutation_selection_as_uncountable() { + let mut request = GraphqlRequestInner::new( + "mutation { ...WriteFields } fragment WriteFields on Mutation { logout }", + ); + + assert_eq!( + graphql_request_operation_details(&mut request), + Some(GraphqlRequestOperationDetails { + operation_type: OperationType::Mutation, + mutation_root_field_count: None, + }) + ); + } + + #[test] + fn graphql_mutation_cache_effect_only_suppresses_when_all_root_fields_marked() { + let cache_effect = GraphqlMutationCacheEffect::default(); + assert!(!graphql_mutation_should_invalidate_cache( + Some(OperationType::Query), + None, + &cache_effect, + )); + assert!(graphql_mutation_should_invalidate_cache( + Some(OperationType::Mutation), + Some(2), + &cache_effect, + )); + + cache_effect.mark_non_invalidating_field(); + assert!(graphql_mutation_should_invalidate_cache( + Some(OperationType::Mutation), + Some(2), + &cache_effect, + )); + + cache_effect.mark_non_invalidating_field(); + assert!(!graphql_mutation_should_invalidate_cache( + Some(OperationType::Mutation), + Some(2), + &cache_effect, + )); + assert!(graphql_mutation_should_invalidate_cache( + Some(OperationType::Mutation), + None, + &cache_effect, + )); + } + + #[test] + fn graphql_response_cache_key_scopes_by_actor_variables_and_epoch() -> anyhow::Result<()> { + let key = cache_key(&request_with_variable(1), Some(10), 0)?; + assert_eq!(cache_key(&request_with_variable(1), Some(10), 0)?, key); + assert_ne!(cache_key(&request_with_variable(1), Some(11), 0)?, key); + assert_ne!(cache_key(&request_with_variable(2), Some(10), 0)?, key); + assert_ne!(cache_key(&request_with_variable(1), Some(10), 1)?, key); + Ok(()) + } + + #[test] + fn graphql_response_cache_key_skips_requests_with_extensions() { + let mut request = request_with_variable(1); + request.extensions.insert("cacheBypass".to_string(), Value::Boolean(true)); + assert!(graphql_response_cache_key(&request, Some(10), None, 0).is_none()); + } + + #[test] + fn graphql_response_cache_key_scopes_by_authorization_header() -> anyhow::Result<()> { + let request = request_with_variable(1); + let bearer_a = HeaderValue::from_static("Bearer a"); + let bearer_b = HeaderValue::from_static("Bearer b"); + + let key = graphql_response_cache_key(&request, Some(10), Some(&bearer_a), 0) + .ok_or_else(|| anyhow::anyhow!("request should be cacheable"))?; + + assert_eq!( + graphql_response_cache_key(&request, Some(10), Some(&bearer_a), 0) + .ok_or_else(|| anyhow::anyhow!("request should be cacheable"))?, + key + ); + assert_ne!( + graphql_response_cache_key(&request, Some(10), Some(&bearer_b), 0) + .ok_or_else(|| anyhow::anyhow!("request should be cacheable"))?, + key + ); + assert_ne!(cache_key(&request, Some(10), 0)?, key); + Ok(()) + } +} diff --git a/backend/src/bin/memory-map-storage-bootstrap.rs b/backend/src/bin/memory-map-storage-bootstrap.rs new file mode 100644 index 0000000..8769d7e --- /dev/null +++ b/backend/src/bin/memory-map-storage-bootstrap.rs @@ -0,0 +1,125 @@ +use { + anyhow::Context, + backend::{ + CorsConfig, + FrontendConfig, + storage::{ + StorageClient, + StorageConfig, + }, + }, + serde::Deserialize, + std::collections::BTreeSet, +}; + +const STORAGE_READY_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(60); + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let config = + StorageBootstrapConfig::from_env().context("Failed to read storage bootstrap config")?; + let storage = StorageClient::from_storage_config(&config.storage) + .context("Failed to build storage client")?; + storage + .wait_until_ready(STORAGE_READY_TIMEOUT) + .await + .context("Failed to wait for S3-compatible storage readiness")?; + storage.ensure_bucket_exists().await.context("Failed to ensure S3 bucket exists")?; + let cors_allowed_origins = config.upload_cors_allowed_origins(); + if !cors_allowed_origins.is_empty() { + storage + .configure_upload_cors(&cors_allowed_origins) + .await + .context("Failed to configure bucket CORS for browser uploads")?; + } + Ok(()) +} + +#[derive(Deserialize)] +struct StorageBootstrapConfig { + storage: StorageConfig, + #[serde(default)] + frontend: Option, + #[serde(default)] + cors: Option, +} + +impl StorageBootstrapConfig { + fn from_env() -> anyhow::Result { + let raw = config::Config::builder() + .add_source( + config::Environment::with_prefix("MEMORY_MAP") + .prefix_separator("__") + .separator("__"), + ) + .build() + .context("Failed to read config from environment")?; + let config: StorageBootstrapConfig = raw + .try_deserialize() + .context("Failed to deserialize storage bootstrap config from environment")?; + config.storage.validate()?; + Ok(config) + } + + fn upload_cors_allowed_origins(&self) -> Vec { + let mut origins = BTreeSet::new(); + if let Some(frontend) = &self.frontend { + insert_origin(&mut origins, &frontend.url); + } + if let Some(cors) = &self.cors { + for origin in cors.allowed_origins.split(',') { + insert_origin(&mut origins, origin); + } + } + origins.into_iter().collect() + } +} + +fn insert_origin( + origins: &mut BTreeSet, + origin: &str, +) { + let origin = origin.trim(); + if !origin.is_empty() { + origins.insert(origin.to_string()); + } +} + +#[cfg(test)] +mod tests { + use { + super::StorageBootstrapConfig, + backend::{ + CorsConfig, + FrontendConfig, + storage::StorageConfig, + }, + }; + + #[test] + fn upload_cors_allowed_origins_combines_frontend_and_cors_config() { + let config = StorageBootstrapConfig { + storage: StorageConfig { + endpoint_url: "http://127.0.0.1:9000".to_string(), + public_endpoint_url: None, + access_key: "access".to_string(), + secret_key: "secret".to_string(), + bucket_name: "memory-map".to_string(), + region: "us-east-1".to_string(), + force_path_style: true, + presigned_url_ttl_seconds: 60, + }, + frontend: Some(FrontendConfig { + url: "http://127.0.0.1:3000".to_string(), + }), + cors: Some(CorsConfig { + allowed_origins: "http://127.0.0.1:3000, http://localhost:3000".to_string(), + }), + }; + + assert_eq!( + config.upload_cors_allowed_origins(), + vec!["http://127.0.0.1:3000".to_string(), "http://localhost:3000".to_string(),] + ); + } +} diff --git a/backend/src/constants.rs b/backend/src/constants.rs index 070422a..46fc1b8 100644 --- a/backend/src/constants.rs +++ b/backend/src/constants.rs @@ -1,37 +1,14 @@ -// Amount of bytes to cache. -pub const CACHE_MAX_CAPACITY: u64 = 10_000; -// Cache time-to-live duration in seconds. Currently 10 minutes. -pub const CACHE_TTL_SECONDS: u64 = 600; -// Max body size for GraphQL queries (1MB). +// Total bytes the GraphQL response cache may hold across all entries, enforced +// through a per-entry weigher. Keeps memory predictable independent of how many +// distinct query/user pairs are cached. +pub const GRAPHQL_RESPONSE_CACHE_MAX_CAPACITY_BYTES: u64 = 64 * 1024 * 1024; +// Cache time-to-live duration in seconds. A short TTL is a backstop for changes +// made outside this process; in-process writes invalidate the cache explicitly. +pub const GRAPHQL_RESPONSE_CACHE_TTL_SECONDS: u64 = 600; +// Max body size for GraphQL requests (1MB). pub const GRAPHQL_BODY_LIMIT_BYTES: usize = 1024 * 1024; -// 1GB. -pub const BODY_MAX_SIZE_LIMIT_BYTES: usize = 1_073_741_824; - -// Errors -pub const ERR_INTERNAL_SERVER: &str = "Internal server error"; -pub const ERR_UNAUTHORIZED: &str = "Unauthorized"; -pub const ERR_FORBIDDEN: &str = "Forbidden"; -pub const ERR_NOT_FOUND: &str = "Not found: "; -pub const ERR_VALIDATION: &str = "Validation error: "; -pub const ERR_MULTIPART_MISSING_NAME: &str = "Multipart field missing name"; -pub const ERR_MULTIPART_MISSING_FILENAME: &str = "Multipart field missing filename"; -pub const ERR_MULTIPART_MISSING_CONTENT_TYPE: &str = "Multipart field missing content type"; -pub const ERR_UNSUPPORTED_FILE_TYPE: &str = "Unsupported file type: "; -pub const ERR_FAILED_READ_BYTES: &str = "Failed to read bytes: "; -pub const ERR_UPLOAD_MINIO: &str = "Failed to upload file to MinIO"; -pub const ERR_DB_CLIENT: &str = "Failed to get database client from pool"; -pub const ERR_HASHING: &str = "Hashing error: "; -pub const ERR_EMAIL: &str = "Email error: "; -pub const ERR_EMAIL_ADDRESS: &str = "Email address error: "; -pub const ERR_SMTP: &str = "SMTP error: "; -pub const ERR_INVALID_NUMBER: &str = "Invalid number format: "; -pub const ERR_CREATE_POOL: &str = "Failed to create pool: "; -pub const ERR_POOL: &str = "Pool error: "; -pub const ERR_DB: &str = "Database error: "; -pub const ERR_MULTIPART: &str = "Multipart error: "; -pub const ERR_CASBIN: &str = "Casbin error: "; -pub const ERR_CONFIG: &str = "Config error: "; -pub const ERR_MIGRATION: &str = "Migration error: "; -pub const ERR_SYSTEM_TIME: &str = "System time error: "; -pub const ERR_IO: &str = "IO error: "; +// Minimum seconds between password reset token issuances per user. +// Prevents bursts of password-reset emails to a single account, without +// causing user enumeration: throttled requests still return success. +pub const PASSWORD_RESET_RATE_LIMIT_SECONDS: i64 = 60; diff --git a/backend/src/controllers.rs b/backend/src/controllers.rs deleted file mode 100644 index e5fdf85..0000000 --- a/backend/src/controllers.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod api; diff --git a/backend/src/controllers/api.rs b/backend/src/controllers/api.rs deleted file mode 100644 index 8b41094..0000000 --- a/backend/src/controllers/api.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod locations; diff --git a/backend/src/controllers/api/locations.rs b/backend/src/controllers/api/locations.rs deleted file mode 100644 index 6438064..0000000 --- a/backend/src/controllers/api/locations.rs +++ /dev/null @@ -1,185 +0,0 @@ -use { - crate::{ - AppState, - constants::{ - ERR_DB_CLIENT, - ERR_FAILED_READ_BYTES, - ERR_MULTIPART_MISSING_CONTENT_TYPE, - ERR_MULTIPART_MISSING_FILENAME, - ERR_MULTIPART_MISSING_NAME, - ERR_UNSUPPORTED_FILE_TYPE, - ERR_UPLOAD_MINIO, - }, - errors::AppError, - graphql::{ - objects::{ - location::Location, - s3_object::PublicityOverride, - }, - queries::mutation::Mutation, - }, - }, - anyhow::Context, - axum::{ - Json, - body::Bytes, - extract::{ - Multipart, - State, - }, - response::IntoResponse, - }, - axum_extra::extract::cookie::PrivateCookieJar, - axum_macros::debug_handler, - deadpool::managed::Object, - deadpool_postgres::Manager, - shared::ALLOWED_MIME_TYPES, -}; - -#[derive(Debug)] -struct FileData { - filename: String, - content_type: String, - bytes: Bytes, -} - -#[debug_handler] -pub async fn post( - State(state): State>>, - jar: PrivateCookieJar, - mut multipart: Multipart, -) -> Result { - let user_id = if let Some(cookie) = jar.get("auth_token") && - let Ok(id) = cookie.value().parse::() - { - id - } else { - return Err(AppError::Unauthorized); - }; - - let mut latitude: Option = None; - let mut longitude: Option = None; - let mut made_on: Option = None; - let mut files: Vec = Vec::new(); - - while let Some(field) = multipart.next_field().await? { - let name = field - .name() - .ok_or_else(|| AppError::Validation(ERR_MULTIPART_MISSING_NAME.to_string()))? - .to_string(); - - match name.as_str() { - "latitude" => { - if let Ok(txt) = field.text().await && - let Ok(val) = txt.parse::() - { - latitude = Some(val); - } - } - "longitude" => { - if let Ok(txt) = field.text().await && - let Ok(val) = txt.parse::() - { - longitude = Some(val); - } - } - "made_on" => { - if let Ok(txt) = field.text().await && - !txt.is_empty() - { - // Store the ISO 8601 UTC timestamp string - made_on = Some(txt); - } - } - "files" => { - let filename = field - .file_name() - .ok_or_else(|| { - AppError::Validation(ERR_MULTIPART_MISSING_FILENAME.to_string()) - })? - .to_string(); - let content_type = field - .content_type() - .ok_or_else(|| { - AppError::Validation(ERR_MULTIPART_MISSING_CONTENT_TYPE.to_string()) - })? - .to_string(); - - if !ALLOWED_MIME_TYPES.contains(&content_type.as_str()) { - return Err(AppError::Validation(format!( - "{}{}", - ERR_UNSUPPORTED_FILE_TYPE, content_type - ))); - } - - let bytes = field - .bytes() - .await - .map_err(|e| AppError::Validation(format!("{}{}", ERR_FAILED_READ_BYTES, e)))?; - files.push(FileData { - filename, - content_type, - bytes, - }); - } - _ => {} - } - } - - tracing::debug!("Received Location:"); - tracing::debug!("Latitude: {:?}", latitude); - tracing::debug!("Longitude: {:?}", longitude); - tracing::debug!("Files: {} uploaded", files.len()); - - let mut uploaded_objects = Vec::new(); - - for file in files { - tracing::debug!( - " - Name: {}, Type: {}, Size: {} bytes", - file.filename, - file.content_type, - file.bytes.len() - ); - - state - .inner - .minio_client - .put_object_content(&state.inner.bucket_name, &file.filename, file.bytes) - .content_type(file.content_type) - .send() - .await - .context(ERR_UPLOAD_MINIO)?; - - let client = state.inner.pool.get().await.context(ERR_DB_CLIENT)?; - let location = if let (Some(latitude), Some(longitude)) = (latitude, longitude) { - Some(Location { - latitude, - longitude, - }) - } else { - None - }; - - match Mutation::upsert_s3_object_worker( - &client, - file.filename, - made_on.clone(), - location, - user_id, - PublicityOverride::Default, - vec![], - ) - .await - { - Ok(s3_object) => uploaded_objects.push(s3_object), - Err(e) => { - tracing::error!("Failed to upsert object: {:?}", e); - return Err(e); - } - } - - state.inner.update_last_modified(); - } - - Ok(Json(uploaded_objects).into_response()) -} diff --git a/backend/src/db/queries.rs b/backend/src/db/queries.rs index 9bc2763..b50759e 100644 --- a/backend/src/db/queries.rs +++ b/backend/src/db/queries.rs @@ -1,75 +1,263 @@ -pub const DELETE_OBJECTS_QUERY: &str = "DELETE FROM objects WHERE id = ANY($1) RETURNING id, name, made_on, ST_Y(location::geometry) AS latitude, ST_X(location::geometry) AS longitude, user_id, publicity;"; +/// The `S3Object` column projection shared by the mutation queries that RETURN an +/// object in a transitional state (and so cannot use the +/// `available_objects_with_users` view). Single-sourced because every column must +/// match `S3Object::try_from`'s by-name reads (especially the `latitude`/ +/// `longitude` aliases); a drifted copy would silently break decoding. Expands to +/// a string literal so it composes into the query consts via `concat!`. +macro_rules! object_returning_columns { + () => { + "id, name, storage_key, content_type, made_on, ST_Y(location::geometry) AS latitude, ST_X(location::geometry) AS longitude, user_id, publicity" + }; +} + +/// The `ObjectUploadSession` column projection, single-sourced because four +/// queries (insert RETURNING, the two per-user SELECTs, and the expired-session +/// claim RETURNING) must all match `ObjectUploadSession::try_from`'s by-name +/// reads. Only the seven columns that `try_from` decodes are listed; the +/// `cleanup_*`/`created_at` bookkeeping columns are read in WHERE/SET clauses but +/// never projected into the struct. `$prefix` disambiguates `storage_key` (and +/// the rest) in the JOIN queries, where the session table is aliased `session.`; +/// the insert RETURNING passes an empty prefix. Expands to a string literal so it +/// composes into the query consts via `concat!`. +macro_rules! upload_session_columns { + ($prefix:literal) => { + concat!( + $prefix, + "object_id, ", + $prefix, + "storage_key, ", + $prefix, + "upload_id, ", + $prefix, + "content_type, ", + $prefix, + "file_size, ", + $prefix, + "part_size_bytes, ", + $prefix, + "expires_at" + ) + }; +} + +pub const MARK_OBJECTS_DELETE_PENDING_QUERY: &str = concat!( + "UPDATE objects +SET storage_state = 'delete_pending', storage_state_updated_at = now() +WHERE id = ANY($1) AND storage_state = 'available' +RETURNING ", + object_returning_columns!(), + ";" +); + +pub const INSERT_OBJECT_QUERY: &str = concat!( + "INSERT INTO objects (name, storage_key, content_type, storage_state, made_on, location, user_id, publicity) +VALUES ($1, $2, $3, 'pending_upload', $4::timestamptz, ST_GeomFromEWKT($5), $6, $7) +RETURNING ", + object_returning_columns!(), + ";" +); + +pub const INSERT_OBJECT_UPLOAD_SESSION_QUERY: &str = concat!( + "INSERT INTO object_upload_sessions ( + object_id, + storage_key, + upload_id, + content_type, + file_size, + part_size_bytes, + expires_at, + cleanup_next_attempt_at +) +VALUES ($1, $2, $3, $4, $5, $6, now() + ($7::BIGINT * interval '1 second'), now() + ($7::BIGINT * interval '1 second')) +RETURNING ", + upload_session_columns!("") +); + +pub const SELECT_ACTIVE_OBJECT_UPLOAD_SESSION_FOR_USER_QUERY: &str = concat!( + "SELECT ", + upload_session_columns!("session."), + " +FROM object_upload_sessions session +JOIN objects object ON object.id = session.object_id +WHERE session.object_id = $1 + AND object.user_id = $2 + AND object.storage_state = 'pending_upload' + AND session.expires_at > now()" +); + +pub const SELECT_OBJECT_UPLOAD_SESSION_FOR_USER_QUERY: &str = concat!( + "SELECT ", + upload_session_columns!("session."), + " +FROM object_upload_sessions session +JOIN objects object ON object.id = session.object_id +WHERE session.object_id = $1 + AND object.user_id = $2 + AND object.storage_state = 'pending_upload'" +); + +/// Claims up to `$1` expired upload sessions whose retry/lease time has arrived +/// and which still have retry budget left. Rows past `$3::INTEGER` attempts are +/// parked with their last error for operator triage. +pub const CLAIM_EXPIRED_OBJECT_UPLOAD_SESSIONS_QUERY: &str = concat!( + "WITH claimed AS MATERIALIZED ( + SELECT session.object_id + FROM object_upload_sessions session + JOIN objects object ON object.id = session.object_id + WHERE session.expires_at <= now() + AND session.cleanup_next_attempt_at <= now() + AND session.cleanup_attempts < $3::INTEGER + AND object.storage_state = 'pending_upload' + ORDER BY session.expires_at, session.cleanup_next_attempt_at, session.created_at + LIMIT $1 + FOR UPDATE OF session SKIP LOCKED +) +UPDATE object_upload_sessions session +SET cleanup_attempts = cleanup_attempts + 1, + cleanup_last_attempt_at = now(), + cleanup_next_attempt_at = now() + ($2::BIGINT * interval '1 second'), + cleanup_last_error = NULL +FROM claimed +WHERE session.object_id = claimed.object_id +RETURNING ", + upload_session_columns!("session.") +); + +pub const DELETE_OBJECT_UPLOAD_SESSION_QUERY: &str = + "DELETE FROM object_upload_sessions WHERE object_id = $1"; + +pub const FINALIZE_OBJECT_UPLOAD_QUERY: &str = "WITH finalized AS ( + UPDATE objects + SET storage_state = 'available', storage_state_updated_at = now() + WHERE id = $1 AND storage_key = $2 AND storage_state = 'pending_upload' + RETURNING id, name, storage_key, content_type, made_on, location, user_id, publicity +) +SELECT + finalized.id, + finalized.name, + finalized.storage_key, + finalized.content_type, + finalized.made_on, + ST_Y(finalized.location::geometry) AS latitude, + ST_X(finalized.location::geometry) AS longitude, + finalized.user_id, + finalized.publicity, + COALESCE(( + SELECT array_agg(users.email) + FROM object_allowed_users allowed + JOIN users ON allowed.user_id = users.id + WHERE allowed.object_id = finalized.id + ), '{}') AS allowed_users +FROM finalized;"; + +pub const SELECT_AVAILABLE_OBJECT_FOR_USER_QUERY: &str = + "SELECT * FROM available_objects_with_users WHERE id = $1 AND user_id = $2;"; + +pub const DELETE_PENDING_OBJECT_UPLOAD_QUERY: &str = "DELETE FROM objects +WHERE id = $1 + AND storage_key = $2 + AND user_id = $3 + AND storage_state = 'pending_upload'"; + +pub const DELETE_PENDING_OBJECT_UPLOAD_BY_SESSION_QUERY: &str = "DELETE FROM objects +WHERE id = $1 + AND storage_key = $2 + AND storage_state = 'pending_upload'"; + +pub const MARK_OBJECT_UPLOAD_SESSION_CLEANUP_FAILED_QUERY: &str = "UPDATE object_upload_sessions +SET cleanup_next_attempt_at = now() + ($3::BIGINT * interval '1 second'), + cleanup_last_error = $2 +WHERE object_id = $1"; + +/// Counts upload-session cleanups that have exhausted their retry budget +/// (`cleanup_attempts >= $1`) and so will never be reclaimed by the cleanup +/// claim. Used to surface a parked backlog to operators. +pub const COUNT_PARKED_OBJECT_UPLOAD_SESSIONS_QUERY: &str = + "SELECT COUNT(*) FROM object_upload_sessions WHERE cleanup_attempts >= $1::INTEGER"; + +pub const MARK_UPLOAD_DELETE_PENDING_QUERY: &str = concat!( + "UPDATE objects +SET storage_state = 'delete_pending', storage_state_updated_at = now() +WHERE id = $1 AND storage_key = $2 AND storage_state IN ('pending_upload', 'available') +RETURNING ", + object_returning_columns!(), + ";" +); + +pub const MARK_STALE_UPLOADS_DELETE_PENDING_QUERY: &str = concat!( + "UPDATE objects +SET storage_state = 'delete_pending', storage_state_updated_at = now() +WHERE storage_state = 'pending_upload' + AND storage_state_updated_at < now() - ($1::BIGINT * interval '1 second') + AND NOT EXISTS ( + SELECT 1 FROM object_upload_sessions session + WHERE session.object_id = objects.id + ) +RETURNING ", + object_returning_columns!(), + ";" +); /// Query to update an existing object in the database. /// It updates the name, made_on timestamp, and location based on the provided ID. -pub const UPDATE_OBJECT_QUERY: &str = "UPDATE objects +pub const UPDATE_OBJECT_QUERY: &str = concat!( + "UPDATE objects SET name = $2, made_on = $3::timestamptz, location = ST_GeomFromEWKT($4), publicity = $5 -WHERE id = $1 -RETURNING id, name, made_on, ST_Y(location::geometry) AS latitude, ST_X(location::geometry) AS longitude, user_id, publicity;"; - -pub const UPSERT_OBJECT_QUERY: &str = "INSERT INTO objects (name, made_on, location, user_id, publicity) -VALUES ($1, $2::timestamptz, ST_GeomFromEWKT($3), $4, $5) -ON CONFLICT (name) DO UPDATE -SET made_on = EXCLUDED.made_on, location = EXCLUDED.location, publicity = EXCLUDED.publicity -RETURNING id, name, made_on, ST_Y(location::geometry) AS latitude, ST_X(location::geometry) AS longitude, user_id, publicity;"; - -pub const SELECT_ALL_OBJECTS_QUERY: &str = "SELECT o.id, o.name, o.made_on, ST_Y(o.location::geometry) AS latitude, ST_X(o.location::geometry) AS longitude, o.user_id, o.publicity, -COALESCE(array_agg(u.email) FILTER (WHERE u.email IS NOT NULL), '{}') AS allowed_users -FROM objects o -LEFT JOIN object_allowed_users oau ON o.id = oau.object_id -LEFT JOIN users u ON oau.user_id = u.id -GROUP BY o.id;"; - -pub const SELECT_OBJECT_BY_ID_QUERY: &str = "SELECT o.id, o.name, o.made_on, ST_Y(o.location::geometry) AS latitude, ST_X(o.location::geometry) AS longitude, o.user_id, o.publicity, -COALESCE(array_agg(u.email) FILTER (WHERE u.email IS NOT NULL), '{}') AS allowed_users -FROM objects o -LEFT JOIN object_allowed_users oau ON o.id = oau.object_id -LEFT JOIN users u ON oau.user_id = u.id -WHERE o.id = $1 -GROUP BY o.id;"; - -pub const SELECT_OBJECT_BY_NAME_QUERY: &str = "SELECT o.id, o.name, o.made_on, ST_Y(o.location::geometry) AS latitude, ST_X(o.location::geometry) AS longitude, o.user_id, o.publicity, -COALESCE(array_agg(u.email) FILTER (WHERE u.email IS NOT NULL), '{}') AS allowed_users -FROM objects o -LEFT JOIN object_allowed_users oau ON o.id = oau.object_id -LEFT JOIN users u ON oau.user_id = u.id -WHERE o.name = $1 -GROUP BY o.id;"; - -pub const SELECT_OBJECTS_BY_IDS_QUERY: &str = "SELECT o.id, o.name, o.made_on, ST_Y(o.location::geometry) AS latitude, ST_X(o.location::geometry) AS longitude, o.user_id, o.publicity, -COALESCE(array_agg(u.email) FILTER (WHERE u.email IS NOT NULL), '{}') AS allowed_users -FROM objects o -LEFT JOIN object_allowed_users oau ON o.id = oau.object_id -LEFT JOIN users u ON oau.user_id = u.id -WHERE o.id = ANY($1) -GROUP BY o.id;"; - -pub const SELECT_OBJECTS_BY_USER_ID_QUERY: &str = "SELECT o.id, o.name, o.made_on, ST_Y(o.location::geometry) AS latitude, ST_X(o.location::geometry) AS longitude, o.user_id, o.publicity, -COALESCE(array_agg(u.email) FILTER (WHERE u.email IS NOT NULL), '{}') AS allowed_users -FROM objects o -LEFT JOIN object_allowed_users oau ON o.id = oau.object_id -LEFT JOIN users u ON oau.user_id = u.id -WHERE o.user_id = $1 -GROUP BY o.id;"; - -pub const SELECT_VISIBLE_OBJECTS_QUERY: &str = "SELECT o.id, o.name, o.made_on, ST_Y(o.location::geometry) AS latitude, ST_X(o.location::geometry) AS longitude, o.user_id, o.publicity, -COALESCE(array_agg(u_allowed.email) FILTER (WHERE u_allowed.email IS NOT NULL), '{}') AS allowed_users -FROM objects o -JOIN users u ON o.user_id = u.id -LEFT JOIN object_allowed_users oau ON o.id = oau.object_id -LEFT JOIN users u_allowed ON oau.user_id = u_allowed.id +WHERE id = $1 AND storage_state = 'available' +RETURNING ", + object_returning_columns!(), + ";" +); + +pub const SELECT_ALL_OBJECTS_QUERY: &str = "SELECT * FROM available_objects_with_users;"; + +pub const SELECT_OBJECT_BY_ID_QUERY: &str = + "SELECT * FROM available_objects_with_users WHERE id = $1;"; + +pub const SELECT_OBJECT_BY_NAME_QUERY: &str = + "SELECT * FROM available_objects_with_users WHERE name = $1;"; + +pub const SELECT_OBJECTS_BY_IDS_QUERY: &str = + "SELECT * FROM available_objects_with_users WHERE id = ANY($1);"; + +pub const SELECT_OBJECTS_BY_USER_ID_QUERY: &str = + "SELECT * FROM available_objects_with_users WHERE user_id = $1;"; + +pub const SELECT_VISIBLE_OBJECTS_QUERY: &str = "SELECT v.* +FROM available_objects_with_users v +JOIN users owner ON owner.id = v.user_id WHERE - ($1::BIGINT IS NOT NULL AND o.user_id = $1) - OR o.publicity = 'public' - OR (o.publicity = 'default' AND u.default_publicity = 'public') - OR (o.publicity = 'selected_users' AND $1::BIGINT IS NOT NULL AND $1 IN (SELECT user_id FROM object_allowed_users WHERE object_id = o.id)) -GROUP BY o.id;"; + ($1::BIGINT IS NOT NULL AND v.user_id = $1) + OR v.publicity = 'public' + OR (v.publicity = 'default' AND owner.default_publicity = 'public') + OR ( + v.publicity = 'selected_users' + AND $1::BIGINT IS NOT NULL + AND EXISTS ( + SELECT 1 + FROM object_allowed_users allowed + WHERE allowed.object_id = v.id + AND allowed.user_id = $1 + ) + );"; pub const DELETE_OBJECT_ALLOWED_USERS_QUERY: &str = "DELETE FROM object_allowed_users WHERE object_id = $1"; -pub const INSERT_OBJECT_ALLOWED_USER_QUERY: &str = - "INSERT INTO object_allowed_users (object_id, user_id) VALUES ($1, $2)"; +pub const REPLACE_OBJECT_ALLOWED_USERS_QUERY: &str = "WITH valid AS ( + SELECT id, email + FROM users + WHERE email = ANY($2) +), +inserted AS ( + INSERT INTO object_allowed_users (object_id, user_id) + SELECT $1, id + FROM valid + RETURNING user_id +) +SELECT valid.email +FROM valid +JOIN inserted ON inserted.user_id = valid.id"; pub const SELECT_ALL_USERS_QUERY: &str = "SELECT id, email, role, created_at, updated_at, default_publicity FROM users"; @@ -80,9 +268,52 @@ pub const SELECT_USER_BY_ID_QUERY: &str = pub const SELECT_USER_BY_EMAIL_QUERY: &str = "SELECT id, email, role, created_at, updated_at, default_publicity FROM users WHERE email = $1"; -pub const SELECT_USER_EXISTS_QUERY: &str = "SELECT 1 FROM users WHERE id = $1"; +pub const SELECT_USER_ID_BY_EMAIL_FOR_UPDATE_QUERY: &str = + "SELECT id FROM users WHERE email = $1 FOR UPDATE"; + +pub const INSERT_OBJECT_STORAGE_DELETIONS_QUERY: &str = + "INSERT INTO object_storage_deletions (storage_key, object_id) +SELECT UNNEST($1::TEXT[]), UNNEST($2::BIGINT[]) +ON CONFLICT (storage_key) DO NOTHING"; + +/// Claims up to `$1` deletion rows whose scheduled retry/lease time has arrived +/// and which still have retry budget left. Rows past `$3::INTEGER` attempts are parked: they remain +/// in the table with `last_error` populated for operator triage, but are never +/// reclaimed by the worker. +pub const CLAIM_OBJECT_STORAGE_DELETIONS_QUERY: &str = "WITH claimed AS MATERIALIZED ( + SELECT storage_key + FROM object_storage_deletions + WHERE attempts < $3::INTEGER + AND next_attempt_at <= now() + ORDER BY next_attempt_at, created_at + LIMIT $1 + FOR UPDATE SKIP LOCKED +) +UPDATE object_storage_deletions deletion +SET attempts = attempts + 1, + last_attempt_at = now(), + next_attempt_at = now() + ($2::BIGINT * interval '1 second'), + last_error = NULL +FROM claimed +WHERE deletion.storage_key = claimed.storage_key +RETURNING deletion.storage_key"; -pub const SELECT_USERS_BY_EMAILS_QUERY: &str = "SELECT id, email FROM users WHERE email = ANY($1)"; +pub const DELETE_OBJECT_STORAGE_DELETIONS_QUERY: &str = + "DELETE FROM object_storage_deletions WHERE storage_key = ANY($1)"; + +pub const DELETE_OBJECTS_BY_STORAGE_KEYS_QUERY: &str = + "DELETE FROM objects WHERE storage_key = ANY($1) AND storage_state = 'delete_pending'"; + +pub const MARK_OBJECT_STORAGE_DELETIONS_FAILED_QUERY: &str = "UPDATE object_storage_deletions +SET next_attempt_at = now() + ($3::BIGINT * interval '1 second'), + last_error = $2 +WHERE storage_key = ANY($1)"; + +/// Counts storage deletions that have exhausted their retry budget +/// (`attempts >= $1`) and so will never be reclaimed by the deletion claim. Used +/// to surface a parked backlog to operators. +pub const COUNT_PARKED_OBJECT_STORAGE_DELETIONS_QUERY: &str = + "SELECT COUNT(*) FROM object_storage_deletions WHERE attempts >= $1::INTEGER"; pub const SELECT_USER_COUNT_BY_EMAIL_QUERY: &str = "SELECT COUNT(*) FROM users WHERE email = $1"; @@ -110,5 +341,50 @@ pub const INSERT_PASSWORD_RESET_TOKEN_QUERY: &str = "INSERT INTO password_reset_ pub const SELECT_PASSWORD_RESET_TOKEN_QUERY: &str = "SELECT user_id FROM password_reset_tokens WHERE token = $1 AND expires_at > now()"; -pub const DELETE_PASSWORD_RESET_TOKEN_QUERY: &str = - "DELETE FROM password_reset_tokens WHERE token = $1"; +/// Returns whether the user has any unconsumed token issued within the rate-limit window. +/// +/// Used by `request_password_reset` to throttle issuance per user; the window is bound +/// by the `$2::BIGINT` seconds parameter so callers control the policy. +pub const RECENT_PASSWORD_RESET_TOKEN_EXISTS_QUERY: &str = "SELECT EXISTS ( + SELECT 1 FROM password_reset_tokens + WHERE user_id = $1 + AND created_at > now() - ($2::BIGINT * interval '1 second') + )"; + +/// Invalidates all unconsumed reset tokens for a user. +/// +/// Used at issuance time (replace siblings with the new token) and at consumption time +/// (after a successful reset, kill any other outstanding tokens so the user has none). +pub const DELETE_PASSWORD_RESET_TOKENS_BY_USER_QUERY: &str = + "DELETE FROM password_reset_tokens WHERE user_id = $1"; + +pub const INSERT_EMAIL_OUTBOX_QUERY: &str = + "INSERT INTO email_outbox (kind, payload) VALUES ($1, $2::TEXT::jsonb)"; + +/// Claims up to `$1` email rows whose scheduled retry/lease time has arrived +/// and which still have retry budget left. Rows past `$3::INTEGER` attempts are +/// parked with their last error for operator triage. +pub const CLAIM_EMAIL_OUTBOX_QUERY: &str = "WITH claimed AS MATERIALIZED ( + SELECT id + FROM email_outbox + WHERE attempts < $3::INTEGER + AND next_attempt_at <= now() + ORDER BY next_attempt_at, created_at + LIMIT $1 + FOR UPDATE SKIP LOCKED +) +UPDATE email_outbox outbox +SET attempts = attempts + 1, + last_attempt_at = now(), + next_attempt_at = now() + ($2::BIGINT * interval '1 second'), + last_error = NULL +FROM claimed +WHERE outbox.id = claimed.id +RETURNING outbox.id, outbox.kind, outbox.payload::TEXT AS payload"; + +pub const DELETE_EMAIL_OUTBOX_QUERY: &str = "DELETE FROM email_outbox WHERE id = ANY($1)"; + +pub const MARK_EMAIL_OUTBOX_FAILED_QUERY: &str = "UPDATE email_outbox +SET next_attempt_at = now() + ($3::BIGINT * interval '1 second'), + last_error = $2 +WHERE id = ANY($1)"; diff --git a/backend/src/email.rs b/backend/src/email.rs index dca4098..249f292 100644 --- a/backend/src/email.rs +++ b/backend/src/email.rs @@ -16,19 +16,19 @@ pub async fn send_password_reset_email( token: &str, ) -> anyhow::Result<()> { let email = Message::builder() - .from(config.smtp_from.parse()?) + .from(config.smtp.from.parse()?) .to(to_email.parse()?) .subject("Password Reset Request") .header(ContentType::TEXT_PLAIN) .body(format!( "Click the link below to reset your password:\n\n{}/reset-password?token={}\n\nThis link expires in 10 minutes.", - config.frontend_url, token + config.frontend.url, token ))?; - let creds = Credentials::new(config.smtp_user.clone(), config.smtp_pass.clone()); + let creds = Credentials::new(config.smtp.user.clone(), config.smtp.pass.clone()); let mailer = - AsyncSmtpTransport::::relay(&config.smtp_host)?.credentials(creds).build(); + AsyncSmtpTransport::::relay(&config.smtp.host)?.credentials(creds).build(); mailer.send(email).await?; diff --git a/backend/src/email_worker.rs b/backend/src/email_worker.rs new file mode 100644 index 0000000..b1f0a62 --- /dev/null +++ b/backend/src/email_worker.rs @@ -0,0 +1,434 @@ +use { + crate::{ + Config, + db::queries::{ + CLAIM_EMAIL_OUTBOX_QUERY, + DELETE_EMAIL_OUTBOX_QUERY, + INSERT_EMAIL_OUTBOX_QUERY, + MARK_EMAIL_OUTBOX_FAILED_QUERY, + }, + email::send_password_reset_email, + errors::AppError, + outbox::{ + DrainOutcome, + FailedGroup, + OutboxProcessor, + OutboxQueue, + OutboxRetryConfig, + drain_outbox, + ensure_positive, + }, + worker::MaintenanceTask, + }, + anyhow::Context, + deadpool::managed::Pool, + deadpool_postgres::{ + Client, + Manager, + }, + serde::{ + Deserialize, + Serialize, + }, + std::{ + future::Future, + time::Duration, + }, + tokio_postgres::{ + Row, + Transaction, + }, +}; + +pub const PASSWORD_RESET_EMAIL_KIND: &str = "password_reset"; + +#[derive(Clone, Debug, Deserialize)] +pub struct EmailOutboxConfig { + #[serde(default = "EmailOutboxConfig::default_retry_seconds")] + pub retry_seconds: i64, + #[serde(default = "EmailOutboxConfig::default_lease_seconds")] + pub lease_seconds: i64, + #[serde(default = "EmailOutboxConfig::default_worker_interval_seconds")] + pub worker_interval_seconds: i64, + #[serde(default = "EmailOutboxConfig::default_batch_size")] + pub batch_size: i64, + #[serde(default = "EmailOutboxConfig::default_max_attempts")] + pub max_attempts: i32, +} + +impl EmailOutboxConfig { + pub const fn default_retry_seconds() -> i64 { + 60 + } + + pub const fn default_lease_seconds() -> i64 { + 300 + } + + pub const fn default_worker_interval_seconds() -> i64 { + 30 + } + + pub const fn default_batch_size() -> i64 { + 100 + } + + pub const fn default_max_attempts() -> i32 { + 10 + } + + pub fn validate(&self) -> anyhow::Result<()> { + self.retry().validate("email_outbox")?; + ensure_positive!(self, worker_interval_seconds); + Ok(()) + } + + /// Lease/retry policy for the email outbox, as a runtime view. + pub fn retry(&self) -> OutboxRetryConfig { + OutboxRetryConfig { + retry_seconds: self.retry_seconds, + lease_seconds: self.lease_seconds, + batch_size: self.batch_size, + max_attempts: self.max_attempts, + } + } + + fn worker_interval(&self) -> Duration { + Duration::from_secs(self.worker_interval_seconds as u64) + } +} + +impl Default for EmailOutboxConfig { + fn default() -> Self { + Self { + retry_seconds: Self::default_retry_seconds(), + lease_seconds: Self::default_lease_seconds(), + worker_interval_seconds: Self::default_worker_interval_seconds(), + batch_size: Self::default_batch_size(), + max_attempts: Self::default_max_attempts(), + } + } +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct PasswordResetEmailPayload { + pub email: String, + pub token: String, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct EmailOutboxMessage { + pub id: i64, + pub kind: String, + pub payload: String, +} + +impl TryFrom for EmailOutboxMessage { + type Error = AppError; + + fn try_from(row: Row) -> Result { + Ok(Self { + id: row.try_get("id").context("Failed to read email outbox id")?, + kind: row.try_get("kind").context("Failed to read email outbox kind")?, + payload: row.try_get("payload").context("Failed to read email outbox payload")?, + }) + } +} + +/// Enqueues a password-reset email for asynchronous delivery by the outbox +/// worker. +/// +/// The payload deliberately carries the plaintext reset `token`: an outbox must +/// hold everything the deferred side effect needs, and the email has to contain +/// the usable token. This is a conscious trade-off, not an oversight. The +/// exposure is small and bounded: the token of record is stored only as a hash +/// in `password_reset_tokens`, the token expires in ten minutes, and this row is +/// deleted as soon as the worker sends the email. Anyone able to read +/// `email_outbox` already has the database access that defeats the auth system. +/// If the threat model later admits an untrusted reader of the outbox (a read +/// replica or a log pipeline), encrypt this payload at rest before considering +/// the more invasive option of minting the token in the worker, which would +/// spread the rate-limit and single-active-token invariant across request and +/// worker paths. +pub async fn enqueue_password_reset_email( + transaction: &Transaction<'_>, + email: &str, + token: &str, +) -> Result<(), AppError> { + let payload = serde_json::to_string(&PasswordResetEmailPayload { + email: email.to_string(), + token: token.to_string(), + }) + .context("Failed to serialize password reset email payload")?; + transaction + .execute(INSERT_EMAIL_OUTBOX_QUERY, &[&PASSWORD_RESET_EMAIL_KIND, &payload]) + .await + .context("Failed to enqueue password reset email")?; + Ok(()) +} + +#[derive(Clone)] +pub struct EmailWorker { + pool: Pool, + sender: SmtpEmailSender, + config: EmailOutboxConfig, +} + +impl EmailWorker { + pub fn new( + pool: Pool, + config: Config, + ) -> Self { + Self { + pool, + config: config.email_outbox.clone(), + sender: SmtpEmailSender { + config, + }, + } + } +} + +impl MaintenanceTask for EmailWorker { + fn name(&self) -> &'static str { + "email_outbox" + } + + fn interval(&self) -> Duration { + self.config.worker_interval() + } + + async fn run_once(&self) -> Result<(), AppError> { + let mut client = self.pool.get().await?; + let processor = EmailOutboxProcessor { + sender: &self.sender, + }; + let mut queue = EmailOutboxQueue(&mut client); + drain_outbox(&mut queue, &processor, &self.config.retry()).await + } +} + +#[derive(Clone)] +struct SmtpEmailSender { + config: Config, +} + +/// Sends one email. `send_password_reset` carries an explicit `Send` bound on its +/// future so the generic `EmailOutboxProcessor` can compose it into the +/// `Send`-bounded `OutboxProcessor::process` future (`drain_outbox` runs in a +/// spawned worker task). +trait EmailSender { + fn send_password_reset( + &self, + payload: &PasswordResetEmailPayload, + ) -> impl Future> + Send; +} + +impl EmailSender for SmtpEmailSender { + async fn send_password_reset( + &self, + payload: &PasswordResetEmailPayload, + ) -> anyhow::Result<()> { + send_password_reset_email(&self.config, &payload.email, &payload.token).await + } +} + +/// The email outbox as an [`OutboxQueue`]. `clear`/`mark_failed` operate on the +/// message ids; the item carries the kind and payload the processor needs. +struct EmailOutboxQueue<'a>(&'a mut Client); + +impl OutboxQueue for EmailOutboxQueue<'_> { + type Item = EmailOutboxMessage; + + async fn claim( + &mut self, + retry: &OutboxRetryConfig, + ) -> Result, AppError> { + let rows = self + .0 + .query( + CLAIM_EMAIL_OUTBOX_QUERY, + &[&retry.batch_size, &retry.lease_seconds, &retry.max_attempts], + ) + .await + .context("Failed to claim email outbox rows")?; + rows.into_iter().map(EmailOutboxMessage::try_from).collect() + } + + async fn clear( + &mut self, + messages: &[EmailOutboxMessage], + ) -> Result<(), AppError> { + if messages.is_empty() { + return Ok(()); + } + let ids = messages.iter().map(|message| message.id).collect::>(); + self.0 + .execute(DELETE_EMAIL_OUTBOX_QUERY, &[&ids]) + .await + .context("Failed to clear delivered email outbox rows")?; + Ok(()) + } + + async fn mark_failed( + &mut self, + messages: &[EmailOutboxMessage], + error_message: &str, + retry_after_seconds: i64, + ) -> Result<(), AppError> { + if messages.is_empty() { + return Ok(()); + } + let ids = messages.iter().map(|message| message.id).collect::>(); + self.0 + .execute(MARK_EMAIL_OUTBOX_FAILED_QUERY, &[&ids, &error_message, &retry_after_seconds]) + .await + .context("Failed to record email outbox delivery failure")?; + Ok(()) + } +} + +/// Sends each claimed message independently, so one failed send does not fail the +/// rest of the batch: successes clear, and each failure becomes its own +/// `FailedGroup` to be marked for retry. +struct EmailOutboxProcessor<'a, S> { + sender: &'a S, +} + +impl OutboxProcessor for EmailOutboxProcessor<'_, S> { + type Item = EmailOutboxMessage; + + async fn process( + &self, + messages: Vec, + ) -> DrainOutcome { + let mut cleared = Vec::new(); + let mut failed = Vec::new(); + for message in messages { + match send_email_message(self.sender, &message).await { + Ok(()) => cleared.push(message), + Err(error) => failed.push(FailedGroup { + items: vec![message], + error, + }), + } + } + DrainOutcome { + cleared, + failed, + } + } +} + +async fn send_email_message( + sender: &impl EmailSender, + message: &EmailOutboxMessage, +) -> anyhow::Result<()> { + match message.kind.as_str() { + PASSWORD_RESET_EMAIL_KIND => { + let payload = serde_json::from_str::(&message.payload) + .context("Failed to deserialize password reset email payload")?; + sender.send_password_reset(&payload).await + } + kind => anyhow::bail!("Unsupported email outbox kind: {kind}"), + } +} + +#[cfg(test)] +mod tests { + use { + super::{ + EmailOutboxMessage, + EmailOutboxProcessor, + EmailSender, + PASSWORD_RESET_EMAIL_KIND, + PasswordResetEmailPayload, + }, + crate::outbox::OutboxProcessor, + std::sync::Mutex, + }; + + #[derive(Default)] + struct FakeEmailSender { + sent: Mutex>, + fail: bool, + } + + impl FakeEmailSender { + fn sent(&self) -> anyhow::Result> { + self.sent + .lock() + .map(|sent| sent.clone()) + .map_err(|_| anyhow::anyhow!("sent mutex poisoned")) + } + } + + impl EmailSender for FakeEmailSender { + async fn send_password_reset( + &self, + payload: &PasswordResetEmailPayload, + ) -> anyhow::Result<()> { + if self.fail { + anyhow::bail!("smtp failed"); + } + self.sent + .lock() + .map_err(|_| anyhow::anyhow!("sent mutex poisoned"))? + .push(payload.clone()); + Ok(()) + } + } + + fn password_reset_message(id: i64) -> anyhow::Result { + Ok(EmailOutboxMessage { + id, + kind: PASSWORD_RESET_EMAIL_KIND.to_string(), + payload: serde_json::to_string(&PasswordResetEmailPayload { + email: "person@example.test".to_string(), + token: "reset-token".to_string(), + })?, + }) + } + + #[tokio::test] + async fn email_processor_sends_and_clears_password_reset_rows() -> anyhow::Result<()> { + let sender = FakeEmailSender::default(); + let processor = EmailOutboxProcessor { + sender: &sender, + }; + + let outcome = processor.process(vec![password_reset_message(42)?]).await; + + assert_eq!(outcome.cleared.iter().map(|message| message.id).collect::>(), vec![42]); + assert!(outcome.failed.is_empty()); + assert_eq!( + sender.sent()?, + vec![PasswordResetEmailPayload { + email: "person@example.test".to_string(), + token: "reset-token".to_string(), + }] + ); + Ok(()) + } + + #[tokio::test] + async fn email_processor_isolates_send_failures_per_message() -> anyhow::Result<()> { + let sender = FakeEmailSender { + fail: true, + ..FakeEmailSender::default() + }; + let processor = EmailOutboxProcessor { + sender: &sender, + }; + + let outcome = processor.process(vec![password_reset_message(42)?]).await; + + assert!(outcome.cleared.is_empty()); + let [group] = outcome.failed.as_slice() else { + anyhow::bail!("expected exactly one failed group"); + }; + assert_eq!(group.items.iter().map(|message| message.id).collect::>(), vec![42]); + assert!(group.error.to_string().contains("smtp failed")); + Ok(()) + } +} diff --git a/backend/src/errors.rs b/backend/src/errors.rs index f9b2bea..2a90ffb 100644 --- a/backend/src/errors.rs +++ b/backend/src/errors.rs @@ -1,25 +1,5 @@ use { - crate::constants::{ - ERR_CASBIN, - ERR_CONFIG, - ERR_CREATE_POOL, - ERR_DB, - ERR_EMAIL, - ERR_EMAIL_ADDRESS, - ERR_FORBIDDEN, - ERR_HASHING, - ERR_INTERNAL_SERVER, - ERR_INVALID_NUMBER, - ERR_IO, - ERR_MIGRATION, - ERR_MULTIPART, - ERR_NOT_FOUND, - ERR_POOL, - ERR_SMTP, - ERR_SYSTEM_TIME, - ERR_UNAUTHORIZED, - ERR_VALIDATION, - }, + async_graphql::ErrorExtensions, axum::{ http::StatusCode, response::{ @@ -27,72 +7,116 @@ use { Response, }, }, - std::fmt, }; -#[derive(Debug)] +/// Application-level error categories that all backend operations funnel into. +/// +/// Source-specific errors (database, S3, hashing, SMTP, config, casbin, etc.) +/// all become `AppError::Internal` via `anyhow::Error`. Variants exist only for +/// categories that the client needs to distinguish in the response. Site-specific +/// detail belongs in `.context("Failed to ...")` chains at call sites; it is no +/// longer baked into per-source-type prefix constants. +#[derive(Debug, thiserror::Error)] pub enum AppError { - Internal(anyhow::Error), + #[error("Internal server error: {0}")] + Internal(#[from] anyhow::Error), + #[error("Unauthorized")] Unauthorized, + #[error("Forbidden")] Forbidden, + #[error("Not found: {0}")] NotFound(String), + #[error("Validation error: {0}")] Validation(String), } -impl fmt::Display for AppError { - fn fmt( - &self, - f: &mut fmt::Formatter<'_>, - ) -> fmt::Result { - match self { - AppError::Internal(e) => write!(f, "{}: {}", ERR_INTERNAL_SERVER, e), - AppError::Unauthorized => write!(f, "{}", ERR_UNAUTHORIZED), - AppError::Forbidden => write!(f, "{}", ERR_FORBIDDEN), - AppError::NotFound(msg) => write!(f, "{}{}", ERR_NOT_FOUND, msg), - AppError::Validation(msg) => write!(f, "{}{}", ERR_VALIDATION, msg), - } - } +/// Stable, client-facing error categories surfaced via `extensions.code` on +/// GraphQL errors and via HTTP status on the REST endpoint. Wire format +/// uses SCREAMING_SNAKE_CASE strings so existing API consumers can match +/// against literal codes without parsing message text. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ErrorCode { + Internal, + Unauthorized, + Forbidden, + NotFound, + Validation, } -impl std::error::Error for AppError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { +impl ErrorCode { + pub fn as_str(self) -> &'static str { match self { - AppError::Internal(e) => e.source(), - _ => None, + ErrorCode::Internal => "INTERNAL", + ErrorCode::Unauthorized => "UNAUTHORIZED", + ErrorCode::Forbidden => "FORBIDDEN", + ErrorCode::NotFound => "NOT_FOUND", + ErrorCode::Validation => "VALIDATION", } } -} -impl From for AppError { - fn from(err: anyhow::Error) -> Self { - AppError::Internal(err) + pub fn parse(value: &str) -> Option { + Some(match value { + "INTERNAL" => ErrorCode::Internal, + "UNAUTHORIZED" => ErrorCode::Unauthorized, + "FORBIDDEN" => ErrorCode::Forbidden, + "NOT_FOUND" => ErrorCode::NotFound, + "VALIDATION" => ErrorCode::Validation, + _ => return None, + }) } } impl AppError { pub fn status_code(&self) -> StatusCode { + match self.code() { + ErrorCode::Internal => StatusCode::INTERNAL_SERVER_ERROR, + ErrorCode::Unauthorized => StatusCode::UNAUTHORIZED, + ErrorCode::Forbidden => StatusCode::FORBIDDEN, + ErrorCode::NotFound => StatusCode::NOT_FOUND, + ErrorCode::Validation => StatusCode::BAD_REQUEST, + } + } + + /// Stable category code that mirrors the HTTP status / GraphQL extension. + pub fn code(&self) -> ErrorCode { match self { - AppError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR, - AppError::Unauthorized => StatusCode::UNAUTHORIZED, - AppError::Forbidden => StatusCode::FORBIDDEN, - AppError::NotFound(_) => StatusCode::NOT_FOUND, - AppError::Validation(_) => StatusCode::BAD_REQUEST, + AppError::Internal(_) => ErrorCode::Internal, + AppError::Unauthorized => ErrorCode::Unauthorized, + AppError::Forbidden => ErrorCode::Forbidden, + AppError::NotFound(_) => ErrorCode::NotFound, + AppError::Validation(_) => ErrorCode::Validation, } } + /// Message shown to clients. Internal errors are intentionally opaque to + /// avoid leaking internal details; the full source is in tracing logs. pub fn client_message(&self) -> String { match self { - AppError::Internal(_) => ERR_INTERNAL_SERVER.to_string(), - AppError::Unauthorized => ERR_UNAUTHORIZED.to_string(), - AppError::Forbidden => ERR_FORBIDDEN.to_string(), + AppError::Internal(_) => "Internal server error".to_string(), + AppError::Unauthorized => "Unauthorized".to_string(), + AppError::Forbidden => "Forbidden".to_string(), AppError::NotFound(msg) => msg.clone(), AppError::Validation(msg) => msg.clone(), } } + /// Wraps the error into an async-graphql Error, populating `extensions.code` + /// so clients can branch on the stable code instead of parsing the message. pub fn extend_graphql(self) -> async_graphql::Error { tracing::error!("GraphQL error: {:?}", self); - async_graphql::Error::new(self.to_string()) + let code = self.code().as_str(); + let message = self.client_message(); + async_graphql::Error::new(message).extend_with(|_, extensions| { + extensions.set("code", code); + }) + } + + /// Central conversion point for resolver code. + /// + /// Use this instead of `async_graphql::Error::from` so every `AppError` + /// reaches clients with the stable `extensions.code` field. + pub fn graphql(error: impl Into) -> async_graphql::Error { + error.into().extend_graphql() } } @@ -105,102 +129,67 @@ impl IntoResponse for AppError { impl From for AppError { fn from(err: async_graphql::Error) -> Self { - AppError::Internal(anyhow::anyhow!(err.message)) - } -} - -impl From> for AppError { - fn from(err: Box) -> Self { - AppError::Internal(anyhow::anyhow!(err)) - } -} - -impl From for AppError { - fn from(err: minio::s3::error::Error) -> Self { - AppError::Internal(anyhow::anyhow!(err)) + let code = err + .extensions + .as_ref() + .and_then(|extensions| extensions.get("code")) + .and_then(|value| match value { + async_graphql::Value::String(string) => Some(string.as_str()), + _ => None, + }) + .and_then(ErrorCode::parse); + + match code { + Some(ErrorCode::Unauthorized) => AppError::Unauthorized, + Some(ErrorCode::Forbidden) => AppError::Forbidden, + Some(ErrorCode::NotFound) => AppError::NotFound(err.message), + Some(ErrorCode::Validation) => AppError::Validation(err.message), + Some(ErrorCode::Internal) | None => AppError::Internal(anyhow::anyhow!(err.message)), + } } } +// Bare `?` is convenient at call sites. Each source-specific error type that +// the code hits routinely gets an explicit From impl that funnels into +// AppError::Internal via anyhow. Per-type message prefixes are dropped: +// site-specific context lives in `.context("Failed to ...")` chains. +macro_rules! impl_into_internal { + ($($ty:ty),* $(,)?) => { + $( + impl From<$ty> for AppError { + fn from(err: $ty) -> Self { + AppError::Internal(anyhow::Error::new(err)) + } + } + )* + }; +} + +impl_into_internal!( + casbin::Error, + config::ConfigError, + deadpool_postgres::CreatePoolError, + deadpool_postgres::PoolError, + lettre::address::AddressError, + lettre::error::Error, + lettre::transport::smtp::Error, + refinery::error::Error, + std::io::Error, + std::num::ParseIntError, + std::time::SystemTimeError, + tokio_postgres::Error, +); + +// argon2::password_hash::Error doesn't implement std::error::Error, so it goes +// through Display rather than the std::error::Error blanket conversion. impl From for AppError { fn from(err: argon2::password_hash::Error) -> Self { - AppError::Internal(anyhow::anyhow!("{}{}", ERR_HASHING, err)) - } -} - -impl From for AppError { - fn from(err: lettre::error::Error) -> Self { - AppError::Internal(anyhow::anyhow!("{}{}", ERR_EMAIL, err)) - } -} - -impl From for AppError { - fn from(err: lettre::address::AddressError) -> Self { - AppError::Internal(anyhow::anyhow!("{}{}", ERR_EMAIL_ADDRESS, err)) - } -} - -impl From for AppError { - fn from(err: lettre::transport::smtp::Error) -> Self { - AppError::Internal(anyhow::anyhow!("{}{}", ERR_SMTP, err)) - } -} - -impl From for AppError { - fn from(err: std::num::ParseIntError) -> Self { - AppError::Validation(format!("{}{}", ERR_INVALID_NUMBER, err)) - } -} - -impl From for AppError { - fn from(err: deadpool_postgres::CreatePoolError) -> Self { - AppError::Internal(anyhow::anyhow!("{}{}", ERR_CREATE_POOL, err)) - } -} - -impl From for AppError { - fn from(err: deadpool_postgres::PoolError) -> Self { - AppError::Internal(anyhow::anyhow!("{}{}", ERR_POOL, err)) - } -} - -impl From for AppError { - fn from(err: tokio_postgres::Error) -> Self { - AppError::Internal(anyhow::anyhow!("{}{}", ERR_DB, err)) - } -} - -impl From for AppError { - fn from(err: axum::extract::multipart::MultipartError) -> Self { - AppError::Validation(format!("{}{}", ERR_MULTIPART, err)) + AppError::Internal(anyhow::anyhow!("{err}")) } } -impl From for AppError { - fn from(err: casbin::Error) -> Self { - AppError::Internal(anyhow::anyhow!("{}{}", ERR_CASBIN, err)) - } -} - -impl From for AppError { - fn from(err: config::ConfigError) -> Self { - AppError::Internal(anyhow::anyhow!("{}{}", ERR_CONFIG, err)) - } -} - -impl From for AppError { - fn from(err: refinery::error::Error) -> Self { - AppError::Internal(anyhow::anyhow!("{}{}", ERR_MIGRATION, err)) - } -} - -impl From for AppError { - fn from(err: std::time::SystemTimeError) -> Self { - AppError::Internal(anyhow::anyhow!("{}{}", ERR_SYSTEM_TIME, err)) - } -} - -impl From for AppError { - fn from(err: std::io::Error) -> Self { - AppError::Internal(anyhow::anyhow!("{}{}", ERR_IO, err)) +impl From> for AppError { + fn from(err: Box) -> Self { + AppError::Internal(anyhow::anyhow!(err)) } } diff --git a/backend/src/graphql/objects.rs b/backend/src/graphql/objects.rs index c1633c4..947302f 100644 --- a/backend/src/graphql/objects.rs +++ b/backend/src/graphql/objects.rs @@ -6,6 +6,7 @@ use { pub mod config; pub mod location; pub mod s3_object; +pub mod upload_session; pub mod user; pub struct RowContext<'a>(pub Row, pub Context<'a>); diff --git a/backend/src/graphql/objects/location.rs b/backend/src/graphql/objects/location.rs index 5c7dc39..5727298 100644 --- a/backend/src/graphql/objects/location.rs +++ b/backend/src/graphql/objects/location.rs @@ -1,5 +1,6 @@ use { crate::{ + errors::AppError, parse_latitude, parse_longitude, }, @@ -8,7 +9,6 @@ use { SimpleObject, }, serde::Serialize, - tokio_postgres::Row, }; #[derive(SimpleObject, InputObject, Clone, Debug, Serialize)] @@ -18,13 +18,74 @@ pub struct Location { pub longitude: f64, } -impl TryFrom for Location { - type Error = Box; - - fn try_from(value: Row) -> Result { - Ok(Location { - latitude: parse_latitude(value.try_get("latitude")?)?, - longitude: parse_longitude(value.try_get("longitude")?)?, +impl Location { + pub fn validated(self) -> Result { + Ok(Self { + latitude: parse_latitude(self.latitude)?, + longitude: parse_longitude(self.longitude)?, }) } + + pub fn geometry(&self) -> Result { + let latitude = parse_latitude(self.latitude)?; + let longitude = parse_longitude(self.longitude)?; + Ok(format!("SRID=4326;POINT({longitude} {latitude})")) + } +} + +#[cfg(test)] +mod tests { + use { + super::Location, + crate::errors::AppError, + }; + + #[test] + fn validated_accepts_boundary_coordinates() { + let result = Location { + latitude: -90.0, + longitude: 180.0, + } + .validated(); + + assert!(result.is_ok(), "valid coordinates were rejected: {:?}", result.as_ref().err()); + if let Ok(location) = result { + assert_eq!(location.latitude, -90.0); + assert_eq!(location.longitude, 180.0); + } + } + + #[test] + fn validated_rejects_out_of_range_coordinates() { + let location = Location { + latitude: 90.1, + longitude: 0.0, + }; + + assert!(matches!(location.validated(), Err(AppError::Validation(_)))); + } + + #[test] + fn geometry_validates_before_formatting() { + let location = Location { + latitude: 0.0, + longitude: 180.1, + }; + + assert!(matches!(location.geometry(), Err(AppError::Validation(_)))); + } + + #[test] + fn geometry_formats_valid_coordinates() { + let location = Location { + latitude: 12.5, + longitude: -45.25, + }; + let result = location.geometry(); + + assert!(result.is_ok(), "valid coordinates were rejected: {:?}", result.as_ref().err()); + if let Ok(geometry) = result { + assert_eq!(geometry, "SRID=4326;POINT(-45.25 12.5)"); + } + } } diff --git a/backend/src/graphql/objects/s3_object.rs b/backend/src/graphql/objects/s3_object.rs index de29370..5de0321 100644 --- a/backend/src/graphql/objects/s3_object.rs +++ b/backend/src/graphql/objects/s3_object.rs @@ -1,7 +1,6 @@ use { crate::{ ContextWrapper, - SharedState, db::queries::{ SELECT_ALL_OBJECTS_QUERY, SELECT_OBJECT_BY_ID_QUERY, @@ -10,8 +9,10 @@ use { SELECT_OBJECTS_BY_USER_ID_QUERY, SELECT_VISIBLE_OBJECTS_QUERY, }, + errors::AppError, graphql::objects::location::Location, }, + anyhow::Context as AnyhowContext, async_graphql::{ Context, Enum, @@ -19,11 +20,7 @@ use { ID, Object, }, - axum::http::Method, - deadpool_postgres::Manager, - futures::future::join_all, jiff::Timestamp, - minio::s3::types::S3Api, postgres_types::{ FromSql, ToSql, @@ -35,7 +32,6 @@ use { std::{ fmt, str::FromStr, - sync::Arc, }, tokio_postgres::Row, }; @@ -93,10 +89,39 @@ where } } +/// Emits a 64-bit id as a JSON string. +/// +/// Matches the GraphQL `ID` wire format and protects JavaScript clients from the +/// `Number` precision ceiling at 2^53. +fn serialize_i64_as_string( + value: &i64, + serializer: S, +) -> Result +where + S: Serializer, { + serializer.collect_str(value) +} + +/// Whether objects of this content type must be served as a download rather than +/// rendered inline. +/// +/// Script-capable types (currently only SVG) are forced to +/// `Content-Disposition: attachment` on their presigned GET URL, so a direct +/// top-level navigation downloads the file instead of executing embedded script +/// in the storage origin. In-app ``/`